Jak zaimplementować getattribute bez błędu nieskończonej rekurencji?
Chcę nadpisać dostęp do jednej zmiennej w klasie, ale zwracam wszystkie inne normalnie. Jak to osiągnąć z __getattribute__
?
Próbowałem następujących (co powinno również zilustrować to, co próbuję zrobić) , ale dostaję błąd rekurencji:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else:
return self.__dict__[name]
>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
6 answers
Pojawia się błąd rekurencji, ponieważ próba uzyskania dostępu do atrybutu self.__dict__
wewnątrz __getattribute__
ponownie wywołuje __getattribute__
. Jeśli zamiast tego użyjesz object
' s __getattribute__
, działa:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else:
return object.__getattribute__(self, name)
To działa, ponieważ object
(w tym przykładzie) jest klasą bazową. Wywołując podstawową wersję __getattribute__
unikasz rekurencyjnego piekła, w którym byłeś wcześniej.
Wyjście Ipython z kodem w foo.py:
In [1]: from foo import *
In [2]: d = D()
In [3]: d.test
Out[3]: 0.0
In [4]: d.test2
Out[4]: 21
Update:
Jest coś w sekcji zatytułowanej więcej dostęp do atrybutów dla klas nowego stylu w aktualnej dokumentacji, gdzie zalecają dokładnie to, aby uniknąć nieskończonej rekurencji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-28 23:20:14
Właściwie, wierzę, że zamiast tego chcesz użyć __getattr__
specjalnej metody.
Cytat z Python docs:
__getattr__( self, name)
Wywoływane, gdy wyszukiwanie atrybutów Nie znajduje atrybutu w zwykłych miejscach (tzn. nie jest atrybutem instancji ani nie znajduje się w drzewie klas dla self). nazwa to nazwa atrybutu. Ta metoda powinna zwrócić (obliczoną) wartość atrybutu lub wywołać wyjątek AttributeError.
Zauważ, że jeśli atrybut zostanie znaleziony przez normalną mechanizm,__getattr__()
nie jest wywoływany. (Jest to celowa asymetria pomiędzy__getattr__()
i__setattr__()
.) Odbywa się to zarówno ze względu na wydajność, jak i dlatego, że w przeciwnym razie__setattr__()
nie miałyby dostępu do innych atrybutów instancji. Zauważ, że przynajmniej na przykład zmienne, możesz udawać całkowitą kontrolę, Nie wstawiając żadnych wartości w słowniku atrybutów instancji (ale zamiast tego wstawiając je do innego obiektu). Zobacz metodę__getattribute__()
poniżej, aby uzyskać całkowitą kontrolę w Nowym Stylu klasy.
Uwaga: Aby to zadziałało, instancja powinna nie mieć atrybutu test
, więc linia self.test=20
powinna zostać usunięta.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55
W celu uniknięcia nieskończonej rekurencji w tej metodzie jego realizacja powinien zawsze wywoływać klasę bazową metoda o tej samej nazwie, aby uzyskać dostęp dowolnych atrybutów, np.,
object.__getattribute__(self, name)
.
Znaczenie:
def __getattribute__(self,name):
...
return self.__dict__[name]
Wywołujesz atrybut o nazwie __dict__
. Ponieważ jest to atrybut, {[4] } jest wywoływany w poszukiwaniu __dict__
który wywołuje __getattribute__
który wywołuje ... yada yada Yada
return object.__getattribute__(self, name)
Korzystanie z base classes __getattribute__
pomaga znaleźć prawdziwy atrybut.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-15 05:41:36
Jesteś pewien, że chcesz użyć __getattribute__
? Co tak naprawdę próbujesz osiągnąć?
Najprostszym sposobem na zrobienie tego, o co prosisz jest:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
test = 0
Lub:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
@property
def test(self):
return 0
Edytuj:
Zauważ, że instancja D
będzie miała różne wartości test
w każdym przypadku. W pierwszym przypadku d.test
będzie 20, w drugim będzie 0. Zostawię to tobie, żebyś się zastanowił dlaczego.
Edit2:
Greg wskazał, że przykład 2 nie powiedzie się, ponieważ właściwość jest tylko do odczytu i metoda __init__
próbowała ustawić do 20. Bardziej kompletny przykład to:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
_test = 0
def get_test(self):
return self._test
def set_test(self, value):
self._test = value
test = property(get_test, set_test)
Oczywiście, jako klasa jest to prawie całkowicie bezużyteczne, ale daje pomysł, aby przejść dalej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-26 14:41:40
Jak stosuje się metodę
__getattribute__
?
Jest wywoływany przed zwykłym przeszukiwaniem kropkowanym. Jeśli podnosi AttributeError
, to wywołujemy __getattr__
.
Stosowanie tej metody jest raczej rzadkie. Istnieją tylko dwie definicje w bibliotece standardowej:
$ grep -Erl "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py
Najlepsze Praktyki
Właściwą metodą programowej kontroli dostępu do pojedynczego atrybutu jest property
. Klasa D
powinna być zapisana w następujący sposób (z opcjonalnym setterem i deleterem aby odtworzyć pozornie zamierzone zachowanie):
class D(object):
def __init__(self):
self.test2=21
@property
def test(self):
return 0.
@test.setter
def test(self, value):
'''dummy function to avoid AttributeError on setting property'''
@test.deleter
def test(self):
'''dummy function to avoid AttributeError on deleting property'''
I użycie:
>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
Właściwość jest deskryptorem danych, więc jest to pierwsza rzecz, której szukano w normalnym algorytmie wyszukiwania kropkowanego.
Opcje dla __getattribute__
Masz kilka opcji, jeśli koniecznie musisz zaimplementować wyszukiwanie dla każdego atrybutu poprzez __getattribute__
.
- raise
AttributeError
, powodujące wywołanie__getattr__
(jeśli jest zaimplementowane) - zwróć coś z niego przez
- za pomocą
super
aby wywołać implementację rodzica (prawdopodobnieobject
'S) - wywołanie
__getattr__
- implementowanie własnego algorytmu wyszukiwania kropek w jakiś sposób
- za pomocą
Na przykład:
class NoisyAttributes(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self, name):
print('getting: ' + name)
try:
return super(NoisyAttributes, self).__getattribute__(name)
except AttributeError:
print('oh no, AttributeError caught and reraising')
raise
def __getattr__(self, name):
"""Called if __getattribute__ raises AttributeError"""
return 'close but no ' + name
>>> n = NoisyAttributes()
>>> nfoo = n.foo
getting: foo
oh no, AttributeError caught and reraising
>>> nfoo
'close but no foo'
>>> n.test
getting: test
20
To, czego pierwotnie chciałeś.
I ten przykład pokazuje, jak możesz zrobić to, co pierwotnie chciałeś:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else:
return super(D, self).__getattribute__(name)
I będzie się tak zachowywał:
>>> o = D()
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
>>> del o.test
Traceback (most recent call last):
File "<pyshell#216>", line 1, in <module>
del o.test
AttributeError: test
Code review
Twój kod z komentarzami. Masz przerywane wyszukiwanie na siebie w __getattribute__
.
Dlatego pojawia się błąd rekurencji. Możesz sprawdzić, czy nazwa to "__dict__"
i użyć super
do obejścia problemu, ale to nie obejmuje __slots__
. Zostawię to jako ćwiczenie czytelnikowi.
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else: # v--- Dotted lookup on self in __getattribute__
return self.__dict__[name]
>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-03 15:24:34
Oto bardziej wiarygodna wersja:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
def __getattribute__(self, name):
if name == 'test':
return 0.
else:
return super(D, self).__getattribute__(name)
Nazywa __getattribute__ metoda z klasy rodzica, w końcu spada z powrotem do obiektu.__getattribute__ metoda, jeśli inni przodkowie nie nadpisują jej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-29 10:18:19