18

I'm not quite sure how to use a decorator on an inherited method. Normally decorators are put before the definition but for an inherited function the definition is given in the parent and not the child class. I would like to avoid rewriting the definition of the method in the child class and simply specify to put the decorator around the inherited method.

To make myself clearer, here is a working example of what I mean:

class Person():
    def __init__(self, age):
        self.age = age

    @classmethod
    def gets_drink(cls, ):
        print "it's time to get a drink!"
        return "A beer"

def dec(some_f):
    def legal_issue(obj,):
        some_f(obj)
        return 'A Coke'
    return legal_issue

class Child(Person):
    def __init__(self, age):
        Person.__init__(self, age=age)

    #in principle i'd like to write
    #@dec
    #self.gets_drink
    #which does not work
    #the only working way I found is by defining the method as a classmethod
    #and write this:

    @dec
    def gets_drink(self, ):
        return Person.gets_drink()

dad = Person(42)
son = Child(12)

print dad.gets_drink()
print son.gets_drink()

The way it is done in this example works, but it looks somehow weird to me, replacing the method with a call to the original method only to be able to put the decorator. Also, for this to work, the method needs to be defined as a classmethod in the parent class.

Am I missing something? How would experienced developers do this?

Any help, insights, critical comments and free coffees are much appreciated!

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
j-i-l
  • 10,281
  • 3
  • 53
  • 70
  • 1
    I am not following you here. Why did you replace `gets_drink` with a call to the original? – Martijn Pieters Jul 15 '14 at 21:24
  • 2
    You mean you want to know if using a decorator is a better way to augment an inherited method than to use `super()` to call the original? – Martijn Pieters Jul 15 '14 at 21:25
  • @MartijnPieters: initially I wanted to modify some of the arguments for objects of the child class and as far as I know, this can be easily done with a decorator. – j-i-l Jul 15 '14 at 21:32
  • I am not really seeing it; you can easily give the new override method a different signature. You don't *have* to use the same arguments as the overridden method of the parent class. – Martijn Pieters Jul 15 '14 at 21:34
  • @MartijnPieters: yes I know. And I'm well aware that this dummy example up there would look nicer if I'd simply overwrite the method. But the question is a more general one, decorators are useful and suppose for the parent class our method does not need a decorator, but for the child class it does. In this case I'd go for a solution looking somehow like the example. Would you do it differently? – j-i-l Jul 15 '14 at 21:40
  • Absolutely; de decorator function would live outside the class body, for example. – Martijn Pieters Jul 15 '14 at 22:30
  • Is it a problem to put it inside the class like this? For me this does not seem particularly unnatural. – j-i-l Jul 15 '14 at 22:34
  • It is now a (broken) method on your class. Sure, the name starts with a `_` *but it isn't a method*. Moreover, it isn't the place where experienced Python devs would look for the definition of the decorator, it certainly surprised me as I was looking for a standalone function instead. – Martijn Pieters Jul 15 '14 at 22:36
  • I get it, it lacks a positional argument, right? Hm, so the approach to take is to define the decorator function outside the class body. – j-i-l Jul 15 '14 at 22:40
  • I cannot see though why python happily _breaks_ the method without raising any exceptions. – j-i-l Jul 15 '14 at 22:42
  • Python didn't break it, you did by putting in a function that cannot be used as a method. – Martijn Pieters Jul 15 '14 at 22:48
  • There is no exception to throw, not until you try to use it as a method. Welcome to a dynamic language! – Martijn Pieters Jul 15 '14 at 22:51
  • Changed the example: the decorator function lives outside the class now. But even if the decorator lives outside the class body (which is certainly how this should be done), the gets_drink method still needs to be decorated. Is in the example not how this should be done? – j-i-l Jul 15 '14 at 22:59
  • @MartijnPieters: Edited the question, hope it's clearer now. – j-i-l Jul 16 '14 at 08:37
  • Yes, thank you, now your issue is a lot clearer. – Martijn Pieters Jul 16 '14 at 10:41

1 Answers1

20

You don't have to use a decorator in decorator syntax. It is just a callable:

class Child(Person):
    def __init__(self, age):
        Person.__init__(self, age=age)

    gets_drink = dec(Person.gets_drink.__func__)

This adds the return value of the decorator to the child class as a method with the same name. The __func__ attribute unwraps the bound (class) method, retrieving the original function.

Note that your new method is now a regular method, not a classmethod, you'd have to re-wrap the result in a classmethod() call for that.

This works because the syntax

@decorator_expression
def function_definition():
    pass

is just syntactic sugar for:

def function_definition():
    pass
function_definition = decorator_expression(function_definition)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Cool, thanks for the answer! Well in the example the method is defined as a classmethod just to make it work - it does not need to be a class method. In fact, if `gets_drink` is not a classmethod, then the `__func__` should also not be needed. – j-i-l Jul 16 '14 at 11:01
  • @jojo: In Python 2 you'd still want to use `__func__` as a unbound method object is returned. Unwrapping avoids adding an unnecessary reference to the parent class and layering. In Python 3, accessing the function on the class returns just the function. – Martijn Pieters Jul 16 '14 at 11:03
  • But I'm still wondering, is how I did in the example a bad approach? – j-i-l Jul 16 '14 at 11:03
  • @jojo: **If** you want to apply a decorator to an inherited method in a subclass, what I did is the better approach. – Martijn Pieters Jul 16 '14 at 11:04
  • @MarijnPieters: Totally agree with you that your approach looks better. But this is not what I'm still wondering about. I'm wondering if how it is done in the example is an approach to avoid and if so why. – j-i-l Jul 16 '14 at 11:06
  • @jojo: You already answered that; you have to create an 'empty' body that just calls the original version. – Martijn Pieters Jul 16 '14 at 11:15