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
Author: user2357112 supports Monica, 2008-12-16

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.

 130
Author: Egil,
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.

 27
Author: tzot,
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

Język Python reference:

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.

 18
Author: ttepasse,
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.

 13
Author: Singletoned,
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 (prawdopodobnie object'S)
    • wywołanie __getattr__
    • implementowanie własnego algorytmu wyszukiwania kropek w jakiś sposób

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
 7
Author: Aaron Hall,
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.

 5
Author: ElmoVanKielmo,
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