Różnica między __getattr__ a __getattribute__

Odpowiedzi:

497

Kluczowa różnica między __getattr__i __getattribute__polega na tym, że __getattr__wywoływana jest tylko wtedy, gdy atrybut nie został znaleziony w zwykły sposób. Jest dobry do implementacji rezerwowania brakujących atrybutów i prawdopodobnie jest jednym z dwóch, których potrzebujesz.

__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.

Klasy w nowym stylu pochodzą, klasy w objectstarym stylu są w Pythonie 2.x bez wyraźnej klasy bazowej. Ale rozróżnienie między klasami w starym i nowym stylu nie jest ważne przy wyborze między __getattr__i __getattribute__.

Prawie na pewno chcesz __getattr__.

Ned Batchelder
źródło
6
Czy mogę zaimplementować oba w tej samej klasie? Jeśli mogę, co to jest za wdrożenie obu?
Alcott,
13
@Alcott: możesz je wdrożyć, ale nie jestem pewien, dlaczego to zrobiłeś. __getattribute__będą wezwani do każdego dostępu i __getattr__będą wezwani do czasów, które __getattribute__wzbudziły AttributeError. Dlaczego nie zatrzymać tego wszystkiego w jednym?
Ned Batchelder,
10
@NedBatchelder, jeśli chcesz (warunkowo) zastąpić wywołania istniejących metod, powinieneś użyć __getattribute__.
Jace Browning,
28
„Aby uniknąć nieskończonej rekurencji w tej metodzie, jej implementacja powinna zawsze wywoływać metodę klasy podstawowej o tej samej nazwie, aby uzyskać dostęp do wszelkich potrzebnych atrybutów, na przykład, obiektu .__ getattribute __ (self, name).”
kmonsoor,
1
@kmonsoor. To dobry powód do wdrożenia obu. objec.__getattribute__wywołuje myclass.__getattr__w odpowiednich okolicznościach.
Szalony fizyk
138

Zobaczmy kilka prostych przykładów obu __getattr__i __getattribute__magicznych metod.

__getattr__

Python wywoła __getattr__metodę za każdym razem, gdy zażądasz atrybutu, który nie został jeszcze zdefiniowany. W poniższym przykładzie moja klasa Count nie ma __getattr__metody. Teraz w głównym przy próbie dostępu zarówno obj1.mymini obj1.mymaxatrybuty wszystko działa poprawnie. Ale kiedy próbuję uzyskać dostęp do obj1.mycurrentatrybutu - Python daje miAttributeError: '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'

Teraz moja klasa Count ma __getattr__metodę. Teraz, gdy próbuję uzyskać dostęp do obj1.mycurrentatrybutu - python zwraca mi wszystko, co zaimplementowałem w mojej __getattr__metodzie. W moim przykładzie, ilekroć próbuję wywołać atrybut, który nie istnieje, Python tworzy ten atrybut i ustawia go na wartość całkowitą 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__

Teraz zobaczmy __getattribute__metodę. Jeśli masz __getattribute__metodę w swojej klasie, Python wywołuje tę metodę dla każdego atrybutu, niezależnie od tego, czy istnieje. Dlaczego więc potrzebujemy __getattribute__metody? Jednym z dobrych powodów jest to, że możesz uniemożliwić dostęp do atrybutów i zwiększyć ich bezpieczeństwo, jak pokazano w poniższym przykładzie.

Ilekroć ktoś próbuje uzyskać dostęp do moich atrybutów, które zaczynają się od podłańcucha „cur”, python podnosi AttributeErrorwyjątek. W przeciwnym razie zwraca ten atrybut.

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)

Ważne: Aby uniknąć nieskończonej rekurencji w __getattribute__metodzie, jej implementacja powinna zawsze wywoływać metodę klasy podstawowej o tej samej nazwie, aby uzyskać dostęp do wszystkich potrzebnych atrybutów. Na przykład: object.__getattribute__(self, name)lub super().__getattribute__(item)nieself.__dict__[item]

WAŻNY

Jeśli twoja klasa zawiera zarówno magiczne metody getattr, jak i getattribute , wówczas __getattribute__jest wywoływana jako pierwsza. Ale jeśli __getattribute__podniesie AttributeErrorwyjątek, wyjątek zostanie zignorowany i __getattr__zostanie wywołana metoda. Zobacz następujący przykład:

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)
N Randhawa
źródło
1
Nie jestem pewien, jaki dokładnie byłby przypadek użycia do zastąpienia, __getattribute__ale na pewno tak nie jest. Ponieważ w twoim przykładzie wszystko, co robisz, __getattribute__to zgłaszanie AttributeErrorwyjątku, jeśli atrybutu nie ma w __dict__obiekcie; ale tak naprawdę nie jest to potrzebne, ponieważ jest to domyślna implementacja, __getattribute__a tak naprawdę jest __getattr__to dokładnie to, czego potrzebujesz jako mechanizmu awaryjnego.
Rohit,
@Rohit currentjest zdefiniowana na wystąpień Count(patrz __init__), więc po prostu podnosząc AttributeErrorjeżeli atrybut nie istnieje nie do końca, co się dzieje - to odracza się __getattr__do wszystkich nazw rozpoczynając „CUR”, w tym current, ale także curious, curly...
joelb
16

