Разница между __getattr__ и __getattribute__

1,00
р.
Я пытаюсь понять, когда использовать __getattr__, а когда __getattribute__. В документации упоминается, что __getattribute__ применяется к классам нового стиля. Что такое классы нового стиля?
Перевод вопрос Difference between __getattr__ vs __getattribute__ от участника Yarin

Ответ
Давайте посмотрим на несколько простых примеров использования __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