views:

85

answers:

4

First time poster, long time reader, so bear with me:

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?

+1  A: 

You need to use an instance of SubSystem to do your decorating or use a classmethod as kenny suggests.

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

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

You decide which to do by deciding if you want all subclass instances to share the same GUI interface or if you want to be able to let distinct ones have distinct interfaces.

If they all share the same GUI interface, use a class method and make everything that the decorator accesses a class instance.

If they can have distinct interfaces, you need to decide if you want to represent the distinctness with inheritance (in which case you would also use classmethod and call the decorator on the subclasses of SubSystem) or if it is better represented as distinct instances. In that case make one instance for each interface and call the decorator on that instance.

aaronasterling
+3  A: 

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
KennyTM
This worked! Never would have thought to turn the decorator into a classmethod.
Aphex
For future reference, this was literally a one line fix by adding @classmethod before def UpdateGUI(self, fun).
Aphex
@Aphex. you should replace the use of `self` with the use of `cls` as KennyTM showed. This will make your code much easier to read.
aaronasterling
I just noticed that. Now I'm confused. Kenny used `cls` in the function header but not in its body - I'm assuming he meant to use `cls` instead of `self` throughout the whole example?
Aphex
@Aphex: The `self` is from the `wrapper`...
KennyTM
Then how is `self` defined in the scope of `foo`? Is it being derived from `cls` somehow? This seems very implicit and nonpythonic (I haven't had much experience with classmethods)...
Aphex
@Aphex: `self` is not defined in scope of `foo`. It is an explicit parameter in the `wrapper` function.
KennyTM
Ah of course. So I'm assuming due to `foo` being a classmethod the `cls` argument is handled by the `@classmethod` decorator to bring the class into scope so `self` can be used?
Aphex
@Aphex: Actually even if the method is not a @classmethod, `self` can still be used. "`self`" is not a keyword, it is just "the first argument of the function" by convention. You could write `def foo(self_, fun):`, for instance.
KennyTM
+1  A: 

It might be easier to just pull the decorator out of the SubSytem class: (Note that I'm assuming that the self that calls setport is the same self that you wish to use to call updateGUIField.)

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

class SubSystem(object):
    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 
        print(name,value)

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

do=DO()
do.setport('p','v')
# ('p', 'v')
unutbu
Sure, I have other decorators in my project that just sit on their own and I import them as needed. However since this decorator uses an instance specific member (self.updateGUIField) it has to be part of the class.
Aphex
@Aphex: Did you try my code? I think it works fine, without the decorator being part of the class.
unutbu
+2  A: 

You've sort of answered the question in asking it: what argument would you expect to get as self if you call SubSystem.UpdateGUI? There isn't an obvious instance that should be passed to the decorator.

There are several things you could do to get around this. Maybe you already have a subSystem that you've instantiated somewhere else? Then you could use its decorator:

subSystem = SubSystem()
subSystem.UpdateGUI(...)

But maybe you didn't need the instance in the first place, just the class SubSystem? In that case, use the classmethod decorator to tell Python that this function should receive its class as the first argument instead of an instance:

@classmethod
def UpdateGUI(cls,...):
    ...

Finally, maybe you don't need access to either the instance or the class! In that case, use staticmethod:

@staticmethod
def UpdateGUI(...):
    ...

Oh, by the way, Python convention is to reserve CamelCase names for classes and to use mixedCase or under_scored names for methods on that class.

katrielalex
@Aaron: true, thanks.
katrielalex
@self.UpdateGUI doesn't work, because self has no meaning in the scope of the class definition. That's why I used the name of the base class.
Aphex