Python decorators that are part of a base class cannot be used to decorate member functions in inherited classes

Aphex picture Aphex · Sep 23, 2010 · Viewed 10.5k times · Source

Python decorators are fun to use, but I appear to have hit a wall due to the way arguments are passed to decorators. Here I have a decorator defined as part of a base class (the decorator will access class members hence it will require the self parameter).

class SubSystem(object):
    def UpdateGUI(self, fun): #function decorator
        def wrapper(*args):
            self.updateGUIField(*args)
            return fun(*args)
        return wrapper

    def updateGUIField(self, name, value):
        if name in self.gui:
            if type(self.gui[name]) == System.Windows.Controls.CheckBox:
                self.gui[name].IsChecked = value #update checkbox on ui 
            elif type(self.gui[name]) == System.Windows.Controls.Slider:
                self.gui[name].Value = value # update slider on ui 

        ...

I've omitted the rest of the implementation. Now this class is a base class for various SubSystems that will inherit from it - some of the inherited classes will need to use the UpdateGUI decorator.

class DO(SubSystem):
    def getport(self, port):
        """Returns the value of Digital Output port "port"."""
        pass

    @SubSystem.UpdateGUI
    def setport(self, port, value):
        """Sets the value of Digital Output port "port"."""
        pass

Once again I have omitted the function implementations as they are not relevant.

In short the problem is that while I can access the decorator defined in the base class from the inherited class by specifiying it as SubSystem.UpdateGUI, I ultimately get this TypeError when trying to use it:

unbound method UpdateGUI() must be called with SubSystem instance as first argument (got function instance instead)

This is because I have no immediately identifiable way of passing the self parameter to the decorator!

Is there a way to do this? Or have I reached the limits of the current decorator implementation in Python?

Answer

kennytm picture kennytm · Sep 23, 2010

You need to make UpdateGUI a @classmethod, and make your wrapper aware of self. A working example:

class X(object):
    @classmethod
    def foo(cls, fun):
        def wrapper(self, *args, **kwargs):
            self.write(*args, **kwargs)
            return fun(self, *args, **kwargs)
        return wrapper

    def write(self, *args, **kwargs):
        print(args, kwargs)

class Y(X):
    @X.foo
    def bar(self, x):
        print("x:", x)

Y().bar(3)
# prints:
#   (3,) {}
#   x: 3