Differenza tra __getattr__ vs __getattribute__


Risposte:


497

Una differenza fondamentale tra __getattr__e __getattribute__è che __getattr__viene invocata solo se l'attributo non è stato trovato nei modi normali. È utile per l'implementazione di un fallback per gli attributi mancanti ed è probabilmente uno dei due desiderati.

__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.

Le classi di nuovo stile derivano da object, le classi di vecchio stile sono quelle di Python 2.x senza una classe di base esplicita. Ma la distinzione tra classi vecchio stile e nuovo stile non è importante quando si sceglie tra __getattr__e __getattribute__.

Quasi sicuramente lo vuoi __getattr__.


6
Posso implementarli entrambi nella stessa classe? Se posso, che cos'è per implementare entrambi?
Alcott,

13
@Alcott: puoi implementarli entrambi, ma non sono sicuro del perché. __getattribute__verrà chiamato per ogni accesso e __getattr__verrà chiamato per i tempi che hanno __getattribute__generato un AttributeError. Perché non tenerlo tutto in uno?
Ned Batchelder,

10
@NedBatchelder, se si desidera (condizionatamente) sovrascrivere le chiamate ai metodi esistenti, si desidera utilizzare __getattribute__.
Jace Browning,

28
"Per 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 oggetto .__ getattribute __ (self, name)."
kmonsoor,

1
@kmonsoor. Questa è una buona ragione per implementare entrambi. objec.__getattribute__invoca myclass.__getattr__nelle giuste circostanze.
Fisico pazzo,

138

Vediamo alcuni semplici esempi di entrambi __getattr__e __getattribute__metodi magici.

__getattr__

Python chiamerà __getattr__metodo ogni volta che richiedi un attributo che non è già stato definito. Nel seguente esempio la mia classe Count non ha alcun __getattr__metodo. Ora, soprattutto, quando provo ad accedere ad entrambi obj1.mymine agli obj1.mymaxattributi tutto funziona bene. Ma quando provo ad accedere obj1.mycurrentall'attributo - Python mi dàAttributeError: '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'

Ora il mio conteggio di classe ha __getattr__metodo. Ora quando provo ad accedere obj1.mycurrentall'attributo - python mi restituisce tutto ciò che ho implementato nel mio __getattr__metodo. Nel mio esempio ogni volta che provo a chiamare un attributo che non esiste, Python crea quell'attributo e lo imposta su un valore intero 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__

Ora vediamo il __getattribute__metodo. Se hai un __getattribute__metodo nella tua classe, python invoca questo metodo per ogni attributo indipendentemente dal fatto che esista o meno. Quindi perché abbiamo bisogno del __getattribute__metodo? Un buon motivo è che puoi impedire l'accesso agli attributi e renderli più sicuri, come mostrato nell'esempio seguente.

Ogni volta che qualcuno prova ad accedere ai miei attributi che iniziano con la sottostringa 'cur' python solleva AttributeErrorun'eccezione. Altrimenti restituisce quell'attributo.

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)

Importante: per evitare la ricorsione infinita nel __getattribute__metodo, la sua implementazione dovrebbe sempre chiamare il metodo della classe base con lo stesso nome per accedere a tutti gli attributi necessari. Ad esempio: object.__getattribute__(self, name)o super().__getattribute__(item)noself.__dict__[item]

IMPORTANTE

Se la tua classe contiene entrambi i metodi magici getattr e getattribute , __getattribute__viene chiamato per primo. Ma se __getattribute__genera AttributeErrorun'eccezione, l'eccezione verrà ignorata e __getattr__verrà invocato il metodo. Vedi il seguente esempio:

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)

1
Non sono sicuro di quale sarebbe esattamente il caso d'uso per l'override, __getattribute__ma sicuramente non lo è. Perché secondo il tuo esempio, tutto ciò che stai facendo __getattribute__è sollevare AttributeErrorun'eccezione se l'attributo non è presente __dict__nell'oggetto; ma non è davvero necessario perché si tratta dell'implementazione predefinita di __getattribute__e infatti __getattr__è esattamente ciò di cui hai bisogno come meccanismo di fallback.
Rohit,

