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

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

Ответ 1

Ключевое различие между __getattr__ и __getattribute__ заключается в том, что __getattr__ вызывается только в том случае, если атрибут не был найден обычными способами. Это полезно для реализации резервной копии отсутствующих атрибутов и, вероятно, является одним из двух, которые вы хотите.

__getattribute__ вызывается, прежде чем смотреть на фактические атрибуты объекта, и поэтому может быть сложно реализовать правильно. Вы можете легко завершить бесконечные рекурсии.

Классы нового стиля получают из object, классы старого стиля - это те, что находятся в Python 2.x без явного базового класса. Но различие между классами старого и нового стилей не является важным при выборе между __getattr__ и __getattribute__.

Вы почти наверняка хотите __getattr__.

Ответ 2

Давайте рассмотрим некоторые простые примеры магических методов __getattr__ и __getattribute__.

__getattr__

Python будет вызывать метод __getattr__ всякий раз, когда вы запрашиваете атрибут, который еще не был определен. В следующем примере класс Count не имеет метода __getattr__. Теперь, когда я пытаюсь получить доступ к атрибутам 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)
print(obj1.mymax)
print(obj1.mycurrent)  --> AttributeError: 'Count' object has no attribute 'mycurrent'

Теперь мой класс Граф имеет метод __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)
print(obj1.mymax)
print(obj1.mycurrent1)

__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) 
        # or you can use ---return super().__getattribute__(item)

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

Важно. Чтобы избежать бесконечной рекурсии в методе __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)
        # or you can use ---return super().__getattribute__(item)
        # note this class subclass object

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

Ответ 3

Классы нового стиля наследуют от object или из другого нового класса стиля:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

В классах старого стиля нет:

class SomeObject:
    pass

Это относится только к Python 2 - в Python 3 все вышеперечисленное создаст классы нового стиля.

См. 9. Классы (учебник по Python), NewClassVsClassicClass и В чем разница между старыми стиль и новые классы стиля в Python? для деталей.

Ответ 4

Это просто пример, основанный на объяснении @Ned Batchelder.

__getattr__ пример:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

И если такой же пример используется с __getattribute__, вы получите → > RuntimeError: maximum recursion depth exceeded while calling a Python object

Ответ 5

Классы нового стиля - это подклассы "объект" (прямо или косвенно). В дополнение к __init__ они имеют метод класса __new__ и имеют несколько более рациональное поведение на низком уровне.

Обычно вам нужно переопределить __getattr__ (если вы переопределяете либо), в противном случае вам будет сложно поддерживать синтаксис "self.foo" в ваших методах.

Дополнительная информация: http://www.devx.com/opensource/Article/31482/0/page/4