Я намагаюся зрозуміти, коли використовувати __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__
метод буде викликаний в кінці ланцюжка пошуку. Це також зробить код більш чистим, оскільки у вас немає списку методів, визначених лише для делегування іншому об'єкту.