Декораторы и метод класса
У меня возникли проблемы с пониманием того, почему происходит следующее. У меня есть декоратор, который ничего не делает, кроме того, что он проверяет, является ли функция методом. Я думал, что понял, что такое метод на Python, но, очевидно, это не так:
import inspect
def deco(f):
def g(*args):
print inspect.ismethod(f)
return f(*args)
return g
class Adder:
@deco
def __call__(self, a):
return a + 1
class Adder2:
def __call__(self, a):
return a + 2
Adder2.__call__ = deco(Adder2.__call__)
Теперь выполните следующие действия:
>>> a = Adder()
>>> a(1)
False
2
>>> a2 = Adder2()
>>> a2(1)
True
3
Я ожидал бы, что этот код будет печатать True два раза.
Итак, украшение функции вручную, как в Adder2, не является точным эквивалентом декорирования с помощью функции @deco?
Кто-нибудь может быть так рад и объяснить, почему это происходит?
Ответы
Ответ 1
Внутри определения класса __call__
есть функция, а не метод. Акт доступа к функции посредством поиска атрибутов (например, с использованием точечного синтаксиса), например, с помощью Adder2.__call__
, возвращает несвязанный метод. И a2.__call__
возвращает связанный метод (с self
привязан к a2
).
Обратите внимание, что в Python3 концепция unbound method
была отброшена. Там Adder2.__call__
также является функцией.
Ответ 2
Методы - это функции, связанные с классом. Методы создаются только тогда, когда вы извлекаете их из уже определенного класса; метод - это оболочка вокруг функции, с ссылкой на класс (а также, возможно, ссылка на экземпляр).
Что происходит в первом случае: Python компилирует определение класса Adder
. Он находит определение декоратора и функцию. Декоратору передается функция, возвращая новую функцию. Эта функция добавляется к определению класса (хранится в классе __dict__
). Все это время вы имеете дело с функцией python, а не с методом. Это происходит позже.
Когда вы вызываете a(1)
, поиск показывает, что экземпляр не имеет __call__
, но класс Adder
, поэтому он извлекается с помощью __getattribute__()
. Это находит функцию (ваш декоратор deco
), который является descriptor, поэтому он __get__()
(поэтому Adder.__call__.__get__(a, Adder))
, возвращающий связанный метод, который затем вызывается и передается в значении 1
. Метод привязан, потому что instance
не является None, если Вызывается __get__()
. Ваш декоратор, который завернул функцию во время создания класса, печатает False
, потому что ему была передана развернутая функция для начала.
Во втором случае вы извлекаете метод (снова через __getattribute__()
вызов __get__()
в функции undecorated Adder2.__call__
), на этот раз unbound (поскольку нет экземпляра, только класс, переданный в __get__()
(полный вызов Adder2.__call__.__get__(None, Adder2)
), и вы затем украшаете этот метод. Теперь ismethod()
печатает True.
Обратите внимание, что в Python 3 последний случай изменяется. В Python 3 уже не существует понятия несвязанного метода, только функции и связанные методы. Таким образом, термин "связанный" полностью опущен. Второй случай также печатает False
, поскольку Adder2.__call__.__get__(None, Adder2)
возвращает функцию в этом случае.