Search code examples
pythonmethodsdecoratorselfpython-decorators

Decorators, lambdas and member function calls in Python?


What's the correct syntax?

Programming attempt

class Foo:
    def hello(self):
        print "Hello cruel world!"
 
    def greet_first(self, f):
        self.hello()
        return lambda *args, **kwargs: f(*args, **kwargs)
 
    @greet_first
    def goodbye(self, concat):
        print "Goodbye {0}".format(concat)
 
if __name__=='__main__':
    bar = Foo()
    bar.goodbye(' and thanks for all the fish')

Debug

Traceback (most recent call last):
  File "prog.py", line 1, in <module>
    class Foo:
  File "prog.py", line 9, in Foo
    @greet_first
TypeError: greet_first() takes exactly 2 arguments (1 given)

Reference

Click to run the code (IDEone)


Solution

  • A decorator is called immediately, it is not treated as a method of Foo but rather is seen as a local function instead. The @greet_first syntax effectively means:

     goodbye = greet_first(goodbye)
    

    and is executed immediately. It is not a bound method, so the self parameter is not included. There is no point in making greet_first a method. Move it out and remove the self argument altogether.

    You need to adjust your decorator to return a callable to replace goodbye:

    def greet_first(f):
        def wrapper(self, *args, **kwargs):
            self.hello()
            return f(self, *args, **kwargs)
        return wrapper
    

    so that self.hello() is called every time goodbye is called.

    If you have to make greet_first part of Foo, you can use a @staticmethod decorator but you have to jump through an extra hoop just to be able to use it for other method declarations; you have to treat it as the descriptor it has become and call .__get__() on it:

    class Foo(object):
        def hello(self):
            print "Hello cruel world!"
    
        @staticmethod
        def greet_first(f):
            def wrapper(self, *args, **kwargs):
                self.hello()
                return f(self, *args, **kwargs)
            return wrapper
    
        @greet_first.__get__(object)
        def goodbye(self, concat):
            print "Goodbye {0}".format(concat)
    

    I call .__get__() with an arbitrary type (object in this case) because staticmethod ignores that argument anyway; we can't use Foo here because that class has not yet been finalized while inside the code that is part of it's definition.

    Note that for @staticmethod to work at all you need to inherit from object in Python 2.