Search code examples
pythondecoratorclass-method

Using pydispatch and decorators for class methods


I am trying something fairly simple. I want to write a decorator which turns a class method in an event handler using pydispatcher. Here is my down-to-the-essentials approach:

from pydispatch.dispatcher import connect,send,Any

SIGNAL = "test"

def triggered(func):
    connect(func,signal=SIGNAL,sender = Any)
    return func

class P(object):
    @triggered
    def a(self,*args,**kwargs):
        print "yay"

if __name__ == "__main__":
    p = P()
    send(signal=SIGNAL,sender="")

This throws the error:

Traceback (most recent call last):
  File "util.py", line 35, in <module>
    send(signal=SIGNAL,sender="")
  File "/usr/local/lib/python2.7/dist-packages/pydispatch/dispatcher.py", line 338, in send
    **named
  File "/usr/local/lib/python2.7/dist-packages/pydispatch/robustapply.py", line 55, in robustApply
    return receiver(*arguments, **named)
TypeError: a() takes at least 1 argument (0 given)

Any ideas?


Solution

  • You registered a function in a class. This means that just the function is registered, not a bound method. Functions are not bound to an instance, so no self argument is being passed in.

    You cannot hope to use the decorator on regular methods in a class definition, because the required context (an instance) is not available at that time.

    You'd have to create an instance first, then register the method bound to that instance for this to work:

    class P(object):
        def a(self, *args, **kwargs):
            print "yay"
    
    if __name__ == "__main__":
        p = P()
        triggered(p.a)
        send(signal=SIGNAL,sender="")
    

    Now triggered() registers the p.a bound method; calling that method will then pass in p as the self argument when called.

    You could alternatively make the a method a static or class method. A static method is essentially just a function; it never takes a self argument. This means you can register it as a signal handler, but you'll never get the benefits of using a class:

    class P(object):
        @staticmethod
        @triggered
        def a(*args, **kwargs):
            print "yay"
    

    If you make it a classmethod, you'll get the class passed in (still not an instance), and you'll have to do the registering outside the class to ensure you get a bound method:

    class P(object):
        @classmethod
        def a(cls, *args, **kwargs):
            print "yay"
    
    triggered(P.a)