11

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?

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
GeneralBecos
  • 2,476
  • 2
  • 22
  • 32

2 Answers2

9

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()
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • This solution only appears to work if the child class is being defined inside the same module as the child class. If I import the parent class elsewhere and try to define the child class, using the decorator on one of the child class's methods, it is no longer defined. – user5359531 Oct 31 '17 at 18:45
  • @user5359531: What is no longer defined? – Ethan Furman Oct 31 '17 at 22:21
  • I meant that the decorator is no longer defined if you import the parent class from another module – user5359531 Nov 01 '17 at 16:22
  • @user5359531: If the decorator is defined in the same module as the parent, you should be able to import both -- or am I missing something? – Ethan Furman Nov 03 '17 at 18:32
  • yes, what I mean is that you are now *required* to import both. And if you have a lot of decorators, this means you now need to manually include an import statement for them all. Otherwise, you must call them with `@modulename.decorator`. – user5359531 Nov 03 '17 at 18:44
  • @user5359531: True, but nothing special about that. You could leave the decorator in the class, and just prefix it with the class name: `ClassA.decorator`; this saves a little typing. That is not the choice I would make, though. – Ethan Furman Nov 06 '17 at 16:57
3

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.

Community
  • 1
  • 1
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841