Python 装饰器:如何在子类中使用父类装饰器

12 投票
2 回答
15118 浏览
提问于 2025-04-17 02:36

注意:

另一个问题的接受答案展示了如何使用父装饰器。

这个问题的接受答案展示了如何将装饰器移动到模块范围。


编辑:使用之前的例子真是个坏主意。希望这个更清楚:

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

这段代码会产生一个NameError:在B里面找不到'deco'这个名字。装饰器需要在类的范围内,因为我需要访问存储的状态。

第三次编辑:根据Sven的建议,我尝试了这个:

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

现在我遇到了TypeError:do_some_A_thing()需要一个参数(但给了0个)。有什么建议吗?

2 个回答

3

对编辑前问题的回答: 你的问题现在和之前链接的那个问题完全一样。比起链接问题里的答案,解决这个问题的一个更简单的方法就是把装饰器移出类的命名空间。

另外,self.do_something(self) 应该改成 self.do_something(),也就是说不要把 self 传两次。

对编辑前问题的回答: 如果你只想用你的装饰器来装饰实例方法,那就不要把 self 参数合并到 *args 中,而是要明确写出来:

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

话说回来,我觉得把 _deco 放在类的命名空间里并把它变成 staticmethod 没什么意义。直接把它移到模块的命名空间里就好了。

11

问题在于,继承只适用于实例属性的查找,而不适用于类的定义。所以当你在B类中尝试使用A类的装饰器deco时,它找不到这个装饰器。解决办法是把deco放到模块的作用域中,因为self这个名字并没有什么特别的地方,你可以继续使用它。同时,你需要明确地把self传递给func,但在调用self.do_something()时就不需要再传了。下面是更新后的代码:

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()

撰写回答