За обращение к атрибутам метода отвечают методы класса (это протокол обращения к атрибутам):
- __getattribute__
- __getattr__
- __setattr__
- __delattr__
Первые два метода отвечают за доступ к атрибутам по чтению. При этом отдельный метод __getattr__ позволяет перехватывать доступ только к не существующим атрибутам.
При этом можно считать, что при доступе на чтение к любому атрибуту сначала будет вызван __getattribute__, а затем уже если этот метод выбросит исключение AttributeError, то тогда будет вызван __getattr__.
Доступ на запись всегда только через метод __setattr__.
При этом при удаление будет вызван __delattr__, но если атрибута на самом деле нет, то возникает сначала обращение к __getattribute__ (и далее по цепочке и к __getattr__, если нужно), а затем уже их результат будет передан как параметр __delattr__.
Например:
1 |
class Test:<br><br> def __init__(self, *args, **kwargs):<br> self.atr_test = 1<br><br> # Метод вызывается для доступа как к существующим так и к не существующим атрибутам<br> # Может не вызыватся для специальных методов<br> def __getattribute__(self, name: str) -> Any:<br> print("__getattribute__", name)<br> # return super().__getattribute__(name) - вызов родительского не переопределенного метода<br> dict_prop = object.__getattribute__(self, '__dict__') # - другой вариант обращения к методу<br><br> # !ОШИБКА программа вылитет из-за переполнения стека<br> # getattr(self, name) - функция вызовет метод __getattribute__ у объекта<br> # self.__dict__[name] - всер равно вызовет метод __getattribute__ для получения __dict__<br><br> if name not in dict_prop:<br> raise AttributeError() # метод должен выбросить исключени AttributeError если атрибута нет<br> return dict_prop[name]<br><br> # Метод вызывается для доступа атрибутам для которых __getattribute__ вызвал исключение AttributeError<br> def __getattr__(self, name: str) -> Any:<br> print("__getattr__", name)<br> return f"no attribute {name}"<br><br> # Метод вызывается при сохранении значения<br> def __setattr__(self, name: str, value: Any) -> None:<br> print("__setattr__", name, value)<br><br> # !ОШИБКА программа вылитет из-за переполнения стека<br> # self.name = value - всер равно вызовет метод __setattr__ для получения __dict__<br> # self.__dict__[name] = value - потребует обращение __getattribute__<br> object.__setattr__(self, name, value)<br> super().__setattr__(name, value)<br><br> # Метод вызывается при удаления значения<br> # особенность в том, что при попытке удаления не существующего метода будут вызыватся<br> # методы __getattribute__ и __getattr__<br> def __delattr__(self, name: str) -> None:<br> print("__delattr__", name)<br> super().__delattr__(name)<br><br><br>if __name__ == "__main__":<br> m = Test()<br> # Обращение к существующему методу (будет вызван только __getattribute__)<br> print(m.atr_test)<br> # Обращение к НЕ существующему методу (будет вызван __getattribute__, а затем __getattr__)<br> print(m.test)<br><br> m.atr_test = 5<br> m.test = 5<br> print(m.test)<br> del m.test<br> try:<br> # Обращение к специальному методу __len__ вызвовов методов не будет<br> m.__delattr__('test')<br> except Exception as e:<br> print('DEL', e)<br><br><br> try:<br> # Обращение к специальному методу __len__ вызвовов методов не будет<br> print(len(m))<br> except Exception as e:<br> print(e) |
Этот демо-пример получит следующие результаты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
__setattr__ atr_test 1 __getattribute__ atr_test 1 __getattribute__ test __getattr__ test no attribute test __setattr__ atr_test 5 __setattr__ test 5 __getattribute__ test 5 __delattr__ test __getattribute__ __delattr__ __getattr__ __delattr__ DEL 'str' object is not callable object of type 'Test' has no len() |
При этом стоит отметить последний оператор len(m), который показывает, что для вызова специальных методов протокол доступа к атрибутам не применяется.
Протокол Дескриптора
Атрибут объекта, доступ к которому подчиняется протоколу дескриптора.
Для реализации дескриптора классу требуется определить один или несколько методов, входящих в протокол дескриптора:
- __get__ – доступ на чтение атрибута
- __set__ – доступ для записи значения атрибута
- __delete__ – удаление атрибута
- __set_name__ – Данный метод вызывается непосредственно после того, как создан класс-владелец. Служит для отслеживание имен атрибутов (раньше только через метаклассы).
По умолчанию при получении значения, установке значения или удалении атрибута мы оперируем словарём объекта (и далее класса и его родителей). Иногда в ходе описанных операций может понадобиться дополнительная логика — для этого и существуют дескрипторы. Важно понимать, что работа дескрипторов обеспечивается методами __getattribute__, __setattr__ и __delattr__. И их переопределение (особенно без обращения к оригинальным методам) приведет к отключению механизма дескрипторов.
Существуют два вида дескрипторов:
Вид | Признак | Пример |
---|---|---|
дескрипторы не-данных | наличие только __get__() |
staticmethod(), classmethod() |
дескрипторы данных | наличие __get__() и других методов протокола |
property() |
Дескрипторы, в которых определён только данный метод, называют дескрипторами не-данных
, в противоположность дескрипторам данных
, для которых определяются и другие методы протокола дескриптора. Если в__dict__ владельца имеется запись с ключём, совпадающим с именем атрибута, указывающего на дескриптор, то в случае дескрипторов не-данных при обращении к атрибуту вернётся значение из __dict__
, а не из дескриптора. В случае дескрипторов данных наоборот — значение из дескриптора.
Пример дескриптора:
1 |
class MyDescriptor:<br /> value = 1<br /><br /> def __set__(self, instance, value):<br /> print('__set__', instance, value)<br /> self.value = value<br /><br /> def __get__(self, instance, owner):<br /> print('__get__', instance, owner)<br /> return 'HELLO FROM DESCRIPTOR' * self.value<br /><br /> def __delete__(self, instance):<br /> print('__delete__', instance, instance)<br /><br /> def __set_name__(self, owner, name):<br /> print('__set_name__', owner, name) |
При этом в методах встречается параметры:
- instance — Экземпляр класса владельца дескриптора, либо ‘None’, если обращаются в контексте класса, а не экземпляра.
- owner — Класс владельца дескриптора.
Чтобы сделать дескриптор данных, доступный только для чтения, потребуется внутри метода поднять исключение AttributeError.
Деструктор __del__
Позволяет определить поведение экземпляра пользовательского типа при готовности интерпретатора уничтожить его object.__del__(self) self — Ссылка на экземпляр.
Вызывается интерпретатором, когда экземпляр назначен к уничтожению. Также известен под именем «деструктор».
Если метод определён в базовом классе, то потомок должен вызывать его явно, чтобы удаление части экземпляра, реализуемой базовым классом произошло без ошибок.
Обратите внимание, что есть возможность (но не рекомендуется) в методе создать новую ссылку на экземпляр, чтобы отсрочить его уничтожение. В следующий раз, когда экземпляр будет назначен к уничтожению, метод будет вызван снова.
Не гарантируется, что метод будет вызван для объектов, всё ещё существующих на момент выхода интерпретатора.
Вызов del x
не приводит напрямую к вызову x.__del__()
, а лишь уменьшает значение счётчика ссылок на единицу. Описываемый метод же будет вызван, только когда счётчик ссылок достигнет нуля.
Вот некоторые обстоятельства, которые могут помешать счётчику дойти до нуля:
- Циклические ссылки (двусвязные списки; древовидные структуры, со ссылками на родителей и детей) — исправляется только разрывом таких ссылок. Циклические ссылки, являющиеся мусором, отлавливаются обнаружителем циклов (он включен по умолчанию), однако могут быть вычищены, только если не заданы
__del__()
на уровне Питона. За более подробным описанием того, как__del__()
влияет на обнаружитель циклов, можно узнать из документации к модулюgc
.
- Ссылка на объект в фрейме стека при возбуждении исключения (трассировка стека, хранимая в
sys.exc_info()[2]
(-py3.0sys.exc_traceback
) продлевает жизнь фрейма) — исправляется путём освобождения ссылки на объект трассировки, когда в нём более нет надобности (-py3.0sys.exc_traceback = None
).
- В интерактивном режиме ссылка на объект в фрейме стека, где возбуждено необработанное исключение (трассировка стека, хранимая в
sys.last_traceback
продлевает жизнь фрейма) — исправляется путёмsys.last_traceback = None
.
Внимание
Ввиду того, что вызов метода зависит от множества обстоятельств, исключения, возбуждаемые в ходе его исполнения игнорируются, при этом в sys.stderr
пишется предупреждение.
Кроме того, когда метод вызывается как реакция на удаление модуля (например, когда исполнение программы завершено), другие глобальные переменные, которые могут быть использованы в методе могут быть уже удалены или находится в процессе удаления (например, при завершение работы механизма импорта). Потому в реализации методов резонно использовать минимум окружения. +py1.5 Питон гарантирует, что глобальные переменные, имена которых начинаются с одиночного почерка, удаляются из модуля прежде остальных. Поэтому если на такие объекты нет ссылок, то они могут являться показателем доступности других объектов модуля на момент исполнения метода.