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

时间:2019-07-10 18:05:29

标签: python decorator class-members

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

这是我想到的:

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

class Spam:
    eggs = []

    @add
    def meth(self):
        pass

这是行不通的,因为到达Spam@add尚未完成自身的定义,因此add会引发NameError,如评论。

我也尝试了一个类方法:

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

我可以手动在列表中添加方法,但是我发现装饰器更加清晰,因为它与方法的定义很接近,因此与定义新方法相比,记住它来装饰新方法要容易得多手动添加到列表中。

2 个答案:

答案 0 :(得分:1)

您当前的方法失败了,因为调用transform_meth时,TextProcessor尚未绑定任何内容(或者如果绑定了该对象,则class语句完成时该对象将被覆盖)。

简单的解决方案是在transform_meth语句中定义class,以便可以简单地将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

答案 1 :(得分:0)

由于每个方法的arg是self,因此您可以像这样将其附加到对象实例:

#b

输出:

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)