Comprensione della differenza tra __getattr__ e __getattribute__


206

Sto cercando di capire la differenza tra __getattr__e __getattribute__, tuttavia, non ci riesco.

La risposta alla domanda StackTranslate.it La differenza tra __getattr__vs__getattribute__ dice:

__getattribute__viene richiamato prima di esaminare gli attributi effettivi sull'oggetto e pertanto può essere difficile implementarlo correttamente. Puoi finire in infinite ricorsioni molto facilmente.

Non ho assolutamente idea di cosa significhi.

Quindi continua dicendo:

Quasi sicuramente lo vuoi __getattr__.

Perché?

Ho letto che se __getattribute__fallisce, __getattr__si chiama. Quindi perché ci sono due metodi diversi che fanno la stessa cosa? Se il mio codice implementa le nuove classi di stile, cosa dovrei usare?

Sto cercando alcuni esempi di codice per cancellare questa domanda. Ho cercato su Google al meglio delle mie capacità, ma le risposte che ho trovato non discutono a fondo del problema.

Se c'è della documentazione, sono pronto a leggerlo.


2
se aiuta, dai documenti "Al fine di evitare la ricorsione infinita in questo metodo, la sua implementazione dovrebbe sempre chiamare il metodo della classe base con lo stesso nome per accedere a tutti gli attributi di cui ha bisogno, ad esempio l'oggetto .__ getattribute __ (self, name). " [ docs.python.org/2/reference/…
kmonsoor

Risposte:


307

Prima alcune basi.

Con gli oggetti, devi occuparti dei suoi attributi. Di solito lo facciamo instance.attribute. A volte abbiamo bisogno di un maggiore controllo (quando non conosciamo in anticipo il nome dell'attributo).

Ad esempio, instance.attributesarebbe diventato getattr(instance, attribute_name). Usando questo modello, possiamo ottenere l'attributo fornendo l' attributo_name come stringa.

Uso di __getattr__

Puoi anche dire a una classe come gestire gli attributi che non gestisce esplicitamente e farlo tramite il __getattr__metodo.

Python chiamerà questo metodo ogni volta che richiedi un attributo che non è già stato definito, quindi puoi definire cosa fare con esso.

Un caso d'uso classico:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

Avvertenze e uso di __getattribute__

Se devi catturare ogni attributo indipendentemente dal fatto che esista o meno , usa __getattribute__invece. La differenza è che __getattr__viene chiamato solo per gli attributi che in realtà non esistono. Se si imposta direttamente un attributo, fare riferimento a tale attributo lo recupererà senza chiamare __getattr__.

__getattribute__ viene chiamato tutte le volte.


"Ad esempio, instance.attribute diventerebbe getattr(instance, attribute_name).". Non dovrebbe essere __getattribute__(instance, attribute_name)?
Md. Abu Nafee Ibna Zahid,

1
@ Md.AbuNafeeIbnaZahid getattrè una funzione integrata. getattr(foo, 'bar')è equivalente a foo.bar.
wizzwizz4,

vale la pena notare che __getattr__viene chiamato solo se definito, in quanto non ereditato daobject
joelb

94

__getattribute__ viene chiamato ogni volta che si verifica un accesso all'attributo.

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

Ciò causerà una ricorsione infinita. Il colpevole qui è la linea return self.__dict__[attr]. Facciamo finta (è abbastanza vicino alla verità) che tutti gli attributi sono memorizzati self.__dict__e disponibili con il loro nome. La linea

f.a

tenta di accedere aall'attributo di f. Questo chiama f.__getattribute__('a'). __getattribute__quindi tenta di caricare self.__dict__. __dict__è un attributo di self == fe quindi chiama Python f.__getattribute__('__dict__')che tenta nuovamente di accedere all'attributo '__dict__'. Questa è una ricorsione infinita.

Se __getattr__fosse stato usato invece allora

  1. Non sarebbe mai stato eseguito perché fha un aattributo.
  2. Se fosse stato eseguito (supponiamo che tu lo chiedessi f.b), non sarebbe stato chiamato per trovare __dict__perché è già lì e __getattr__viene invocato solo se tutti gli altri metodi per trovare l'attributo non sono riusciti .

Il modo 'corretto' di scrivere usando la classe sopra __getattribute__è

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr)associa il __getattribute__metodo della superclasse "più vicina" (formalmente, la classe successiva nell'ordine di risoluzione del metodo o MRO della classe) all'oggetto corrente, selfquindi lo chiama e consente di eseguire il lavoro.

Tutti questi problemi vengono evitati utilizzando ciò __getattr__che consente a Python di fare le cose normali fino a quando non viene trovato un attributo. A quel punto, Python passa il controllo del tuo __getattr__metodo e gli consente di inventare qualcosa.

Vale anche la pena notare che è possibile imbattersi in una ricorsione infinita con __getattr__.

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

Lascio quello come esercizio.


60

Penso che le altre risposte abbiano fatto un ottimo lavoro nel spiegare la differenza tra __getattr__e __getattribute__, ma una cosa che potrebbe non essere chiara è il motivo per cui vorresti usare __getattribute__. La cosa interessante __getattribute__è che essenzialmente ti consente di sovraccaricare il punto quando accedi a una classe. Ciò consente di personalizzare la modalità di accesso agli attributi a basso livello. Ad esempio, supponiamo che io voglia definire una classe in cui tutti i metodi che accettano solo un argomento personale vengono trattati come proprietà:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

E dall'interprete interattivo:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

Ovviamente questo è un esempio sciocco e probabilmente non vorrai mai farlo, ma ti mostra il potere che puoi ottenere dall'impedire __getattribute__.


6

Ho esaminato l'eccellente spiegazione degli altri. Tuttavia, ho trovato una semplice risposta da questo blog Python Magic Methods e__getattr__ . Tutti i seguenti sono da lì.

Usando il __getattr__metodo magico, possiamo intercettare quella ricerca di attributi inesistente e fare qualcosa in modo che non fallisca:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

Ma se l'attributo esiste, __getattr__non verrà invocato:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute__è simile a __getattr__, con l'importante differenza che __getattribute__intercetterà OGNI ricerca di attributi, non importa se l'attributo esiste o meno.

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

In questo esempio, l' doggetto ha già un valore di attributo. Ma quando proviamo ad accedervi, non otteniamo il valore atteso originale ("Python"); stiamo solo ottenendo ciò che è stato __getattribute__restituito. Significa che abbiamo praticamente perso l'attributo value; è diventato "irraggiungibile".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.