Sto cercando di capire quando usare __getattr__
o __getattribute__
. Le menzioni della documentazione si__getattribute__
applicano alle classi di nuovo stile. Cosa sono le lezioni di nuovo stile?
Sto cercando di capire quando usare __getattr__
o __getattribute__
. Le menzioni della documentazione si__getattribute__
applicano alle classi di nuovo stile. Cosa sono le lezioni di nuovo stile?
Risposte:
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__
.
__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?
__getattribute__
.
objec.__getattribute__
invoca myclass.__getattr__
nelle giuste circostanze.
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.mymin
e agli obj1.mymax
attributi tutto funziona bene. Ma quando provo ad accedere obj1.mycurrent
all'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.mycurrent
all'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 AttributeError
un'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]
Se la tua classe contiene entrambi i metodi magici getattr e getattribute , __getattribute__
viene chiamato per primo. Ma se __getattribute__
genera
AttributeError
un'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)
__getattribute__
ma sicuramente non lo è. Perché secondo il tuo esempio, tutto ciò che stai facendo __getattribute__
è sollevare AttributeError
un'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.
current
è definito su istanze di Count
(vedi __init__
), quindi semplicemente sollevare AttributeError
se 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
...
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
__getattr__()
implementazioni del mondo reale accettano solo un insieme finito di nomi di attributi validi aumentando AttributeError
per 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.
defaultdict
.
__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.
Le classi di nuovo stile ereditano da object
o da un'altra nuova classe di stile:
class SomeObject(object):
pass
class SubObject(SomeObject):
pass
Le lezioni di vecchio stile non:
class SomeObject:
pass
Questo vale solo per Python 2: in Python 3 tutto quanto sopra creerà nuove classi di stile.
Vedi 9. Classi (tutorial di Python), NewClassVsClassicClass e Qual è la differenza tra vecchio stile e nuove classi di stile in Python? per dettagli.
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
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.