@Rohit currentè definito su istanze di Count(vedi __init__), quindi semplicemente sollevare AttributeErrorse l'attributo non c'è non è proprio quello che sta succedendo - difende __getattr__per tutti i nomi che iniziano 'cur', incluso current, ma anche curious, curly...
joelb

16

Questo è solo un esempio basato sulla spiegazione di Ned Batchelder .

__getattr__ esempio:

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')

E se lo stesso esempio viene usato con __getattribute__You get >>>RuntimeError: maximum recursion depth exceeded while calling a Python object


9
In realtà, questo è terribile. Le __getattr__()implementazioni del mondo reale accettano solo un insieme finito di nomi di attributi validi aumentando AttributeErrorper nomi di attributi non validi, evitando in tal modo problemi sottili e difficili da eseguire il debug . Questo esempio accetta incondizionatamente tutti i nomi degli attributi come validi - un bizzarro (e francamente soggetto a errori) di uso improprio __getattr__(). Se si desidera "controllo totale" sulla creazione degli attributi come in questo esempio, si desidera __getattribute__()invece.
Cecil Curry

3
@CecilCurry: tutti i problemi che hai collegato riguardano implicitamente la restituzione di Nessuno anziché un valore, cosa che questa risposta non ha. Cosa c'è di sbagliato nell'accettare tutti i nomi degli attributi? È lo stesso di a defaultdict.
Nick Matteo,

2
Il problema è che __getattr__verrà chiamato prima della ricerca della superclasse. Questo è OK per una sottoclasse diretta di object, poiché gli unici metodi a cui tieni davvero ci sono metodi magici che ignorano comunque l'istanza, ma per qualsiasi struttura di eredità più complessa, rimuovi completamente la capacità di ereditare qualcosa dal genitore.
Fisico pazzo,

1
@Simon K Bhatta4ya, la tua ultima dichiarazione stampata è un commento. Giusto? È lunga e noiosa da leggere (bisogna scorrere molto nella parte destra). Che ne dici di mettere la linea dopo la sezione del codice? O se vuoi inserirlo nella sezione del codice, penso che sarebbe meglio suddividerlo in due righe diverse.
Md. Abu Nafee Ibna Zahid,


6

Le classi di nuovo stile sono quelle che sottoclassano "oggetto" (direttamente o indirettamente). Hanno un __new__metodo di classe in aggiunta __init__e hanno un comportamento a basso livello un po 'più razionale.

Di solito, vorrai eseguire l'override __getattr__(se stai eseguendo l'override), altrimenti avrai difficoltà a supportare la sintassi "self.foo" nei tuoi metodi.

Informazioni extra: http://www.devx.com/opensource/Article/31482/0/page/4


2

Nel leggere il PCB di Beazley & Jones, mi sono imbattuto in un caso d'uso esplicito e pratico __getattr__che aiuta a rispondere alla parte "quando" della domanda del PO. Dal libro:

"Il __getattr__()metodo è una specie di catch-all per la ricerca degli attributi. È un metodo che viene chiamato se il codice tenta di accedere a un attributo che non esiste." Lo sappiamo dalle risposte precedenti, ma nella ricetta PCB 8.15, questa funzionalità viene utilizzata per implementare il modello di progettazione della delega . Se l'oggetto A ha un attributo oggetto B che implementa molti metodi a cui l'oggetto A vuole delegare, piuttosto che ridefinire tutti i metodi dell'oggetto B nell'oggetto A solo per chiamare i metodi dell'oggetto B, definire un __getattr__()metodo come segue:

def __getattr__(self, name):
    return getattr(self._b, name)

dove _b è il nome dell'attributo dell'oggetto A che è un oggetto B. Quando viene chiamato un metodo definito sull'oggetto B sull'oggetto A, il __getattr__metodo verrà invocato alla fine della catena di ricerca. Ciò renderebbe anche il codice più pulito, poiché non si dispone di un elenco di metodi definiti solo per la delega a un altro oggetto.

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.