Кеннет Рейтц – Автостопом по Python (страница 37)
········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 собран с помощью функциональности класса BaseResponse, как и Request. Мы изучим его интерфейс и не будем смотреть на сам код. Взглянем лишь на строку документации для класса BaseResponse, чтобы узнать, как его использовать.
Как объект класса может быть вызываемой функцией? Дело в том, что для него был определен метод BaseRequest.__call__. В следующем примере кода мы покажем лишь этот метод.
Пора извлечь следующий урок: если язык позволяет что-то сделать, почему бы не сделать это? После того как мы поняли, что можно добавить метод __call__() к любому объекту и сделать его вызываемой функцией, мы можем вернуться к оригинальной документации и еще раз перечитать раздел о модели данных в Python (http://docs.python.org/3/reference/datamodel.html).
Подобное разбиение может быть полезно во вспомогательной библиотеке вроде Werkzeug, поскольку она говорит пользователю, какие функции связаны друг с другом, а какие — нет. Разработчик может быть уверен, что атрибуты в одном классе-примеси не будут изменены функциями другого класса-примеси.
В Python для того, чтобы идентифицировать класс-примесь, не нужно ничего, кроме как следовать соглашению, в рамках которого к имени класса добавляется слово «Mixin». Это означает, что, если вы не хотите обращать внимания на порядок разрешения методов, все методы класса-примеси должны иметь разные имена.
В Werkzeug методы класса-примеси иногда могут требовать наличия определенных атрибутов. Эти требования зачастую приводятся в строке документации класса-примеси.
Базовый класс object добавляет атрибуты по умолчанию, на которые полагаются другие встроенные параметры. Классы, которые не наследуют от класса object, называются старыми или классическими классами. В Python 3 таких классов нет, наследование от класса object выполняется по умолчанию. Это означает, что все классы Python 3 являются новыми. Новые классы доступны в Python 2.7 (их поведение не изменялось с версии Python 2.3), но наследование должно быть прописано явно, и мы считаем, что так нужно делать всегда.
Более подробная информация содержится в документации к Python для новых классов (https://www.python.org/doc/newstyle/), в руководстве по адресу http://www.python-course.eu/classes_and_type.php, а история их создания — в статье по адресу http://tinyurl.com/history-new-style-classes. Рассмотрим некоторые различия между старыми и новыми классами (в Python 2.7; все классы Python 3 являются новыми):
>>> class A(object):
… ····"""Новый класс, он является подклассом object."""
…
>>> class B:
… ····"""Старый класс."""
…
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__',
'__getattribute__', '__hash__', '__init__', '__module__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
>>>
>>> dir(B)
['__doc__', '__module__']
>>>