To tylko przykład oparty na wyjaśnieniu Neda Batcheldera .

__getattr__ przykład:

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')

A jeśli użyjesz tego samego przykładu __getattribute__, otrzymasz >>>RuntimeError: maximum recursion depth exceeded while calling a Python object

Simon K Bhatta4ya
źródło
9
To jest okropne. Rzeczywiste __getattr__()implementacje akceptują tylko skończony zestaw poprawnych nazw atrybutów, zbierając AttributeErrornieprawidłowe nazwy atrybutów, unikając w ten sposób subtelnych i trudnych do debugowania problemów . Ten przykład bezwarunkowo akceptuje wszystkie nazwy atrybutów jako prawidłowe - dziwne (i szczerze mówiąc podatne na błędy) niewłaściwe użycie __getattr__(). Jeśli chcesz mieć „całkowitą kontrolę” nad tworzeniem atrybutów, jak w tym przykładzie, __getattribute__()zamiast tego.
Cecil Curry,
3
@CecilCurry: Wszystkie problemy, które podłączyłeś, obejmują niejawne zwracanie wartości Brak zamiast wartości, której ta odpowiedź nie robi. Co jest złego w akceptowaniu wszystkich nazw atrybutów? To to samo co defaultdict.
Nick Matteo,
2
Problem polega na tym __getattr__, że zostanie wywołany przed wyszukiwaniem w nadklasie. Jest to OK dla bezpośredniej podklasy object, ponieważ jedynymi metodami, na których naprawdę Ci zależy, są magiczne metody, które i tak ignorują instancję, ale w przypadku bardziej złożonej struktury dziedziczenia całkowicie usuwasz możliwość dziedziczenia czegokolwiek od rodzica.
Mad Physicist
1
@Simon K Bhatta4ya, twoje ostatnie wydrukowane oświadczenie jest komentarzem. Dobrze? Jest to długa linia i żmudna do czytania (trzeba przewijać dużo po prawej stronie). Co powiesz na umieszczenie wiersza za sekcją kodu? Lub jeśli chcesz umieścić go w sekcji kodu, myślę, że lepiej byłoby podzielić go na dwie różne linie.
Md. Abu Nafee Ibna Zahid
14

Klasy w nowym stylu dziedziczą objectpo innej klasie lub po niej:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

Klasy w starym stylu nie:

class SomeObject:
    pass

Dotyczy to tylko Pythona 2 - w Pythonie 3 wszystkie powyższe będą tworzyć klasy w nowym stylu.

Zobacz 9. Klasy (samouczek języka Python), NewClassVsClassicClass i Jaka jest różnica między starymi stylami a nowymi klasami stylów w Pythonie? dla szczegółów.

Sam Dolan
źródło
6

Klasy nowego stylu to te, które podklasują „obiekt” (bezpośrednio lub pośrednio). Mają __new__metodę klasową __init__i mają nieco bardziej racjonalne zachowanie na niskim poziomie.

Zwykle będziesz chciał przesłonić __getattr__(jeśli przesłonisz albo), w przeciwnym razie będziesz miał trudności z obsługą składni „self.foo” w ramach twoich metod.

Dodatkowe informacje: http://www.devx.com/opensource/Article/31482/0/page/4

Pan Fooz
źródło
2

Czytając płytkę drukowaną Beazley & Jones, natknąłem się na konkretny i praktyczny przypadek użycia, __getattr__który pomaga odpowiedzieć na część „kiedy” pytania PO. Z książki:

„Ta __getattr__()metoda przypomina rodzaj wyszukiwania całościowego. Jest to metoda wywoływana, jeśli kod próbuje uzyskać dostęp do atrybutu, który nie istnieje”. Wiemy to z powyższych odpowiedzi, ale w recepturze PCB 8.15 ta funkcja służy do implementacji wzorca projektu delegacji . Jeśli Obiekt A ma atrybut Obiekt B, który implementuje wiele metod, do których Obiekt A chce delegować, zamiast redefiniować wszystkie metody obiektu B w obiekcie A tylko w celu wywołania metod obiektu B, zdefiniuj __getattr__()metodę w następujący sposób:

def __getattr__(self, name):
    return getattr(self._b, name)

gdzie _b jest nazwą atrybutu obiektu A, który jest obiektem B. Gdy metoda zdefiniowana na obiekcie B __getattr__zostanie wywołana na obiekcie A, metoda zostanie wywołana na końcu łańcucha wyszukiwania. Uczyniłoby to również kod czystszym, ponieważ nie masz zdefiniowanej listy metod tylko do delegowania do innego obiektu.

soporific312
źródło