Разница между __getattr__ и __getattribute__
Я пытаюсь понять, когда использовать __getattr__, а когда __getattribute__. В документации упоминается, что __getattribute__ применяется к классам нового стиля. Что такое классы нового стиля?
Перевод вопрос Difference between __getattr__ vs __getattribute__ от участника Yarin
Ответы (2 шт):
Давайте посмотрим на несколько простых примеров использования __getattr__ и __getattribute__.
__getattr__
Python будет вызывать метод __getattr__ всякий раз, когда вы запросите атрибут, который еще не был определен. В следующем примере мой класс Count не имеет метода __getattr__. Теперь в main, когда я пытаюсь получить доступ к атрибутам obj1.mymin и obj1.mymax все работает нормально. Но когда я пытаюсь получить доступ к атрибуту obj1.mycurrent - Python выдает мне
AttributeError: 'Count' object has no attribute 'mycurrent'
class Count():
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
obj1 = Count(1,10)
print(obj1.mymin) # 1
print(obj1.mymax) # 10
print(obj1.mycurrent) # AttributeError: 'Count' object has no attribute 'mycurrent'
Теперь у моего класса Count есть метод __getattr__. Теперь, когда я пытаюсь получить доступ к obj1.mycurrent python возвращает мне все, что я реализовал в своем __getattr__. В моем примере каждый раз, когда я пытаюсь вызвать атрибут, которого не существует, python создает этот атрибут и устанавливает ему целочисленное значение 0.
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
def __getattr__(self, item):
self.__dict__[item]=0
return 0
obj1 = Count(1,10)
print(obj1.mymin) # 1
print(obj1.mymax) # 10
print(obj1.mycurrent1) # 0
__getattribute__
Теперь давайте посмотрим на метод __getattribute__. Если в вашем классе есть метод __getattribute__, python вызывает этот метод для каждого атрибута независимо от того, существует он или нет. Так зачем же нам нужен метод __getattribute__? Одна из веских причин заключается в том, что вы можете запретить доступ к атрибутам и сделать их более безопасными, как показано в следующем примере.
Всякий раз, когда кто-то пытается получить доступ к атрибутам, начинающимся с подстроки 'cur' python вызывает исключение AttributeError. В противном случае он возвращает этот атрибут.
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# либо можете использовать
# return super().__getattribute__(item)
obj1 = Count(1,10)
print(obj1.mymin) # 1
print(obj1.mymax) # 10
print(obj1.current) # AttributeError
Важно: чтобы избежать бесконечной рекурсии в __getattribute__, его реализация всегда должна вызывать метод базового класса с тем же именем для доступа к любым необходимым ему атрибутам. Например:
object.__getattribute__(self, name)
или
super().__getattribute__(item) # и нет self.__dict__[item]
Если ваш класс содержит магические методы __getattr__ и __getattribute__, то сначала вызывается __getattribute__. Но если __getattribute__ вызывает исключение AttributeError, то исключение будет проигнорировано и будет вызван метод __getattr__.
class Count(object):
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattr__(self, item):
self.__dict__[item]=0
return 0
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# или вы можете использовать return super().__getattribute__(item)
obj1 = Count(1,10)
print(obj1.mymin) # 1
print(obj1.mymax) # 10
print(obj1.current) # 0
Перевод ответа от участника N Randhawa
Метод __getattr__ определяет поведение, когда наш атрибут, который мы пытаемся получить, не найден.
А __getattribute__ вызывается в любом случае, когда мы обращаемся к какому-либо атрибуту объекта.
class Researcher:
def __init__(self, name):
self.name = name
"""
метод __getattr__ определяет поведение,
когда наш атрибут, который мы пытаемся получить, не найден
"""
def __getattr__(self, name):
return 'Nothing found :('
"""
метод __getattribute__ вызывается в любом случае, когда мы
обращаемся к какому-либо атрибуту объекта
"""
def __getattribute__(self, name):
if name == 'attr':
return 'nope attr'
else:
return object.__getattribute__(self, name)
obj = Researcher("test")
print(obj.attr) --> nope attr
print(obj.what) --> Nothing found :(
print(obj.name) --> test