Почему первый аргумент метода класса python изменяется при использовании в качестве декоратора

#python #python-decorators

#python #python-декораторы

Вопрос:

Рассмотрим следующий код:

 class C:
    def decorator(self):
        print(self)
        def inner(*args):
            return self(*args)
        return inner

    @decorator
    def decorated(self):
        ...


if __name__ == '__main__':
    c = C()
    c.decorator() # 1
    c.decorated() #2
  

Результат таков:

 <function C.decorated at 0x1121cd9d8>
<__main__.C object at 0x111f5a8d0>
  
  1. Когда decorator функция вызывается как обычная функция класса, первым аргументом, который она получает, является экземпляр класса.
  2. Однако при decorated вызове и decorator использовании функции в качестве декоратора первым аргументом является decorated функция; decorator теперь функция ведет себя практически так же, как обычная функция, определенная вне класса.

Интуитивно, поскольку decorator функция определяется как функция внутри класса без @staticmethod @classmethod декоратора or , я подумал, что первым аргументом, который он получает, всегда должен быть экземпляр класса. Однако это было не так.

Я не часто вижу этот шаблон кодирования, но широко ли используется этот шаблон кодирования? Каковы возможные подводные камни при использовании этого шаблона кодирования?

Ответ №1:

Я внес небольшое изменение в ваш код и попытался объяснить, что происходит в комментариях:

 class C:
    def decorator(self, count=[0]):
        print(count, ":", self)
        count[0]  = 1
        def inner(*args):
            return self(*args)
        return inner

    @decorator
    def decorated(self):
        print(self)

if __name__ == '__main__':
    c = C() #0 Applies the decorator like
            #  decorated = decorator(decorated)
            #  Above, self equals decorated function, and gets printed
            #  inside decorator function.
            #  The decorated function is now a function that returns decorated(*args), actually None, but prints self, at call moment (not now).
    c.decorator() #1 normal bound method call, self is c
    print("[2] : ", end="") # for visual reference
    c.decorated() #2 calls C.decorated(c) so now self is c again and decorated is the result of decorated(c) that returns None but prints self (equals c), with no print from the decorator bound method, that does not run.
  

Выводит:

 [0] : <function C.decorated at 0x7f26945b4ee0>
[1] : <__main__.C object at 0x7f26945bddc0>
[2] : <__main__.C object at 0x7f26945bddc0>
  

Я думаю, что это использование немного сбивает с толку, поэтому, если у вас нет конкретной причины для этого… Однако я видел некоторый продвинутый код с декораторами или метаклассами, который может быть трудным для понимания. Это всегда вопрос того, действительно ли нужно что-то стоящее. Я предлагаю вам поискать типичные способы использования декоратора и декоратора класса. Редко встречающийся внешний вид отличается от широко используемого.

Ответ №2:

Кажется, что «проблема» на самом деле заключается в инициализации

 class C:
    def decorator(self):
        print(self)
        def inner(*args):
            return self(*args)
        return inner
    @decorator
    def decorated(self):
        pass
if __name__ == '__main__':
    c = C()
    #c.decorator() # 1
    #c.decorated() #2
  

python test.py

 <function C.decorated at 0x00000289DD5BE160>
  

Итак … при инициализации класса с декорированными функциями сначала он изменяет функцию (для запуска декоратора вместо декорированного).
Вероятно, есть какая-то документация, которую можно прочитать. Я никогда не использовал декораторы внутри класса, только отдельно.