Автостопом по Python.
Примеры структуры из Werkzeug.
В первых двух примерах, связанных со структурой, демонстрируются питонские способы использования динамической типизации. Мы предупреждали, что присваивание переменной разных значений может приводить к появлению проблем (см. подраздел «Динамическая типизация» раздела «Структурируем проект» главы 4), но не упомянули преимущества такого присваивания. Одно из них заключается в том, что вы можете использовать любой тип объекта, который ведет себя предсказуемо. Это называется утиной типизацией. Утиная типизация исповедует следующую философию: «Если что-то выглядит, как утка[70], крякает, как утка, то это и есть утка».
В обоих примерах используется возможность вызвать объекты, которые не являются функциями: вызов 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, чтобы узнать, как его использовать.
В примере, показанном в строках документации, функция index() вызывается в ответ на запрос HTTP. Ответом будет строка Index page.
Эта сигнатура нужна в приложениях WSGI, как указано в PEP 333/PEP 3333.
Класс Response является подклассом BaseResponse, поэтому ответ представляет собой объект класса BaseResponse.
Для ответа 404 требуется лишь установить значение ключевого слова status.
И вуаля — объект response является вызываемой функцией сам по себе, все сопутствующие заголовки и детали имеют разумные значения по умолчанию (либо переопределены, если путь отличается от /).
Как объект класса может быть вызываемой функцией? Дело в том, что для него был определен метод BaseRequest.__call__. В следующем примере кода мы покажем лишь этот метод.
Эта сигнатура позволяет сделать объекты класса BaseResponse вызываемыми функциями.
Здесь учтены требования к вызову приложений WSGI для функции start_response.
А здесь возвращается итерабельный объект типа bytes.
Пора извлечь следующий урок: если язык позволяет что-то сделать, почему бы не сделать это? После того как мы поняли, что можно добавить метод __call__() к любому объекту и сделать его вызываемой функцией, мы можем вернуться к оригинальной документации и еще раз перечитать раздел о модели данных в Python (http://docs.python.org/3/reference/datamodel.html).
Примеси (еще одна отличная штука).
Примеси в Python — это классы, которые предназначены для того, чтобы добавлять определенную функциональность — набор связанных атрибутов. В Python, в отличие от Java, вы можете реализовать множественное наследование. Это означает, что парадигма, при которой создаются подклассы для полдюжины разных классов одновременно, — это один из способов разбить функциональность на отдельные классы. Это похоже на пространства имен.
Подобное разбиение может быть полезно во вспомогательной библиотеке вроде Werkzeug, поскольку она говорит пользователю, какие функции связаны друг с другом, а какие — нет. Разработчик может быть уверен, что атрибуты в одном классе-примеси не будут изменены функциями другого класса-примеси.
В Python для того, чтобы идентифицировать класс-примесь, не нужно ничего, кроме как следовать соглашению, в рамках которого к имени класса добавляется слово «Mixin». Это означает, что, если вы не хотите обращать внимания на порядок разрешения методов, все методы класса-примеси должны иметь разные имена.
В Werkzeug методы класса-примеси иногда могут требовать наличия определенных атрибутов. Эти требования зачастую приводятся в строке документации класса-примеси.
В классе UserAgentMixin нет ничего особенного; он создает подкласс объекта, что выполняется по умолчанию в Python 3 и настоятельно рекомендуется для совместимости с Python 2. Все это нужно делать явно, поскольку «явное лучше, чем неявное».
UserAgentMixin.user_agent предполагает, что существует атрибут self.environ.
При включении в список базовых классов для Request предоставляемый им атрибут становится доступным с помощью вызова Request(environ). user_agent.
Больше ничего нет — мы полностью рассмотрели определение класса Request. Вся функциональность предоставляется базовым классом или классами-примесями. Модульными, подключаемыми и такими же великолепными, как Форд Префект.
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.