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

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

Перевод вопрос Difference between __getattr__ vs __getattribute__ от участника Yarin


Ответы (2 шт):

Автор решения: Danis

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

→ Ссылка
Автор решения: lirik

Метод __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
→ Ссылка