Próbuję zrozumieć, kiedy użyć __getattr__lub __getattribute__. Dokumentacja wspomina __getattribute__dotyczy klas w nowym stylu. Co to są zajęcia w nowym stylu?
Próbuję zrozumieć, kiedy użyć __getattr__lub __getattribute__. Dokumentacja wspomina __getattribute__dotyczy klas w nowym stylu. Co to są zajęcia w nowym stylu?
Odpowiedzi:
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__.
__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?
__getattribute__.
objec.__getattribute__wywołuje myclass.__getattr__w odpowiednich okolicznościach.
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]
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)
__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.
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...
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
__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.
defaultdict.
__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.
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.
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
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.