Zrozumienie różnicy między __getattr__ a __getattribute__

206

Próbuję zrozumieć różnicę między __getattr__a __getattribute__jednak jestem braku na niego.

Odpowiedź na pytanie przepełnienie stosu Różnica między __getattr__VS__getattribute__ mówi:

__getattribute__jest wywoływany przed spojrzeniem na rzeczywiste atrybuty obiektu, więc może być trudny do prawidłowego wdrożenia. Bardzo łatwo możesz skończyć w nieskończonych rekurencjach.

Nie mam pojęcia, co to znaczy.

Następnie mówi:

Prawie na pewno chcesz __getattr__.

Czemu?

Czytam, że jeśli się __getattribute__nie powiedzie, __getattr__to się nazywa. Dlaczego więc dwie różne metody robią to samo? Jeśli mój kod implementuje nowe klasy stylów, czego powinienem użyć?

Szukam kilku przykładów kodu, aby wyjaśnić to pytanie. Zrobiłem wszystko, co w mojej mocy, ale odpowiedzi, które znalazłem, nie omawiają dokładnie problemu.

Jeśli jest jakaś dokumentacja, jestem gotów ją przeczytać.

użytkownik225312
źródło
2
jeśli to pomaga, z dokumentów „Aby uniknąć nieskończonej rekurencji w tej metodzie, jej implementacja powinna zawsze wywoływać metodę klasy bazowej o tej samej nazwie, aby uzyskać dostęp do wszelkich potrzebnych atrybutów, na przykład obiekt .__ getattribute __ (jaźń, nazwa). „ [ docs.python.org/2/reference/…
kmonsoor

Odpowiedzi:

306

Najpierw podstawy.

W przypadku obiektów musisz poradzić sobie z ich atrybutami. Zwykle tak robimy instance.attribute. Czasami potrzebujemy większej kontroli (gdy nie znamy wcześniej nazwy atrybutu).

Na przykład instance.attributestałby się getattr(instance, attribute_name). Za pomocą tego modelu możemy uzyskać atrybut, podając nazwę atrybutu jako ciąg.

Zastosowanie __getattr__

Możesz także powiedzieć klasie, jak postępować z atrybutami, którymi nie zarządza, i zrobić to za pomocą __getattr__metody.

Python wywoła tę metodę za każdym razem, gdy zażądasz atrybutu, który nie został jeszcze zdefiniowany, więc możesz zdefiniować, co z nim zrobić.

Klasyczny przypadek użycia:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

Ostrzeżenia i wykorzystanie __getattribute__

Jeśli chcesz złapać każdy atrybut, niezależnie od tego, czy istnieje , użyj go __getattribute__. Różnica polega na tym, że __getattr__wywoływane są tylko atrybuty, które tak naprawdę nie istnieją. Jeśli ustawisz atrybut bezpośrednio, odwołanie się do tego atrybutu spowoduje jego odzyskanie bez wywoływania __getattr__.

__getattribute__ nazywa się cały czas.

pyfunc
źródło
„Na przykład stałoby się instance.attribute getattr(instance, attribute_name).”. Nie powinno tak być __getattribute__(instance, attribute_name)?
Md. Abu Nafee Ibna Zahid
1
@ Md.AbuNafeeIbnaZahid getattrto wbudowana funkcja. getattr(foo, 'bar')jest równoważne z foo.bar.
wizzwizz4
warto zauważyć, że __getattr__jest wywoływany tylko wtedy, gdy jest zdefiniowany, ponieważ nie jest dziedziczonyobject
joelb
94

__getattribute__ jest wywoływany za każdym razem, gdy nastąpi dostęp do atrybutu.

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

Spowoduje to nieskończoną rekurencję. Sprawcą jest tutaj linia return self.__dict__[attr]. Udawajmy (jest wystarczająco blisko prawdy), że wszystkie atrybuty są przechowywane self.__dict__i dostępne według ich nazw. Linia

f.a

próbuje uzyskać dostęp do aatrybutu f. To wzywa f.__getattribute__('a'). __getattribute__następnie próbuje załadować self.__dict__. __dict__jest atrybutem, self == fdlatego wywołania w języku Python, f.__getattribute__('__dict__')które ponownie próbują uzyskać dostęp do atrybutu '__dict__”. To jest nieskończona rekurencja.

Jeśli __getattr__zamiast tego został użyty

  1. Nigdy by się nie uruchomił, ponieważ fma aatrybut.
  2. Gdyby zadziałał (powiedzmy, że prosiłeś f.b), nie zostałby wywołany w celu znalezienia, __dict__ponieważ już tam __getattr__jest i jest wywoływany tylko wtedy, gdy wszystkie inne metody znalezienia atrybutu zawiodły .

„Prawidłowym” sposobem na napisanie powyższej klasy __getattribute__jest

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr)wiąże __getattribute__metodę „najbliższej” nadklasy (formalnie następnej klasy w klasie klasy Resolution Resolution Order lub MRO) z bieżącym obiektem, selfa następnie wywołuje ją i pozwala na wykonanie tej pracy.

Wszystkich tych problemów można uniknąć, używając __getattr__Pythona, który robi to normalnie, dopóki nie zostanie znaleziony atrybut. W tym momencie Python przekazuje kontrolę nad twoją __getattr__metodą i pozwala jej coś wymyślić.

Warto również zauważyć, że możesz spotkać się z nieskończoną rekurencją __getattr__.

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

Zostawię to jako ćwiczenie.

aaronasterling
źródło
60

Myślę, że inne odpowiedzi wykonali wspaniałą pracę wyjaśniając różnicę pomiędzy __getattr__a __getattribute__, ale jedna rzecz, która może nie być jasne, dlaczego chcesz używać __getattribute__. Fajną rzeczą __getattribute__jest to, że zasadniczo pozwala na przeciążenie kropki podczas uzyskiwania dostępu do klasy. Pozwala to dostosować sposób dostępu do atrybutów na niskim poziomie. Załóżmy na przykład, że chcę zdefiniować klasę, w której wszystkie metody pobierające tylko argument własny są traktowane jako właściwości:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

I z interaktywnego tłumacza:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

Oczywiście jest to głupi przykład i prawdopodobnie nigdy nie chciałbyś tego zrobić, ale pokazuje on moc, którą można uzyskać z zastąpienia __getattribute__.

Jason Baker
źródło
6

Przeszedłem doskonałe wyjaśnienie innych. Znalazłem jednak prostą odpowiedź z tego bloga Python Magic Methods and__getattr__ . Stamtąd pochodzą wszystkie następujące elementy.

Za pomocą __getattr__metody magicznej możemy przechwycić to nieistniejące wyszukiwanie atrybutów i zrobić coś, aby nie zawiodło:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

Ale jeśli atrybut istnieje, __getattr__nie zostanie wywołany:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute__jest podobny do __getattr__, z tą ważną różnicą, że __getattribute__przechwyci KAŻDE wyszukiwanie atrybutu, nie ma znaczenia, czy atrybut istnieje, czy nie.

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

W tym przykładzie dobiekt ma już wartość atrybutu. Ale kiedy próbujemy uzyskać do niego dostęp, nie otrzymujemy oryginalnej oczekiwanej wartości („Python”); otrzymujemy tylko to, co __getattribute__wróci. Oznacza to, że praktycznie straciliśmy atrybut wartości; stało się „nieosiągalne”.

Mathsyouth
źródło