В обоих примерах используется возможность вызвать объекты, которые не являются функциями: вызов cached_property.__init__() позволяет проинициализировать экземпляры класса, чтобы их можно быть применять как обычные функции, а вызов Response.__call__() позволяет объекту класса Response вызвать как функцию самого себя.
В последнем фрагменте используется реализация некоторых классов-примесей (каждый из них определяет какую-либо функциональность в объекте класса Request), характерная для Werkzeug, чтобы продемонстрировать, что такие классы — это тоже отличная штука.
Декораторы, основанные на классах (питонский способ использовать динамическую типизацию)
Werkzeug применяет утиную типизацию для того, чтобы создать декоратор @cached_property. Когда мы говорили о свойстве, описывая проект Tablib, то упоминали, что оно похоже на функцию. Обычно декораторы являются функциями, но поскольку тип ничем не навязывается, они могут быть любым вызываемым объектом: свойство на самом деле является классом. (Вы можете сказать, что оно задумывалось как функция, поскольку его имя не начинается с прописной буквы, а в PEP 8 говорится, что имена классов должны начинаться c прописной буквы.) При использовании нотации, похожей на вызов функции (property()), будет вызван метод property.__init__() для инициализации и возврата экземпляра свойства — класс, для которого соответствующим образом определен метод __init__(), работает как вызываемая функция. Кря.
В следующем фрагменте кода содержится полное определение свойства cached_property, которое является подклассом класса property. Документация класса cached_property говорит сама за себя. Когда это свойство будет использоваться для декорирования BaseRequest.form в коде, который мы только что видели, instance.form будет иметь тип cached_property и с точки зрения пользователя будет вести себя как словарь, поскольку для него определены методы __get__() и __set__(). При получении доступа к BaseRequest.form в первый раз он считает данные формы (если она существует), а затем запишет их в instance.form.__dict__, чтобы к ним можно было получить доступ в дальнейшем:
class cached_property(property):
····"""Декоратор, который преобразует функцию в ленивое свойство.
········Обернутая функция в первый раз вызывается для получения результата,
········затем полученный результат используется при следующем обращении к value::
············class Foo(object):
················@cached_property
················def foo(self):
····················# выполняем какие-нибудь важные расчеты
····················return 42
········Класс должен иметь '__dict__' для того, чтобы это свойство работало.
········"""
········# деталь реализации: для подкласса, встроенного
········# в Python свойства-декоратора
········# мы переопределяем метод __get__ так, чтобы получать кэшированное
········# значение.
········# Если пользователь хочет вызвать метод __get__ вручную, свойство будет
········# работать как обычно, поскольку логика поиска реплицируется
········# в методе __get__ при вызове вручную.
········def __init__(self, func, name=None, doc=None):
············self.__name__ = name or func.__name__
············self.__module__ = func.__module__
············self.__doc__ = doc or func.__doc__
············self.func = func
········def __set__(self, obj, value):
············obj.__dict__[self.__name__] = value
········def __get__(self, obj, type=None):
············if obj is None:
················return self
············value = obj.__dict__.get(self.__name__, _missing)
············if value is _missing:
················value = self.func(obj)
················obj.__dict__[self.__name__] = value
············return value
Взглянем на этот код в действии:
>>> from werkzeug.utils import cached_property
>>>
>>> class Foo(object):
… ····@cached_property
… ····def foo(self):
… ········print("You have just called Foo.foo()!")
… ········return 42
…
>>> bar = Foo()
>>>
>>> bar.foo
You have just called Foo.foo()!
42
>>> bar.foo
42
>>> bar.foo # Обратите внимание, сообщение не выводится снова…
42
Response.__call__
Класс Response собран с помощью функциональности класса BaseResponse, как и Request. Мы изучим его интерфейс и не будем смотреть на сам код. Взглянем лишь на строку документации для класса BaseResponse, чтобы узнать, как его использовать.
Читать дальше