Python волшебные методы “__getattr__”, “__getattribute__” и другие

За обращение к атрибутам метода отвечают методы класса (это протокол обращения к атрибутам):

  • __getattribute__
  • __getattr__
  • __setattr__
  • __delattr__

Первые два метода отвечают за доступ к атрибутам по чтению. При этом отдельный метод __getattr__ позволяет перехватывать доступ только к не существующим атрибутам. 

При этом можно считать, что при доступе на чтение к любому атрибуту сначала будет вызван __getattribute__, а затем уже если этот метод выбросит исключение AttributeError, то тогда будет вызван __getattr__.

Доступ на запись всегда только через метод __setattr__. 

При этом при удаление будет вызван __delattr__, но если атрибута на самом деле нет, то возникает сначала обращение к __getattribute__ (и далее по цепочке и к __getattr__, если нужно), а затем уже их результат будет передан как параметр __delattr__.

Например:

Этот демо-пример получит следующие результаты:

При этом стоит отметить последний оператор len(m), который показывает, что для вызова специальных методов протокол доступа к атрибутам не применяется. 

 

Протокол Дескриптора

Атрибут объекта, доступ к которому подчиняется протоколу дескриптора.
Для реализации дескриптора классу требуется определить один или несколько методов, входящих в протокол дескриптора:

  • __get__ – доступ на чтение атрибута
  • __set__ – доступ для записи значения атрибута
  • __delete__ – удаление атрибута
  • __set_name__ – Данный метод вызывается непосредственно после того, как создан класс-владелец. Служит для отслеживание имен атрибутов (раньше только через метаклассы).

По умолчанию при получении значения, установке значения или удалении атрибута мы оперируем словарём объекта (и далее класса и его родителей). Иногда в ходе описанных операций может понадобиться дополнительная логика — для этого и существуют дескрипторы. Важно понимать, что работа дескрипторов обеспечивается методами __getattribute__, __setattr__ и __delattr__. И их переопределение (особенно без обращения к оригинальным методам) приведет к отключению механизма дескрипторов.

Существуют два вида дескрипторов:

Вид Признак Пример
дескрипторы не-данных наличие только __get__() staticmethod()classmethod()
дескрипторы данных наличие __get__() и других методов протокола property()

Дескрипторы, в которых определён только данный метод, называют дескрипторами не-данных, в противоположность дескрипторам данных, для которых определяются и другие методы протокола дескриптора. Если в__dict__ владельца имеется запись с ключём, совпадающим с именем атрибута, указывающего на дескриптор, то в случае дескрипторов не-данных при обращении к атрибуту вернётся значение из __dict__, а не из дескриптора. В случае дескрипторов данных наоборот — значение из дескриптора.

Пример дескриптора:

При этом в методах встречается параметры:

  • instance — Экземпляр класса владельца дескриптора, либо ‘None’, если обращаются в контексте класса, а не экземпляра.
  • owner — Класс владельца дескриптора.

Чтобы сделать дескриптор данных, доступный только для чтения, потребуется внутри метода поднять исключение AttributeError.

Деструктор __del__

Позволяет определить поведение экземпляра пользовательского типа при готовности интерпретатора уничтожить его object.__del__(self) self — Ссылка на экземпляр.

Вызывается интерпретатором, когда экземпляр назначен к уничтожению. Также известен под именем «деструктор».
Если метод определён в базовом классе, то потомок должен вызывать его явно, чтобы удаление части экземпляра, реализуемой базовым классом произошло без ошибок.

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

Не гарантируется, что метод будет вызван для объектов, всё ещё существующих на момент выхода интерпретатора.

Вызов del x не приводит напрямую к вызову x.__del__(), а лишь уменьшает значение счётчика ссылок на единицу. Описываемый метод же будет вызван, только когда счётчик ссылок достигнет нуля.

Вот некоторые обстоятельства, которые могут помешать счётчику дойти до нуля:

  • Циклические ссылки (двусвязные списки; древовидные структуры, со ссылками на родителей и детей) — исправляется только разрывом таких ссылок. Циклические ссылки, являющиеся мусором, отлавливаются обнаружителем циклов (он включен по умолчанию), однако могут быть вычищены, только если не заданы __del__() на уровне Питона. За более подробным описанием того, как __del__() влияет на обнаружитель циклов, можно узнать из документации к модулю gc.
  • Ссылка на объект в фрейме стека при возбуждении исключения (трассировка стека, хранимая в sys.exc_info()[2] (-py3.0 sys.exc_traceback) продлевает жизнь фрейма) — исправляется путём освобождения ссылки на объект трассировки, когда в нём более нет надобности (-py3.0 sys.exc_traceback = None).
  • В интерактивном режиме ссылка на объект в фрейме стека, где возбуждено необработанное исключение (трассировка стека, хранимая в sys.last_traceback продлевает жизнь фрейма) — исправляется путём sys.last_traceback = None.

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

Кроме того, когда метод вызывается как реакция на удаление модуля (например, когда исполнение программы завершено), другие глобальные переменные, которые могут быть использованы в методе могут быть уже удалены или находится в процессе удаления (например, при завершение работы механизма импорта). Потому в реализации методов резонно использовать минимум окружения. +py1.5 Питон гарантирует, что глобальные переменные, имена которых начинаются с одиночного почерка, удаляются из модуля прежде остальных. Поэтому если на такие объекты нет ссылок, то они могут являться показателем доступности других объектов модуля на момент исполнения метода.

Обсуждение закрыто.