Я пытаюсь понять, когда использовать __getattr__
или __getattribute__
. Документация упоминает __getattribute__
относится к классам нового стиля. Что такое классы нового стиля?
Я пытаюсь понять, когда использовать __getattr__
или __getattribute__
. Документация упоминает __getattribute__
относится к классам нового стиля. Что такое классы нового стиля?
Ответы:
Ключевое различие между __getattr__
и __getattribute__
заключается в том, что __getattr__
он вызывается только в том случае, если атрибут не был найден обычными способами. Это хорошо для реализации запасного варианта для отсутствующих атрибутов и, вероятно, является одним из двух, которые вы хотите.
__getattribute__
вызывается перед просмотром фактических атрибутов объекта, и поэтому может быть сложно реализовать правильно. Вы можете очень легко оказаться в бесконечных рекурсиях.
Классы нового стиля являются производными, классы object
старого стиля - это классы в Python 2.x без явного базового класса. Но различие между классами старого и нового стиля не является важным при выборе между __getattr__
и __getattribute__
.
Вы почти наверняка хотите __getattr__
.
__getattribute__
будет вызываться для каждого доступа, и __getattr__
будет вызываться для времен, которые __getattribute__
подняли AttributeError
. Почему бы просто не сохранить все это в одном?
__getattribute__
.
objec.__getattribute__
вызывает myclass.__getattr__
при правильных обстоятельствах.
Давайте посмотрим на несколько простых примеров обоих __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)
__getattribute__
но, конечно, это не так. Потому что, согласно вашему примеру, все, что вы делаете, это __getattribute__
вызывает AttributeError
исключение, если атрибут отсутствует в __dict__
объекте; но вам это на самом деле не нужно, потому что это стандартная реализация, __getattribute__
а infact __getattr__
- именно то, что вам нужно в качестве резервного механизма.
current
определяется по экземплярам Count
(см __init__
), так что просто повышение , AttributeError
если атрибут не существует , не совсем то , что происходит - это сдвинуто __getattr__
для всех имен , начиная «пса», в том числе current
, но curious
, curly
...
Это всего лишь пример, основанный на объяснении Неда Батчелдера .
__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
__getattr__()
Реализации реального мира принимают только конечный набор допустимых имен атрибутов, вызывая AttributeError
недопустимые имена атрибутов, тем самым избегая тонких и трудных для отладки проблем . Этот пример безоговорочно принимает все имена атрибутов как допустимые - странное (и откровенно подверженное ошибкам) неправильное использование __getattr__()
. Если вы хотите "полный контроль" над созданием атрибутов, как в этом примере, вы хотите __getattribute__()
вместо этого.
defaultdict
.
__getattr__
он будет вызван перед поиском суперкласса. Это нормально для прямого подкласса object
, поскольку единственные методы, которые вас действительно волнуют, это магические методы, которые в любом случае игнорируют экземпляр, но для любой более сложной структуры наследования вы полностью исключаете возможность наследовать что-либо от родительского объекта.
Классы нового стиля наследуются object
от другого класса нового стиля:
class SomeObject(object):
pass
class SubObject(SomeObject):
pass
Классы старого стиля не делают:
class SomeObject:
pass
Это относится только к Python 2 - в Python 3 все вышеперечисленное создаст классы нового стиля.
См. 9. Классы (руководство по Python), NewClassVsClassicClass и В чем разница между классами старого и нового стилей в Python? для деталей.
Классы нового стиля подклассы «объект» (прямо или косвенно). У них есть __new__
метод класса в дополнение к__init__
более рациональному низкоуровневому поведению.
Обычно вы хотите переопределить __getattr__
(если вы переопределите и то и другое), иначе вам будет сложно поддерживать синтаксис «self.foo» в ваших методах.
Дополнительная информация: http://www.devx.com/opensource/Article/31482/0/page/4
Читая через 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__
метод будет вызван в конце цепочки поиска. Это также сделает код чище, поскольку у вас нет списка методов, определенных только для делегирования другому объекту.