Різниця між __getattr__ проти __getattribute__


417

Я намагаюся зрозуміти, коли використовувати __getattr__або __getattribute__. TheДокументація згадує __getattribute__відноситься до класів нового стилю. Що таке нові класи?



5
@Trilarion, але це питання згадує відповідь звідси ...
JC Rocamonde,

Відповіді:


497

Ключова різниця між __getattr__і __getattribute__полягає в тому, що __getattr__викликається лише в тому випадку, якщо атрибут не знайдено звичайними способами. Це добре для реалізації резервного резерву для відсутніх атрибутів і, ймовірно, один із двох, які ви хочете.

__getattribute__викликається перед переглядом фактичних атрибутів на об'єкті, тому може бути складним у тому, щоб правильно реалізувати. Ви можете дуже легко закінчитись нескінченними рекурсіями.

Класи нового стилю походять від класів objectстарого стилю - це класи в Python 2.x без явного базового класу. Але відмінність класів у старому стилі та новому стилі не є важливим при виборі між __getattr__та__getattribute__ .

Ви майже напевно хочете __getattr__.


6
Чи можу я реалізувати їх обох в одному класі? Якщо я можу, що це за реалізація обох?
Алькотт

13
@Alcott: ти можеш реалізувати їх обоє, але я не впевнений, чому б це зробити. __getattribute__буде викликано для кожного доступу, і __getattr__буде викликано час, який __getattribute__підняв AttributeError. Чому б просто не тримати це все в одному?
Нед Батчелдер

10
@NedBatchelder, якщо ви хочете (умовно) замінити виклики на існуючі методи, ви хочете використовувати __getattribute__.
Джейс Браунінг

28
"Щоб уникнути нескінченної рекурсії в цьому методі, його реалізація завжди повинна викликати метод базового класу з тим самим іменем для доступу до будь-яких необхідних йому атрибутів, наприклад, об'єкта .__ getattribute __ (self, name)."
kmonsoor

1
@kmonsoor. Це хороший привід реалізувати і те, і інше. objec.__getattribute__викликає myclass.__getattr__за правильних обставин.
Божевільний фізик

138

Розглянемо кілька простих прикладів __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)

1
Я не впевнений, який саме варіант буде використовуватись для переосмислення, __getattribute__але, безумовно, це не так. Тому що у вашому прикладі все, що ви робите, __getattribute__- це AttributeErrorвиняток, якщо атрибут відсутній в __dict__об'єкті; але вам це дійсно не потрібно, оскільки це реалізація за замовчуванням, __getattribute__а infact __getattr__- саме те, що вам потрібно як механізм резервного копіювання.
Рохіт

@Rohit currentвизначається на примірниках Count(див. __init__), Тому просто підвищення, AttributeErrorякщо атрибут не є, не зовсім те, що відбувається - він визначає __getattr__для всіх імен, починаючи з "cur", включаючи current, але також curious, curly...
joelb

16

Це лише приклад, заснований на поясненні Неда Батчелдера .

__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


9
Власне, це жахливо. __getattr__()Реалізації в реальному світі приймають лише обмежений набір дійсних імен атрибутів, піднімаючи AttributeErrorнедійсні імена атрибутів, тим самим уникаючи тонких і важких для налагодження проблем . Цей приклад безумовно приймає всі імена атрибутів як дійсні - химерне (і відверто схильне до помилок) використання __getattr__(). Якщо ви хочете "повністю контролювати" створення атрибутів, як у цьому прикладі, ви хочете __getattribute__()замість цього.
Сесіль Карі

3
@CecilCurry: Усі проблеми, з якими ви пов’язані, пов'язані з неявним поверненням None, а не значення, на що ця відповідь не робить. Що не так у прийнятті всіх імен атрибутів? Це те саме, що а defaultdict.
Нік Маттео

2
Проблема полягає в тому, що __getattr__буде викликано перед пошуком надкласового класу. Це добре для прямого підкласу object, оскільки єдиними методами, які вам дійсно цікаві, є магічні методи, які так чи інакше ігнорують екземпляр, але для будь-якої більш складної структури успадкування ви повністю видаляєте можливість успадкувати що-небудь з батьківського.
Божевільний фізик

1
@Simon K Bhatta4ya, остання друкована заява - це коментар. Правильно? Це довгий рядок і нудно читати (треба багато прокручувати праворуч). А як ставити рядок після розділу коду? Або якщо ви хочете помістити його в розділ коду, я думаю, було б краще розбити його на два різні рядки.
Пані Абу Нафей Ібна Захід

14

Класи нового стилю успадковують objectабо від іншого нового класу стилів:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

Заняття в старому стилі не:

class SomeObject:
    pass

Це стосується лише Python 2 - у Python 3 все вищезазначене створить класи нового стилю.

Див. 9. Класи (підручник Python), NewClassVsClassicClass та в чому різниця між старим стилем та класами нового стилю в Python? для деталей.


6

Класи нового стилю - це підкласи «об’єкт» (прямо чи опосередковано). Вони мають __new__класний метод на додаток до __init__і мають трохи більш раціональну поведінку на низькому рівні.

Зазвичай, вам захочеться перевизначити __getattr__(якщо ви переосмислите будь-яке), інакше у вас буде важко підтримувати синтаксис "self.foo" в межах своїх методів.

Додаткова інформація: http://www.devx.com/opensource/Article/31482/0/page/4


2

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.