В разделе рассматриваются специальные (магические) методы, которые могут быть определены для настройки поведения при доступе к атрибутам экземпляров класса (использование, присвоение или удаление x.name
).
object.__getattr__()
вызывается при обращении, к несуществующему атрибуту,object.__getattribute__()
вызывается всегда, при обращении к любому атрибуту,object.__setattr__()
вызывается при попытке присвоения значения любому атрибуту,object.__delattr__()
вызывается при попытке удаления любого атрибута,object.__dir__()
вызывается функцией dir()
.object.__getattr__(self, name)
:Метод object.__getattr__()
вызывается, когда доступ к атрибуту по умолчанию завершается с ошибкой AttributeError
(либо __getattribute__()
вызывает исключение AttributeError
, потому что name
не является атрибутом экземпляра, либо атрибутом в дереве классов для self
; либо метод __get__()
свойства name
вызывает AttributeError
). Этот метод должен либо вернуть (вычисленное) значение атрибута, либо вызвать исключение AttributeError
.
Обратите внимание, что если атрибут найден с помощью обычного механизма, то метод __getattr__()
не вызывается. (Это преднамеренная асимметрия между __getattr__()
и __setattr__()
.) Это делается как из соображений эффективности, так и потому, что в противном случае __getattr__()
не сможет получить доступ к другим атрибутам экземпляра. Обратите внимание, что, по крайней мере, для переменных экземпляра можно подделать полный контроль, не вставляя никаких значений в словарь атрибутов экземпляра, а вместо этого вставляя их в другой объект.
Смотрите метод __getattribute__()
, чтобы получить полный контроль над доступом к атрибутам.
class Order:
bascket = 0
def __init__(self, price, num):
self.price = price
self.num = num
def __getattr__(self, attrname):
# магический метод `__getattr__` будет вызываться при
# попытке доступа к любому несуществующему атрибуту,
# имя атрибута сохраняется в переменной `attrname`
if attrname == "total":
# условие выполнится при доступе к атрибуту `total`,
# т.к. он фактически не определен и является вычисляемым
return self.price * self.num
elif attrname == "bascket":
# условие выполнятся не будет, т.к.
# атрибут `bascket` существует
return self.bascket + 100
else:
# условие выполнится при доступе к
# любому, несуществующему атрибуту
print(f'Атрибута {attrname} не существует!')
raise AttributeError
>>> order = Order(10, 15)
# вычисляемый атрибут
>>> order.total
# 150
# атрибут `bascket` задан, для него
# магический метод `__getattr__` не вызывается
>>> order.bascket
# 0
# атрибут `basket_total` не существует
>>> order.basket_total
# Атрибута basket_total не существует!
# Traceback (most recent call last):
# ...
# AttributeError
# присвоим значение `на лету`
>>> order.basket_total = 0
# теперь все нормально
>>> order.basket_total
# 0
Из примера отчетливо видно, что специальный (магический) метод __getattr__()
вызывается автоматически только у тех атрибутов, которые не определены (когда доступ к атрибуту по умолчанию завершается с ошибкой AttributeError
).
object.__getattribute__(self, name)
:Метод object.__getattribute__()
вызывается безоговорочно (всегда) для реализации доступа к атрибутам для экземпляров класса. Если класс также определяет специальный метод __getattr__()
, то последний не будет вызываться, если метод __getattribute__()
либо не вызовет его явно, либо не вызовет исключение AttributeError
.
Этот метод должен возвращать (вычисленное) значение атрибута или вызывать исключение AttributeError
. Чтобы избежать бесконечной рекурсии в этом методе, его реализация всегда должна вызывать метод базового класса с тем же name
для доступа к любым нужным атрибутам, например, object.__getattribute__(self, name)
.
Примечание. Специальный метод object.__getattribute__()
может быть пропущен при поиске специальных методов в результате неявного вызова через синтаксис языка или встроенных функций. Смотрите материал "Поиск специального метода класса в Python".
class Order:
basket = 0
def __init__(self, price, num):
self.price = price
self.num = num
def __getattribute__(self, attrname):
# магический метод `__getattribute__` будет вызываться
# ВСЕГДА, при попытке доступа к любому атрибуту,
# имя атрибута сохраняется в переменной `attrname`
if attrname == "total":
# `total` - вычисляемый атрибут,
return self.price * self.num
if attrname == "price":
# `price` - существующий атрибут, что бы не было
# рекурсии обратимся к нему как сказано в документации
return object.__getattribute__(self, attrname)
elif attrname == "basket":
# `basket` существующий атрибут,
# попробуем к нему обратиться через `self`
return self.basket
elif attrname == "total_basket":
# `basket` атрибут не существует, при этом
# сразу возвращается значение
return 0
else:
# определяем как вызывать остальные существующие
# атрибуты - в частности `num`
return object.__getattribute__(self, attrname)
>>> order = Order(10, 15)
>>> order.total
# 150
>>> order.price
# 10
# назначим другую стоимость
>>> order.price = 5
# вычисляемый атрибут работает
>>> order.total
# 75
# к этому атрибуту обратились через `self`
>>> order.basket # в итоге рекурсия (как описано в документации)
# Traceback (most recent call last):
# ...
# [Previous line repeated 995 more times]
# File "<stdin>", line 13, in __getattribute__
# RecursionError: maximum recursion depth exceeded in comparison
# в определении метода `__getattribute__`
# к атрибуту `total_basket` не обращаемся
# а сразу возвращаем значение
>>> order.total_basket
# 0
# попробуем что нибудь присвоить
>>> order.total_basket = 100
# значение осталось то же, как и ожидалось
>>> order.total_basket
# 0
# атрибут `other` - не определен
>>> order.other
# Traceback (most recent call last):
# ...
# AttributeError: 'Order' object has no attribute 'other'
# присвоим значение `на лету`
>>> order.other = 'ok'
# теперь работает
>>> order.other
'ok'
Из примера отчетливо видно, что специальный (магический) метод __getattribute__()
вызывается автоматически для всех атрибутов, существуют они или нет. Но, для предотвращения рекурсии, к существующим атрибутам необходимо обращаться явно object.__getattribute__(self, attrname)
.
object.__setattr__(self, name, value)
:Метод object.__setattr__()
вызывается при попытке присвоения значения атрибуту. Метод вызывается вместо обычного механизма (т.е. сохранения значения в словаре экземпляра). Аргумент name
- это имя атрибута, value
- значение, которое будет ему присвоено.
Если специальный метод __setattr__()
назначает атрибут экземпляра класса, то он должен вызвать метод базового класса с тем же именем name
, например, object.__setattr__(self, name, value)
.
class Order:
basket = 0 # кол-во товара в корзине
def __init__(self, price, num):
self.price = price
self.num = num
def __setattr__(self, attrname, value):
if attrname == 'basket':
# увеличиваем количество товаров в корзине на
# значение, которое передаем атрибуту `basket`
self.__dict__[attrname] = self.basket + value
else:
# для остальных атрибутов, просто
# присваиваем новое значение
self.__dict__[attrname] = value
def __getattr__(self, attrname):
# магический метод `__getattr__` вызываться всегда
# если атрибут фактически не определен и не создан `на лету`
if attrname == "total":
# `total` - вычисляемый атрибут (общая стоимость товара)
return self.price * self.num
elif attrname == "total_basket":
# `basket` - вычисляемый атрибут (стоимость товара в корзине)
return self.price * self.basket
else:
raise AttributeError
>>> order = Order(10, 15)
>>> order.total
# 150
# смотрим что в корзине
>>> order.basket
# 0
# положим 1 товар в корзину
>>> order.basket = 1
# смотрим
>>> order.basket
# 1
# положим еще 2 товара в корзину
>>> order.basket = 2
# смотрим
>>> order.basket
# 3
# стоимость товаров в корзине
>>> order.total_basket
# 30
object.__delattr__(self, name)
:Метод object.__delattr__()
работает так же как __setattr__()
, но только для удаления атрибута вместо присвоения. Метод должен быть реализован, только если имеет смысл операции удаления del obj.name
для этого атрибута объекта.
object.__dir__(self)
:Метод object.__dir__()
вызывается, когда для объекта вызывается функция dir()
. Должна быть возвращена последовательность. Функция dir()
преобразует возвращенную последовательность в список и сортирует его.