Разница между __getattr__ и __getattribute__
-
17-09-2020 - |
Вопрос
Я пытаюсь понять, когда использовать __getattr__
или __getattribute__
.В Документация упоминания __getattribute__
применяется к классам нового стиля.Что такое классы в новом стиле?
Решение
Ключевое различие между __getattr__
и __getattribute__
это что __getattr__
вызывается только в том случае, если атрибут не был найден обычными способами.Это хорошо для реализации резервного варианта для устранения отсутствующих атрибутов, и, вероятно, это один из двух, которые вам нужны.
__getattribute__
вызывается перед просмотром фактических атрибутов объекта, и поэтому может быть сложно правильно реализовать.Вы можете очень легко оказаться в бесконечных рекурсиях.
Классы нового стиля являются производными от object
, классы старого стиля - это классы в Python 2.x без явного базового класса.Но различие между классами старого и нового стилей не является важным при выборе между __getattr__
и __getattribute__
.
Вы почти наверняка хотите __getattr__
.
Другие советы
Давайте рассмотрим несколько простых примеров того и другого __getattr__
и __getattribute__
магические методы.
__getattr__
Python вызовет __getattr__
метод всякий раз, когда вы запрашиваете атрибут, который еще не был определен.В следующем примере мой класс Подсчитывать не имеет никакого __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)
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__
способ?Одна из веских причин заключается в том, что вы можете запретить доступ к атрибутам и сделать их более безопасными, как показано в следующем примере.
Всякий раз, когда кто-то пытается получить доступ к моим атрибутам, которые начинаются с substring 'cur' питон поднимает 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__
повышает
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)
Это всего лишь пример, основанный на Объяснение Неда Батчелдера.
__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
Классы нового стиля наследуются от object
, или из другого нового класса стилей:
class SomeObject(object):
pass
class SubObject(SomeObject):
pass
Классы старого стиля не:
class SomeObject:
pass
Это относится только к Python 2 - в Python 3 все вышеперечисленное создаст классы нового стиля.
Видишь 9.Классы (Учебное пособие по Python), NewClassVсКлассичЕский класс и В чем разница между классами старого стиля и нового стиля в Python? для получения подробной информации.
Классы нового стиля - это классы, которые относятся к подклассу "object" (прямо или косвенно).У них есть __new__
метод класса в дополнение к __init__
и иметь несколько более рациональное низкоуровневое поведение.
Обычно вы захотите переопределить __getattr__
(если вы переопределяете либо то, либо другое), в противном случае вам будет трудно поддерживать синтаксис "self.foo" в ваших методах.
Дополнительная информация: http://www.devx.com/opensource/Article/31482/0/page/4
Изучая печатную плату Beazley & Jones, я наткнулся на четкий и практичный пример использования для __getattr__
это помогает ответить на часть вопроса OP "когда".Из книги:
"Тот __getattr__()
метод - это что-то вроде универсального средства для поиска атрибутов.Это метод, который вызывается, если код пытается получить доступ к несуществующему атрибуту ". Мы знаем это из приведенных выше ответов, но в рецепте PCB 8.15 эта функциональность используется для реализации шаблон проектирования делегирования.Если объект A имеет атрибут Object B, который реализует множество методов, которым Объект A хочет делегировать, вместо переопределения всех методов объекта B в объекте A только для вызова методов объекта B, определите __getattr__()
способ заключается в следующем:
def __getattr__(self, name):
return getattr(self._b, name)
где _b - имя атрибута объекта A, который является объектом B.Когда метод, определенный для объекта B, вызывается для объекта A, __getattr__
метод будет вызван в конце цепочки поиска.Это также сделало бы код более чистым, поскольку у вас нет списка методов, определенных только для делегирования другому объекту.
- получить атрибут:Используется для извлечения атрибута из экземпляра.Он фиксирует каждую попытку доступа к атрибуту экземпляра с помощью точечной нотации или встроенной функции getattr().
- getattr ( получить ):Выполняется как последний ресурс, когда атрибут не найден в объекте.Вы можете выбрать возврат значения по умолчанию или повышение значения AttributeError.
Возвращаясь к __гетатрибут__ функция;если реализация по умолчанию не была переопределена;при выполнении метода выполняются следующие проверки:
- Проверьте, существует ли дескриптор с таким же именем (имя атрибута), определенный в любом классе в цепочке MRO (разрешение объекта метода).
- Затем просматривает пространство имен экземпляра
- Затем просматривает пространство имен класса
- Затем в пространство имен каждой базы и так далее.
- Наконец, если не найден, реализация по умолчанию вызывает запасной вариант getattr ( получить )() метод экземпляра, и он вызывает исключение AttributeError в качестве реализации по умолчанию.
Это фактический реализация объекта.__метод getattribute__:
"..c: функция::PyObject* PyObject_GenericGetAttr(PyObject *o, PyObject *имя) Универсальная функция получения атрибута, предназначенная для помещения в тип слот tp_getattro объекта.Он ищет дескриптор в словаре классов в MRO объекта, а также атрибут в объекте :attr:~object .диктовать (если присутствует).Как описано в: ссылка: дескрипторы, дескрипторы данных отдают предпочтение атрибутам экземпляра, в то время как не-данные дескрипторы этого не делают.В противном случае возникает ошибка :exc:AttributeError ."
Я нахожу, что никто не упоминает об этой разнице:
__getattribute__
имеет реализацию по умолчанию, но __getattr__
не делает этого.
class A:
pass
a = A()
a.__getattr__ # error
a.__getattribute__ # return a method-wrapper
Это имеет ясный смысл:с тех пор как __getattribute__
имеет реализацию по умолчанию, в то время как __getattr__
нет, очевидно, что python поощряет пользователей внедрять __getattr__
.