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 object
starym 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.mymin
i obj1.mymax
atrybuty wszystko działa poprawnie. Ale kiedy próbuję uzyskać dostęp do obj1.mycurrent
atrybutu - 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.mycurrent
atrybutu - 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 AttributeError
wyją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
AttributeError
wyją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 AttributeError
wyją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.
current
jest zdefiniowana na wystąpień Count
(patrz __init__
), więc po prostu podnosząc AttributeError
jeż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 AttributeError
nieprawidł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ą object
po 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.