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


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)

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


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


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


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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.