Если вы используете в своем коде длинные регулярные выражения, не забывайте про параметр re.VERBOSE[69] — сделайте их более понятными для других людей. Пример регулярных выражений показан во фрагменте файла werkzeug/routing.py:
import re
····_rule_re = re.compile(r'''
····(?P<static>[^<]*) # static rule data
····<
····(?:
········(?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # имя преобразователя
········(?:\((?P<args>.*?)\))? # аргументы преобразователя
········\: # разделитель переменных
····)?
····(?P<variable>[a-zA-Z_][a-zA-Z0-9_]*) # имя переменной
····>
''', re.VERBOSE)
Примеры структуры из 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__ вручную, свойство будет
69
re.VERBOSE позволяет писать более читаемые регулярные выражения путем изменения обработки пробелов и добавления комментариев. Подробную информацию вы можете получить из документации к re (https://docs.python.org/3/library/re.html).
70
То есть, если объект можно вызвать, его можно проитерировать или же для него определен правильный метод…