如何使用装饰器将类的方法添加到类内的列表中

Posted

技术标签:

【中文标题】如何使用装饰器将类的方法添加到类内的列表中【英文标题】:How to add methods of class to a list inside the class with a decorator 【发布时间】:2019-07-10 18:05:29 【问题描述】:

我想从装饰类的方法并将每个装饰方法添加到该列表的装饰器更新“类范围”列表。

这就是我想到的:

def add(meth: callable):
    Spam.eggs.append(func)
    return meth

class Spam:
    eggs = []

    @add
    def meth(self):
        pass

这不会起作用,因为Spam 在到达@add 时还没有完成自我定义,因此add 会引发NameError,正如cmets 中所指出的那样。

我也尝试了一个类方法:

class Spam:
    eggs = []

    @classmethod
    def add(cls, meth: callable):
        cls.eggs.append(meth)
        return meth

    @add
    def meth(self):
        pass

但这也不起作用,因为当达到 @add 时,add 绑定到不可调用的 classmethod 装饰实例。


这是我需要的:

我有一个类,它有几个方法,它们接受一个参数(除了self),这些方法以这样一种方式转换对象,使这些方法可以相互组合。我想以一种自动将它们添加到类中的列表的方式来装饰它们中的每一个。

例如:

from typing import List

def transform_meth(meth: callable):
    TextProcessor.transforms.add(meth)
    return meth

class TextProcessor:
    transforms: List[callable] = []

    @transform_meth
    def m1(self, text):
        return text

    @transform_meth
    def m2(self, text):
        return text

    def transform(self, text):
        for transform in self.transforms:
            text = transform(text)
        return text

我可以手动添加列表中的方法,但我发现装饰器更清晰,因为它接近方法的定义,因此在定义新方法时更容易记住装饰新方法而不是添加它手动添加到列表中。

【问题讨论】:

如果您的装饰器与Spam 的当前定义如此紧密地绑定,为什么首先要使用装饰器?最起码还是要返回func 我能问一下你为什么这样做而不是使用属性和设置/获取吗? 你能检查你的代码吗,当我运行它时它会给我NameError: name 'Spam' is not defined 与@DeveshKumarSingh 的评论相关,如果Spam 已经定义并且您正在重新定义它,那么您的代码运行没有该错误的唯一方法,在这种情况下foo 正在更新 original 类,当它被调用时,该类在 class 语句完成评估后被替换。 @chepner 这就是问题所在。使用原样的代码,当调用装饰器时,Spam 还不存在,因为定义尚未完成? 【参考方案1】:

您当前的方法失败了,因为当调用 transform_meth 时,TextProcessor 还没有绑定到任何东西(或者如果是,则当 class 语句完成时,该对象会被覆盖)。

简单的解决方案是在class 语句中定义transform_meth,这样它就可以简单地将transforms 声明为非局部变量。但是,这不起作用,因为 class 语句没有建立新的范围。

相反,您可以定义一个创建装饰器的函数,该装饰器采用所需的列表(此时只是class 语句主体中的一个名称,而不是来自任何假定的范围)。该函数返回列表参数的闭包 以便您可以附加到它。

def make_decorator(lst):

    # *This* will be the function bound to the name 'transform_meth'
    def _(meth):
        lst.append(meth)
        return meth

    return _


class TextProcessor:
    transforms: List[callable] = []

    transform_meth = make_decorator(transforms)

    @transform_meth
    def m1(self, text):
        return text

    @transform_meth
    def m2(self, text):
        return text

    def transform(self, text):
        for transform in self.transforms:
            text = transform(text)
        return text

    del transform_meth  # Not needed anymore, don't create a class attribute

【讨论】:

【参考方案2】:

由于每个方法的 arg 都是 self 你可以像这样附加到对象实例:

from functools import wraps

def appender(f):
    @wraps(f)
    def func(*args, **kwargs):
        if f not in args[0].transforms:
            args[0].transforms.append(f)
        return f(*args, **kwargs)
    return func

class Foo(object):
    def __init__(self):
        self.transforms = []
    @appender
    def m1(self, arg1):
        return arg1
    @appender
    def m2(self, arg1):
        return arg1
    def transform(self, text):
        methods = [f for f in dir(self) if not f.startswith("__") and callable(getattr(self,f)) and f != 'transform']
        for f in methods:
            text = getattr(self,f)(text)
        return text

f = Foo()
f.transform('your text here')
print(f.transforms)

输出:

[<function Foo.m1 at 0x1171e4e18>, <function Foo.m2 at 0x1171e4268>]

【讨论】:

这里的问题是该方法每次被调用时都会被添加到列表中(例如,通过再次调用f.m1('a')m1 会重新添加到列表中)。它只能在调用装饰器时添加一次。 ^ 对此进行了编辑。如果您不关心顺序,也可以使用集合而不是列表。 不过,问题仍然存在。这些方法应该按定义的顺序添加,并且与它们是否被直接调用无关。考虑上面的transform 方法。如果我们跳过m1m2 调用,并调用f.transform('some text'),则m1m2 都不会被调用,因为f.transforms 将为空(因为尚未调用方法)。相反,我们希望 m1m2 在定义类后立即出现在列表中(并按此顺序)。 怎么样?如果您希望在实例化时完成,可以将 transform 调用移动到 __init__

以上是关于如何使用装饰器将类的方法添加到类内的列表中的主要内容,如果未能解决你的问题,请参考以下文章

如何装饰班级?

绑定方法与非绑定方法

将装饰器定义为类内的方法

使用装饰器的类内的函数包装器

5.10 绑定方法和非绑定方法

如何使用装饰器将多个 div 或字段集添加到 zend_form?