Новости

18.05.2021

Книга «Python. Лучшие практики и инструменты»

Третье издание «Python. Лучшие практики и инструменты» даст вам инструменты для эффективного решения любой задачи разработки и сопровождения софта. Авторы начинают с рассказа о новых возможностях Python 3.7 и продвинутых аспектах синтаксиса Python. Продолжают советами по реализации популярных парадигм, в том числе объектно-ориентированного, функционального и событийно-ориентированного программирования. Также авторы рассказывают о наилучших практиках именования, о том, какими способами можно автоматизировать развертывание программ на удаленных серверах. Вы узнаете, как создавать полезные расширения для Python на C, C++, Cython и CFFI.

Паттерны доступа к расширенным атрибутам


Изучая Python, многие программисты C++ и Java удивляются отсутствию ключевого слова private. Наиболее близкая к нему концепция — это искажение (декорирование) имени (name mangling). Каждый раз, когда атрибут получает префикс __, он динамически переименовывается интерпретатором:

Доступ к атрибуту __secret_value по его изначальному имени приведет к выбрасыванию исключения AttributeError:

Это сделано специально для того, чтобы избежать конфликта имен по наследованию, так как атрибут переименовывается именем класса в качестве префикса. Это не точный аналог private, поскольку атрибут может быть доступен через составленное имя. Данное свойство можно применить для защиты доступа некоторых атрибутов, однако на практике __ не используется никогда. Если атрибут не является публичным, то принято использовать префикс _. Он не вызывает алгоритм декорирования имени, но документирует атрибут как приватный элемент класса и является преобладающим стилем.

В Python есть и другие механизмы, позволяющие отделить публичную часть класса от приватной. Дескрипторы и свойства дают возможность аккуратно оформить такое разделение.

Дескрипторы

Дескриптор позволяет настроить действие, которое происходит, когда вы ссылаетесь на атрибут объекта.

Дескрипторы лежат в основе организации сложного доступа к атрибутам в Python. Они используются для реализации свойств, методов, методов класса, статических методов и надтипов. Это классы, которые определяют, каким образом будет получен доступ к атрибутам другого класса. Иными словами, класс может делегировать управление атрибута другому классу.

Классы дескрипторов основаны на трех специальных методах, которые формируют протокол дескриптора:

__set__(self, obj, value) — вызывается всякий раз, когда задается атрибут. В следующих примерах мы будем называть его «сеттер»;

__get__(self, obj, owner=None) — вызывается всякий раз, когда считывается атрибут (далее геттер);

__delete__(self, object) — вызывается, когда del вызывается атрибутом.

Дескриптор, который реализует __get__ и __set__, называется дескриптором данных. Если он просто реализует __get__, то называется дескриптором без данных.

Методы этого протокола фактически вызываются методом __getattribute__() (не путать с __getattr__(), который имеет другое назначение) при каждом поиске атрибута. Всякий раз, когда такой поиск выполняется с помощью точки или прямого вызова функции, неявно вызывается метод __getattribute__(), который ищет атрибут в следующем порядке.

  1. Проверяет, является ли атрибут дескриптором данных на объекте класса экземпляра.
  2. Если нет, то смотрит, найдется ли атрибут в __dict__ объекта экземпляра.
  3. Наконец, проверяет, является ли атрибут дескриптором без данных на объекте класса экземпляра.


Вот пример его использования в интерактивном режиме:

Иными словами, дескрипторы данных имеют приоритет над __dict__, который, в

Вот пример его использования в интерактивном режиме:свою очередь, имеет приоритет над дескрипторами без данных.

Для ясности приведем пример из официальной документации Python, в котором показано, как дескрипторы работают в реальном коде:

Вот пример его использования в интерактивном режиме:

Пример ясно показывает, что если класс имеет дескриптор данных для этого атрибута, то вызывается метод __get__(), чтобы вернуть значение каждый раз, когда извлекается атрибут экземпляра, а __set__() вызывается всякий раз, когда такому атрибуту присваивается значение. Использование метода __del__ в предыдущем примере не показано, но должно быть очевидно: он вызывается всякий раз, когда атрибут экземпляра удаляется с помощью оператора del instance.attribute или delattr(instance, 'attribute').

Разница между дескрипторами с данными и без имеет большое значение по причинам, которые мы упомянули в начале подраздела. В Python используется протокол дескриптора для связывания функций класса с экземплярами через методы. Они также применяются в декораторах classmethod и staticmethod. Это происходит потому, что функциональные объекты по сути также являются дескрипторами без данных:

Это верно и для функций, созданных с помощью лямбда-выражений:

Таким образом, если __dict__ не будет иметь приоритет над дескрипторами без данных, мы не сможем динамически переопределить конкретные методы уже созданных экземпляров во время выполнения. К счастью, благодаря тому, как дескрипторы работают в Python, это возможно; поэтому разработчики могут выбирать, в каких экземплярах что работает, не используя подклассы.

Пример из реальной жизни: ленивое вычисление атрибутов. Один из примеров использования дескрипторов — задержка инициализации атрибута класса в момент доступа к нему из экземпляра. Это может быть полезно, если инициализация таких атрибутов зависит от глобального контекста приложения. Другой случай — когда такая инициализация слишком затратна, и неизвестно, будет ли атрибут вообще использоваться после импорта класса. Такой дескриптор можно реализовать следующим образом:

Ниже представлен пример использования:

Официальная библиотека OpenGL Python на PyPI под названием PyOpenGL использует такую технику, чтобы реализовать объект lazy_property, который является одновременно декоратором и дескриптором данных:

Такая реализация аналогична использованию декоратора property (о нем поговорим позже), но функция, которая оборачивается декоратором, выполняется только один раз, а затем атрибут класса заменяется значением, возвращенным этим свойством функции. Данный метод часто бывает полезен, когда необходимо одновременно выполнить два требования:

  • экземпляр объекта должен быть сохранен как атрибут класса, который распределяется между его экземплярами (для экономии ресурсов);
  • этот объект не может быть инициализирован в момент импорта, поскольку процесс его создания зависит от некоего глобального состояния приложения/контекста.


В случае приложений, написанных с использованием OpenGL, вы будете часто сталкиваться с такой ситуацией. Например, создание шейдеров в OpenGL обходится дорого, поскольку требует компиляции кода, написанного на OpenGL Shading Language (GLSL). Разумно создавать их только один раз и в то же время держать их описание в непосредственной близости от классов, которым они нужны. С другой стороны, шейдерные компиляции не могут быть выполнены без инициализации контекста OpenGL, так что их трудно определить и собрать в глобальном пространстве имен модуля на момент импорта.

С полным содержанием статьи можно ознакомиться на сайте "Хабрахабр":

https://habr.com/ru/company/piter/blog/556786/


Комментарии: 0

Пока нет комментариев


Оставить комментарий






CAPTCHAОбновить изображение

Наберите текст, изображённый на картинке

Все поля обязательны к заполнению.

Перед публикацией комментарии проходят модерацию.