Разница между __getattr__ против __getattribute__

418

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

Ярина
источник
Возможный дубликат Понимание разницы между __getattr__ и __getattribute__
Триларион
5
@Trilarion, но этот вопрос упоминает ответ отсюда ...
JC Rocamonde

Ответы:

497

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

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

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

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

Нед Бэтчелдер
источник
6
Могу ли я реализовать их обоих в одном классе? Если я могу, что это для реализации обоих?
Олкотт
13
@Alcott: вы можете реализовать их оба, но я не уверен, почему вы это сделаете. __getattribute__будет вызываться для каждого доступа, и __getattr__будет вызываться для времен, которые __getattribute__подняли AttributeError. Почему бы просто не сохранить все это в одном?
Нед Бэтчелдер
10
@NedBatchelder, если вы хотите (условно) переопределить вызовы существующих методов, вы захотите использовать __getattribute__.
Джейс Браунинг
28
«Чтобы избежать бесконечной рекурсии в этом методе, его реализация всегда должна вызывать метод базового класса с тем же именем для доступа к любым необходимым ему атрибутам, например, объекту .__ getattribute __ (self, name).»
kmonsoor
1
@kmonsoor. Это хорошая причина для реализации обоих. objec.__getattribute__вызывает myclass.__getattr__при правильных обстоятельствах.
Безумный физик
138

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

Теперь у моего класса 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)
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)
N Randhawa
источник
1
Я не уверен, что именно будет использовать вариант для переопределения, __getattribute__но, конечно, это не так. Потому что, согласно вашему примеру, все, что вы делаете, это __getattribute__вызывает AttributeErrorисключение, если атрибут отсутствует в __dict__объекте; но вам это на самом деле не нужно, потому что это стандартная реализация, __getattribute__а infact __getattr__- именно то, что вам нужно в качестве резервного механизма.
Рохит
@Rohit currentопределяется по экземплярам Count(см __init__), так что просто повышение , AttributeErrorесли атрибут не существует , не совсем то , что происходит - это сдвинуто __getattr__для всех имен , начиная «пса», в том числе current, но curious, curly...
joelb
16

Это всего лишь пример, основанный на объяснении Неда Батчелдера .

__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__You, вы получите >>>RuntimeError: maximum recursion depth exceeded while calling a Python object

Саймон К Бхатта4я
источник
9
На самом деле, это ужасно. __getattr__()Реализации реального мира принимают только конечный набор допустимых имен атрибутов, вызывая AttributeErrorнедопустимые имена атрибутов, тем самым избегая тонких и трудных для отладки проблем . Этот пример безоговорочно принимает все имена атрибутов как допустимые - странное (и откровенно подверженное ошибкам) ​​неправильное использование __getattr__(). Если вы хотите "полный контроль" над созданием атрибутов, как в этом примере, вы хотите __getattribute__()вместо этого.
Сесил Карри
3
@CecilCurry: Все проблемы, которые вы связали, включают в себя неявное возвращение значения None, а не значение, что не дает этот ответ. Что не так с принятием всех имен атрибутов? Это так же, как defaultdict.
Ник Маттео
2
Проблема в том, что __getattr__он будет вызван перед поиском суперкласса. Это нормально для прямого подкласса object, поскольку единственные методы, которые вас действительно волнуют, это магические методы, которые в любом случае игнорируют экземпляр, но для любой более сложной структуры наследования вы полностью исключаете возможность наследовать что-либо от родительского объекта.
Безумный физик
1
@Simon K Bhatta4ya, ваше последнее печатное заявление - комментарий. Правильно? Это длинная строка и утомительно читать (нужно много пролистывать в правой части). Как насчет размещения строки после раздела кода? Или, если вы хотите поместить это в раздел кода, я думаю, что было бы лучше разбить его на две разные строки.
Г-н Абу Нафи Ибна Захид
14

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

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

Классы старого стиля не делают:

class SomeObject:
    pass

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

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

Сэм Долан
источник
6

Классы нового стиля подклассы «объект» (прямо или косвенно). У них есть __new__метод класса в дополнение к__init__ более рациональному низкоуровневому поведению.

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

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

Мистер Фуз
источник
2

Читая через Beazley & Jones PCB, я наткнулся на явный и практический пример использования __getattr__ который помогает ответить на вопрос «когда» в вопросе OP. Из книги:

«Этот __getattr__()метод является своего рода универсальным средством поиска атрибутов. Это метод, который вызывается, если код пытается получить доступ к атрибуту, который не существует». Мы знаем это из приведенных выше ответов, но в рецепте PCB 8.15 эта функциональность используется для реализации шаблона проектирования делегирования . Если у Объекта A есть атрибут Object B, который реализует много методов, которым Object A хочет делегировать, вместо того, чтобы переопределять все методы Object B в Object A просто для вызова методов Object B, определите __getattr__()метод следующим образом:

def __getattr__(self, name):
    return getattr(self._b, name)

где _b - это имя атрибута объекта A, который является объектом B. Когда метод, определенный в объекте B, вызывается для объекта A, __getattr__метод будет вызван в конце цепочки поиска. Это также сделает код чище, поскольку у вас нет списка методов, определенных только для делегирования другому объекту.

soporific312
источник