作为基类一部分的 Python 装饰器不能用于装饰继承类中的成员函数

Posted

技术标签:

【中文标题】作为基类一部分的 Python 装饰器不能用于装饰继承类中的成员函数【英文标题】:Python decorators that are part of a base class cannot be used to decorate member functions in inherited classes 【发布时间】:2011-04-16 11:20:12 【问题描述】:

Python 装饰器使用起来很有趣,但由于参数传递给装饰器的方式,我似乎碰壁了。在这里,我将一个装饰器定义为基类的一部分(装饰器将访问类成员,因此它需要 self 参数)。

class SubSystem(object):
    def UpdateGUI(self, fun): #function decorator
        def wrapper(*args):
            self.updateGUIField(*args)
            return fun(*args)
        return wrapper

    def updateGUIField(self, name, value):
        if name in self.gui:
            if type(self.gui[name]) == System.Windows.Controls.CheckBox:
                self.gui[name].IsChecked = value #update checkbox on ui 
            elif type(self.gui[name]) == System.Windows.Controls.Slider:
                self.gui[name].Value = value # update slider on ui 

        ...

我省略了其余的实现。现在这个类是从它继承的各种子系统的基类——一些继承的类需要使用 UpdateGUI 装饰器。

class DO(SubSystem):
    def getport(self, port):
        """Returns the value of Digital Output port "port"."""
        pass

    @SubSystem.UpdateGUI
    def setport(self, port, value):
        """Sets the value of Digital Output port "port"."""
        pass

我再次省略了函数实现,因为它们不相关。

简而言之,问题是虽然我可以通过将继承类中定义的装饰器指定为 SubSystem.UpdateGUI 来访问基类中定义的装饰器,但在尝试使用它时最终会得到这个 TypeError:

unbound method UpdateGUI() must be called with SubSystem instance as first argument (got function instance instead)

这是因为我没有立即可识别的方式将self 参数传递给装饰器!

有没有办法做到这一点?还是我已经达到了 Python 中当前装饰器实现的极限?

【问题讨论】:

【参考方案1】:

您需要将UpdateGUI 设为@classmethod,并让您的wrapper 知道self。一个工作示例:

class X(object):
    @classmethod
    def foo(cls, fun):
        def wrapper(self, *args, **kwargs):
            self.write(*args, **kwargs)
            return fun(self, *args, **kwargs)
        return wrapper

    def write(self, *args, **kwargs):
        print(args, kwargs)

class Y(X):
    @X.foo
    def bar(self, x):
        print("x:", x)

Y().bar(3)
# prints:
#   (3,) 
#   x: 3

【讨论】:

这成功了!从来没有想过把装饰器变成一个类方法。 为了将来参考,这实际上是通过在 def UpdateGUI(self, fun) 之前添加 @classmethod 来修复的。 @Aphex。如 KennyTM 所示,您应该使用 cls 替换 self 的使用。这将使您的代码更易于阅读。 我刚刚注意到了。现在我很困惑。肯尼在函数头中使用了cls,但没有在其主体中使用——我假设他打算在整个示例中使用cls而不是self 那么selffoo的范围内是怎么定义的呢?它是否以某种方式源自cls?这似乎非常隐含和非pythonic(我对类方法没有太多经验)......【参考方案2】:

将装饰器从 SubSytem 类中拉出来可能更容易: (请注意,我假设调用setportself 与您希望用来调用updateGUIFieldself 相同。)

def UpdateGUI(fun): #function decorator
    def wrapper(self,*args):
        self.updateGUIField(*args)
        return fun(self,*args)
    return wrapper

class SubSystem(object):
    def updateGUIField(self, name, value):
        # if name in self.gui:
        #     if type(self.gui[name]) == System.Windows.Controls.CheckBox:
        #         self.gui[name].IsChecked = value #update checkbox on ui 
        #     elif type(self.gui[name]) == System.Windows.Controls.Slider:
        #         self.gui[name].Value = value # update slider on ui 
        print(name,value)

class DO(SubSystem):
    @UpdateGUI
    def setport(self, port, value):
        """Sets the value of Digital Output port "port"."""
        pass

do=DO()
do.setport('p','v')
# ('p', 'v')

【讨论】:

当然,我的项目中有其他装饰器,它们只是独立存在,我会根据需要导入它们。然而,由于这个装饰器使用一个实例特定的成员(self.updateGUIField),它必须是类的一部分。 @Aphex:你试过我的代码了吗?我认为它工作得很好,没有装饰器是课程的一部分。 这可能会破坏@Aphex 想要的耦合。 UpdateGUI 没有在SubSystem(或SubSystem 的子类)的上下文中被调用是没有意义的,所以它应该是一个静态或类方法。【参考方案3】:

你已经回答了这个问题:如果你打电话给SubSystem.UpdateGUI,你希望self得到什么参数?没有明显的实例应该传递给装饰器。

您可以做几件事来解决这个问题。也许您已经有一个在其他地方实例化的subSystem?然后你可以使用它的装饰器:

subSystem = SubSystem()
subSystem.UpdateGUI(...)

但也许您一开始并不需要实例,只需要 SubSystem?在这种情况下,使用 classmethod 装饰器告诉 Python 这个函数应该接收它的类作为第一个参数而不是实例:

@classmethod
def UpdateGUI(cls,...):
    ...

最后,也许您不需要访问实例或类!在这种情况下,请使用staticmethod

@staticmethod
def UpdateGUI(...):
    ...

哦,顺便说一句,Python 约定是为类保留 CamelCase 名称,并为该类上的方法使用混合大小写或 under_scored 名称。

【讨论】:

@self.UpdateGUI 不起作用,因为 self 在类定义的范围内没有任何意义。这就是我使用基类名称的原因。【参考方案4】:

您需要使用 SubSystem 的实例来进行装饰,或者按照 kenny 的建议使用 classmethod

subsys = SubSystem()
class DO(SubSystem):
    def getport(self, port):
        """Returns the value of Digital Output port "port"."""
        pass

    @subsys.UpdateGUI
    def setport(self, port, value):
        """Sets the value of Digital Output port "port"."""
        pass

您可以通过决定是否希望所有子类实例共享相同的 GUI 界面或是否希望能够让不同的子类实例具有不同的界面来决定做什么。

如果它们都共享相同的 GUI 界面,请使用类方法并将装饰器访问的所有内容都设为类实例。

如果它们可以有不同的接口,您需要决定是否要通过继承来表示独特性(在这种情况下,您还可以使用 classmethod 并在 SubSystem 的子类上调用装饰器)或者是否是更好地表示为不同的实例。在这种情况下,为每个接口创建一个实例并在该实例上调用装饰器。

【讨论】:

它可能有效,但创建基类的单例实例只是为了调用装饰器是一个坏主意。除其他外,self 将引用装饰器和其他方法中的不同对象。然后你必须实例化一个你可能不想要的对象。而是选择其他答案中建议的其他选项,例如定义 staticmethod 或 classmethod。

以上是关于作为基类一部分的 Python 装饰器不能用于装饰继承类中的成员函数的主要内容,如果未能解决你的问题,请参考以下文章

Python装饰器

python装饰器

python装饰器

Python进阶精华-编写装饰器为被包装的函数添加参数

python装饰器了解

Python装饰器简单说明