基础类的Python装饰器无法装饰继承类中的成员函数

30 投票
4 回答
13095 浏览
提问于 2025-04-16 04:31

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来从继承的类访问基类中定义的装饰器,但当我尝试使用它时,最终会出现这个类型错误:

未绑定的方法UpdateGUI()必须以SubSystem实例作为第一个参数调用(而不是函数实例)

这是因为我没有明显的方法来将self参数传递给装饰器!

有没有办法解决这个问题?还是说我已经碰到了Python当前装饰器实现的限制?

4 个回答

3

把装饰器从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')
3

你在提问的时候其实已经部分回答了问题:如果你调用 SubSystem.UpdateGUI,你希望 self 得到什么参数呢?这里没有明显的实例可以传给装饰器。

不过,你可以采取几种方法来解决这个问题。也许你在别的地方已经创建了一个 subSystem 的实例?那你可以使用它的装饰器:

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

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

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

最后,也许你根本不需要访问实例或类!在这种情况下,可以使用 staticmethod

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

顺便提一下,Python 的约定是将 CamelCase 命名保留给类,而对类的方法则使用混合大小写或下划线命名。

31

你需要把 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

撰写回答