Как добавить методы класса в список внутри класса с помощью декоратора - PullRequest
0 голосов
/ 10 июля 2019

Я хотел бы обновить список «для всего класса» из декоратора, который украшает методы класса и добавляет каждый оформленный метод в этот список.

Вот что пришло на ум:

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 ]

1 голос
/ 10 июля 2019

Ваш текущий подход терпит неудачу, потому что, когда вызывается 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
0 голосов
/ 10 июля 2019

Поскольку аргумент каждого метода является 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>]
...