Подтвердить что ты не робот

Декораторы и метод класса

У меня возникли проблемы с пониманием того, почему происходит следующее. У меня есть декоратор, который ничего не делает, кроме того, что он проверяет, является ли функция методом. Я думал, что понял, что такое метод на 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?

Кто-нибудь может быть так рад и объяснить, почему это происходит?

4b9b3361

Ответ 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) возвращает функцию в этом случае.