Ricerca degli attributi
Per cercare gli attributi di un oggetto O, come ad esempio O.a, si esegue il seguente algoritmo (supponendo che l’interprete abbia già recuperato l’oggetto O):
- ricerca
atra tutti gli attributi diO - se
Onon è una classe procede al passo 4 - effettua una ricerca nelle superclassi di
O - posto
T=type(O)esegue una ricerca negli attributi diT - esegue una ricerca nelle superclassi di
Tseguendo l’ordine indicato da MRO, che può essere visualizzato conO.mro().

class N(type):
a = 0
class M(N):
pass
class C:
pass
class B(C):
pass
class A(B,metaclass=M): # Questo è il modo di specificare un tipo diverso da type
passtitle: Attenzione!
`A.a` viene trovato senza problemi, perchè `a` in `N`.
Se però si crea una istanza di `A`, quale `i = A()` e si cerca di accedere ad `a`, viene generato un errore perchè `a` non fa parte di `A`.
Questo perché l'attributo non è definito in A o in una delle sue superclassi, ma in una metaclass.Intervenire sul protocollo
La ricerca degli attributi non è implementata completamente nell’interprete, ma può essere modificata dallo sviluppatore tramite alcuni metodi magici:
__getattribute__: invocato per accessi in lettura__getattr__: invocato da__getattribute__quando si verifica una miss (non trova l’attributo)__setattr__: usato per modificare/definire il valore di un attributo
class parent:
z = "Classe padre"
class son(parent):
y = "Classe figlia"
def __init__(self, x="")
self.x = x
o = son("ciao")
print(o.x) # ciao
o.x = "hello"
print(o.x) # helloQuesto ultimo pezzo di codice, dove vengono usati metodi chiamati implicitamente dall’interprete, si può riscrivere in maniera più esplicita come segue:
print(o.__getattribute__("x"))
o.__setattr__("x", "hello")
print(o.__getattribute__("x"))Tutti questi metodi sono definiti nella classe object, che è la superclasse di tutti gli oggetti.
Si possono stampare tutti gli attributi presenti in un oggetto tramite uno speciale dizionare (anch’esso un attributo)
pprint(dict(son.__dict__))
pprint(dict(parend.__dict__))
pprint(object.__dict__['__getattribute__'])Ridefinire i metodi base
Si possono ovviamente ridefinire i metodi appena visti, per modificare qualche aspetto del procedimento standard. Python supporta super().
__getattribute__
class A:
def __getattribute__(self, x):
print("eseguo getattribute in A")
return super().__getattribute__(x)
class B(A):
def __getattribute__(self, x):
print("eseguo getattribute in B")
return super().__getattribute__(x)
class C(A):
def __getattribute__(self, x):
print("eseguo getattribute in C")
return super().__getattribute__(x)
class D(B, C):
y = "ciao"
a = D()
a.y # stampa tutti i print di __getattribute__ di
# (in ordine) B, C, A__getattr__
Questo metodo viene chiamato quando __getattribute__ non ritorna nulla.
class A:
def __getattr__(self, x):
print("eseguo getattr in A, perchè non esiste questo attributo")
return self.__setattr__(x, "no value")
class B(A): pass
class C(A): pass
class D(B, C):
y = "ciao"
a = D()
a.s # viene eseguito __getattr__ di A
a.s # stampa "no value"Overload metodi
Quando si chiama un metodo oggetto.f, ovviamente f viene cercata tra gli attributi di oggetto. Quando viene trovata, si controlla che sia codice effettivamente eseguibile, e in tal caso si passano i parametri. Se non è codice eseguibile, __getattributes__ demanda il lookup alla classe object.
Se lo trova, agisce come decoratore, e restituisce una funzione che, quando verrà chiamata, conta il numero di parametri, scorre la catena ereditaria per cercare una funzione con la stessa firma, ed esegue il metodo trovato.
Se non trova nulla ritorna None, che poi causerà un errore.
Ispezionare un metodo
Per ispezionare il numero di parametri di un metodo, esiste una libreria inspect.
from inspect import signature
signature(funzione).parameters