0

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)

Community
  • 1
  • 1
A T
  • 13,008
  • 21
  • 97
  • 158

1 Answers1

2

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Would I be looking at using a `@staticmethod` here if I wanted to keep the function attached to the object? – A T Feb 09 '13 at 13:15
  • @AT: You cannot mix a methods (static or otherwise) with decorators. Separate the two. You *can* slap on a `@staticmethod` decorator, but it would not directly be callable as a method of it's own. You *can* then use `@Foo.greet_first` outside of the class as a decorator for other methods, yes. – Martijn Pieters Feb 09 '13 at 13:16
  • 1
    @AT: That IDEOne edit is set to C++; that certainly won't work. It works in my Python 2.7 session... Switch to python and it works just fine: http://ideone.com/wa61XF – Martijn Pieters Feb 09 '13 at 13:31
  • That's odd, not sure why it switched back to C++… – A T Feb 09 '13 at 13:43