Я намагаюся зрозуміти, коли використовувати __getattr__або __getattribute__. TheДокументація згадує __getattribute__відноситься до класів нового стилю. Що таке нові класи?
Я намагаюся зрозуміти, коли використовувати __getattr__або __getattribute__. TheДокументація згадує __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'
Тепер мій клас графа має __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, так і gettttribute магічні методи, то __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__для всіх імен, починаючи з "cur", включаючи 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
Читаючи PCB Beazley & Jones, я натрапив на явний і практичний випадок використання __getattr__ який допомагає відповісти на "коли" частину питання ОП. З книги:
"Цей __getattr__()метод схожий на загальну інформацію для пошуку атрибутів. Це метод, який викликається, якщо код намагається отримати доступ до атрибута, який не існує." Ми знаємо це з вищенаведених відповідей, але в рецепті PCB 8.15 ця функціональність використовується для реалізації структури дизайну делегування . Якщо в Object A є атрибут Object B, який реалізує багато методів, які Object A хоче делегувати, а не перевизначати всі методи Object B в Object A просто для виклику методів Object B, визначте __getattr__()метод таким чином:
def __getattr__(self, name):
return getattr(self._b, name)
де _b - це атрибут "Об'єкт А", який є "Об'єктом Б.". Коли метод, визначений на "Об'єкт В", викликається на "Об'єкт А", __getattr__метод буде викликаний в кінці ланцюжка пошуку. Це також зробить код більш чистим, оскільки у вас немає списку методів, визначених лише для делегування іншому об'єкту.