В разделе рассматриваются специальные (магические) методы, которые могут быть определены для настройки поведения при доступе к атрибутам экземпляров класса (использование, присвоение или удаление 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__()
может быть пропущен при поиске специальных методов в результате неявного вызова через синтаксис языка или встроенных функций.
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()
преобразует возвращенную последовательность в список и сортирует его.