Декоратор скрывает self

Я пытаюсь создать декоратор, который похож на property для своих задач, но моя реализация, которая подобна property на чистом Python не работает. Я не могу получить доступ к переменным класса до оборачивания через self. Вот минимальный воспроизводимый пример:

class MyWrapper(object):
    def __init__(self, first=None, second=None) -> None:
        self.first = first
        self.second = second

    def __call__(self, *args, **kwargs):
        if self.first is not None:
            self.first(*args, **kwargs)
        
        if self.second is not None:
            self.second(*args, **kwargs)
    
    def second_handler(self, func):
        wrapper = type(self)(self.first, func)
        return wrapper
        
        
class TestClass(object):
    def __init__(self, name) -> None:
        self.name = name
    
    @MyWrapper
    def foo(self):
        print(f'Hello, {self.name}!')
    
    @foo.second_handler
    def foo(self):
        print(f'Bye, {self.name}!')
    
    
def main(*args, **kwargs):
    instance = TestClass('User')
    instance.foo()


if __name__ == '__main__':
    main()

Должен вывести:

Hi User!
Bye, User!

В момент вызова, self подменяется декоратором и я теряю связь с атрибутами исходного класса.


Ответы (1 шт):

Автор решения: insolor

Нужно реализовать дескриптор — специальный класс, который содержит метод __get__ (и другие методы, например __set__, если нужно переопределить присваивание атрибуту объекта). Такой класс может быть использован как декоратор для методов. Собственно в примере по вашей ссылке property и реализован как дескриптор (методы __get__, __set__, __delete__, __set_name__ - это специфические для дескрипторов методы).

Пришлось немного поколодовать, потому что сходу не вспомнил как это работает, минимальный пример такой:

class MyWrapper:
    _second_handler = None
    
    def __init__(self, method):
        self.method = method
    
    def __get__(self, obj, objtype=None):
        self.obj = obj
        return self
    
    def __call__(self, *args, **kwargs):
        self.method(self.obj, *args, **kwargs)
        if self._second_handler is not None:
            self._second_handler(self.obj, *args, **kwargs)
    
    def second_handler(self, method):
        self._second_handler = method
        return self


class TestClass:
    def __init__(self, name) -> None:
        self.name = name
    
    @MyWrapper
    def foo(self):
        print(f'Hello, {self.name}!')
    
    @foo.second_handler
    def foo(self):
        print(f'Bye, {self.name}!')


def main():
    instance = TestClass('User')
    instance.foo()


if __name__ == '__main__':
    main()

Тут __get__ будет вызван при попытке обращения к атрибуту foo, в него же в параметр obj передастся объект, у которого атрибут запрашивается, его нужно сохранить и использовать как self при вызове сохраненных методов.

→ Ссылка