Я пытаюсь понять, когда использовать __getattr__
или __getattribute__
. документация упоминает __getattribute__
применительно к классам нового стиля. Что такое классы нового стиля?
Разница между __getattr__ vs __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