Python — динамически типизированный язык программирования, что означает, что мы можем изменять атрибуты и методы объектов в реальном времени. Однако для контроля за доступом к атрибутам и их модификацией, Python предлагает некоторые встроенные методы, такие как __getattr__()
и __getattribute__()
.
Метод __getattr__(self, name)
вызывается, когда попытка чтения атрибута name
не удается, т.е. когда атрибута нет в экземпляре и классе. В противном случае, этот метод не вызывается. Этот метод возвращает значение для несуществующего атрибута, если он был определен.
class Test:
def __init__(self, a, b):
self.a = a
self.b = b
def __getattr__(self, attr):
return f"{attr} не существует"
obj = Test(5, 10)
print(obj.a) # Выведет: 5
print(obj.c) # Выведет: c не существует
Метод __getattribute__(self, name)
вызывается при попытке получить любой атрибут объекта. Он перехватывает любые попытки обращения к атрибутам, независимо от того, существуют они или нет.
class Test:
def __init__(self, a, b):
self.a = a
self.b = b
def __getattribute__(self, attr):
if attr == "a":
return 0
else:
return object.__getattribute__(self, attr)
obj = Test(5, 10)
print(obj.a) # Выведет: 0
print(obj.b) # Выведет: 10
Обратите внимание, что object.__getattribute__(self, attr)
используется для предотвращения зацикливания.
Главное различие между __getattr__()
и __getattribute__()
состоит в том, что __getattribute__()
вызывается всякий раз, когда происходит обращение к атрибуту, в то время как __getattr__()
вызывается только тогда, когда атрибут не найден.
Следовательно, __getattribute__()
может быть использован для перехвата всех попыток доступа к атрибутам, включая те, которые на самом деле существуют. Это может быть полезно в некоторых случаях, например, когда вам нужно вести лог обращений к атрибутам. Однако __getattribute__()
следует использовать с осторожностью, поскольку он может легко привести к зацикливанию.
С другой стороны, __getattr__()
может быть использован для обработки обращений к несуществующим атрибутам, что может быть полезно для создания объектов-прокси или объектов с динамическими атрибутами.
Благодаря этим методам можно реализовывать интересные и сложные структуры в Python. Для примера рассмотрим класс, который динамически генерирует свойства из словаря:
class DynamicAttr:
def __init__(self, attr_map):
self._attr_map = attr_map
def __getattr__(self, name):
if name in self._attr_map:
return self._attr_map[name]
else:
raise AttributeError(f"No such attribute: {name}")
obj = DynamicAttr({'a': 1, 'b': 2})
print(obj.a) # Выведет: 1
print(obj.b) # Выведет: 2
Помимо __getattr__()
и __getattribute__()
существуют ещё и методы __setattr__()
и __delattr__()
, которые соответственно позволяют контролировать изменение и удаление атрибутов. Они также очень полезны и часто используются вместе с __getattr__()
и __getattribute__()
.
Как было упомянуто ранее, __getattribute__()
перехватывает любые попытки обращения к атрибутам, и это может легко привести к бесконечным рекурсивным вызовам. Например, предположим, что вы пытаетесь получить доступ к атрибуту внутри метода __getattribute__()
:
class RecursiveAttr:
def __init__(self, a):
self.a = a
def __getattribute__(self, name):
return self.a # Это вызовет бесконечную рекурсию!
Python попытается вызвать __getattribute__()
для обращения к атрибуту a, что приведет к еще одному вызову __getattribute__()
и так далее. Вот почему в примере с __getattribute__()
было использовано object.__getattribute__(self, attr)
, чтобы обеспечить нормальное поведение.
В общем, оба метода являются мощными инструментами для создания сложных классов и объектов в Python, но они требуют аккуратного использования.
Методы __getattr__()
и __getattribute__()
дают вам возможность контролировать доступ к атрибутам ваших объектов в Python. Они могут быть особенно полезны для реализации паттернов проектирования, таких как прокси, или при работе с объектами, у которых атрибуты меняются в реальном времени. Тем не менее, с этими методами следует обращаться осторожно, чтобы избежать нежелательных побочных эффектов, таких как зацикливание.
Содержание: