Декоратор для изменения состояния объекта

#python #class #inheritance #decorator

#python #класс #наследование #декоратор

Вопрос:

Я хочу создать класс для наследования в другом месте и использовать декоратор для хранения определенных методов в атрибуте.

Я попробовал следующий декоратор

 def filtermethod(f):
    def wrapped(self, *args, **kwargs):
        self.methods.append(f)
        return f(self, *args, **kwargs)
    return wrapped
  

и определите классы с помощью

 class foo:
    def __init__(self):
        self.methods = []

    def apply_filter(self, x):
        for fun in self.methods:
            x = fun(x)
        return x

class foo2(foo):
    @filtermethod
    def method1(self, x):
        return [i for i in x if i > 5]

    @filtermethod
    def method2(self, x):
        return [i for i in x if i < 18]
  

и протестируйте класс с помощью следующего

 foo2().apply_filter([1, 4, 1, 5, 73, 25, 7, 2, 26, 13, 46, 9])
  

и ожидайте, что все оформленные функции будут применены к аргументу, но я вижу

 [1, 4, 1, 5, 73, 25, 7, 2, 26, 13, 46, 9]
  

вместо

 [7,13,9]
  

В принципе, я хочу добавить каждую функцию, которая украшена @filtermethod атрибутом self.methods , (для последовательного применения с self.apply_filter ), но я просто не могу.

Есть какие-либо подсказки?

Комментарии:

1. единственный раз, когда вы добавляете обернутый метод в self.methods , это когда вы на самом деле вызываете обернутый метод — например, method1 или method2 .

Ответ №1:

filtermethod сам вызывается по мере создания класса. следовательно, вам нужно что-то сделать на уровне класса.

ниже я изменил ваш код, чтобы показать, как это может работать. все, что я делаю, это то, что декоратор помечает функции, которые он оборачивает, переменной, которая __init_subclass__ выбирает и добавляет к _methods (как несвязанный метод).

 def filtermethod(f):
    f._is_filter_method = True
    return f


class foo:
    def __init_subclass__(cls, **kwargs):
        funcs = (getattr(cls, v) for v in dir(cls))
        cls._methods = [f for f in funcs if hasattr(f, '_is_filter_method')]

    def apply_filter(self, x):
        for fun in self._methods:
            x = fun(self, x)
        return x


class foo2(foo):
    @filtermethod
    def method1(self, x):
        return [i for i in x if i > 5]

    @filtermethod
    def method2(self, x):
        return [i for i in x if i < 18]


class foo3(foo2):
    @filtermethod
    def method1(self, x):
        return [i for i in x if i == 4]
  

редактировать: исправлено для переопределения

Комментарии:

1. Спасибо. Могу ли я перезаписать на method1 с новым дочерним классом foo3(foo2) ?

2. с новым классом foo3(foo2) я не смог перезаписать method1 должным образом. method2 Либо стал неактивным, либо оба метода вообще не работали

3. @mccandar попробуйте это сейчас — исправил

Ответ №2:

Если вы запустите свой код и посмотрите на список методов, вы заметите, что он пуст. Декоратор будет украшать метод при определении, а не при инициализации экземпляра. Один из способов увидеть, как это происходит, — поместить некоторые отпечатки в декоратор и запустить только определение класса.

Чтобы достичь желаемого, одним из способов было бы использовать переменную класса в качестве реестра методов и слегка изменить декоратор как:

 def filtermethod(registry):
    def inner(f):
        registry.append(f)
        def wrapped(*args, **kwargs):
            return f(*args, **kwargs)
        return wrapped
    return inner

class foo:
    methods = []

    def apply_filter(self, x):
        for fun in foo.methods:
            x = fun(self, x)
        return x

class foo2(foo):
    @filtermethod(foo.methods)
    def method1(self, x):
        return [i for i in x if i > 5]

    @filtermethod(foo.methods)
    def method2(self, x):
        return [i for i in x if i < 18]
  

Обратите внимание, что self передается в качестве аргумента в apply_filter методе.

Когда вы запускаете:

 foo2().apply_filter([1, 4, 1, 5, 73, 25, 7, 2, 26, 13, 46, 9])
  

Вы получите:

 [7, 13, 9]