Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python decorators: how to use parent class decorators in a child class [duplicate]

Note:

The accepted answer on the other question shows how to use the parent decorater.

The accepted answer on this question shows moving the decorator to the module scope.


EDIT: Using the previous example was a bad idea. Hopefully this is more clear:

class A:
    def deco( func ):
        print repr(func)
        def wrapper( self, *args ):
            val = func( *args )
            self.do_something()
            return val
        return wrapper

    def do_something( self ):
        # Do something
        print 'A: Doing something generic for decoration'

    @deco
    def do_some_A_thing ( self ):
        # Do something 
        print 'A: Doing something generic'

class B ( A ):

    @deco
    def do_some_B_thing( self ):
        # Do something
        print "B: Doing something specific"

a = A()
b = B()
a.do_some_A_thing()
b.do_some_B_thing()

#Expected Output:
    #A: Doing something generic
    #A: Doing something generic for decoration
    #B: Doing something specific
    #A: Doing something generic for decoration

This code generates a NameError: name 'deco' is not defined inside B. The decorator needs to be inside the class scope because I require access to stored state.

Third Edit: On Sven's suggestions, I tried this:

class A:
    def deco( func ):
        def wrapper( self, *args ):
            val = func( *args )
            self.do_something(*args)
            return val
        return wrapper

    def do_something( self ):
        # Do something
        print 'A: Doing something generic for decoration'

    @deco
    def do_some_A_thing ( self ):
        # Do something 
        print 'A: Doing something generic'

    deco = staticmethod(deco)

class B ( A ):

    @A.deco
    def do_some_B_thing( self ):
        # Do something
        print "B: Doing something specific"



a = A()
b = B()
a.do_some_A_thing()
b.do_some_B_thing()

#Expected Output:
    #A: Doing something generic
    #A: Doing something generic for decoration
    #B: Doing something specific
    #A: Doing something generic for decoration

I now have have TypeError: do_some_A_thing() takes exactly 1 argument (0 given). Any pointers?

like image 905
GeneralBecos Avatar asked Sep 19 '11 15:09

GeneralBecos


2 Answers

The problem is that inheritance works for instance attribute lookup, not for class definitions. So when you try to decorate with A.deco in B, it can't find it. The solution is to move deco out to module scope, and because there is nothing magic about the name self, you can keep using it. You also need to explicitly pass self to func, and you do not need to pass it in self.do_something(). Here's the updated code:

def deco( func ):
    print repr( func )
    def wrapper( self, *args ):
        val = func( self, *args )
        self.do_something()
        return val
    return wrapper

class A:
    def do_something( self ):
        # Do something
        print 'A: Doing something generic for decoration'

    @deco
    def do_some_A_thing ( self ):
        # Do something 
        print 'A: Doing something generic'

class B ( A ):

    @deco
    def do_some_B_thing( self ):
        # Do something
        print "B: Doing something specific"

a = A()
b = B()
a.do_some_A_thing()
b.do_some_B_thing()
like image 171
Ethan Furman Avatar answered Nov 03 '22 14:11

Ethan Furman


Answer to edited question: Your question has become an exact duplicate of the question it linked to before the edit. An easier fix than the one given in the answers to the linked question is to simply move the decorator out of the class namespace.

Moreover, self.do_something(self) should be self.do_something() instead -- don't pass self twice.

Answer to question before the edit: If you only ever want to decorate instance methods with your decorator, don't collapse the self parameter into *args, but rather leave it explicit:

def _deco(func):
    def wrapper(self, *args):
        res = func(self, *args)
        self.some_other_baseclass_method(*args)
        return res
    return wrapper

That said, I don't see the point of having _deco inside the class namespace and turning it into a staticmethod. Just move it to the module namespace.

like image 39
Sven Marnach Avatar answered Nov 03 '22 12:11

Sven Marnach