简体   繁体   English

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

[英]How to add methods of class to a list inside the class with a decorator

I would like to update a "class-wide" list from a decorator that decorates the class' methods and adds each decorated method to that list. 我想从装饰器更新一个“全类”列表,该装饰器装饰该类的方法并将每个装饰的方法添加到该列表中。

This is what came to mind: 这是我想到的:

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

class Spam:
    eggs = []

    @add
    def meth(self):
        pass

This won't work though because Spam hasn't finished defining itself when @add is reached, and thus add raises a NameError , as pointed out in the comments. 不过这不会起作用,因为到达@add时, Spam尚未完成自身的定义,因此add会引发NameError ,如注释中所指出。

I also tried a class method: 我也尝试了一个类方法:

class Spam:
    eggs = []

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

    @add
    def meth(self):
        pass

But this doesn't work either because when @add is reached, add is bound to the classmethod decorated instance, which is not callable. 但这也不起作用,因为到达@add时, add绑定到装饰了classmethod实例,该实例不可调用。


Here is what I need this for: 这是我需要的:

I have a class with several methods that take one argument (besides self ) that transform that object in such a way that these methods may be composed with one another. 我有一类带有几种方法的类,这些方法采用一个参数( self除外),该参数以一种可以使这些方法相互组合的方式转换该对象。 I want to decorate each of these in such a way that they're automatically added to a list in the class. 我想以一种将它们自动添加到该类列表中的方式来装饰它们。

Eg: 例如:

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

I could add the methods in the list manually, but I find the decorator to be clearer since it is close to the definition of the method, and thus it is easier to remember to decorate a new method when defining it than adding it to the list manually. 我可以手动将方法添加到列表中,但是我发现装饰器更加清晰,因为它与方法的定义很接近,因此与定义新方法相比,记住它来装饰新方法要容易得多。手动。

Your current approach fails because when transform_meth is called, TextProcessor isn't bound to anything yet (or if it is, that object gets overwritten when the class statement completes). 您当前的方法失败了,因为调用了transform_meth时, TextProcessor尚未绑定到任何对象(或者如果绑定了,则在class语句完成时该对象将被覆盖)。

The simple solution would be to define transform_meth inside the class statement, so that it could simply declare transforms as a nonlocal variable. 一种简单的解决方案是在class语句内定义transform_meth ,这样它就可以简单地transforms声明为一个非局部变量。 However, that won't work because a class statement doesn't establish a new scope. 但是,这将不起作用,因为class语句未建立新的作用域。

Instead, you can define a function that creates the decorator, which takes the desired list (at that point a just a name in the body of the class statement, not from any assumed scope). 相反,您可以定义一个创建装饰器的函数,该装饰器将获取所需的列表(此时, class声明主体中仅是一个名称,而不是来自任何假定的范围)。 That function returns a closure over the list argument so that you can append to it. 该函数在list参数上返回一个闭包,以便您可以附加到它。

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

Since the arg of each method is self you can append to the object instance like so: 由于每个方法的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)

Output: 输出:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM