Descrittori

Classi utilizzate per controllare l’accesso ad attributi di altre classi.

Un descrittore quindi è una classe che implementa almeno uno di questi metodi:

  1. __get__(self, obj, objtype=None)
  2. __set__(self, obj, value)
  3. __delete__(self, obj)

Il descrittore implementa un protocollo generale applicabile a tutte le istanze. Il descrittore, dal momento che i metodi hanno accesso all’istanza che li accedono, deve essere assegnato ad un attributo di classe. Se fosse assegnato ad ogni istanza, ci sarebbe una ridondanza inutile.

__get__(self, obj, objtype=None) Viene chiamato quando si accede all’attributo del tipo della classe che lo implementa. obj si riferisce all’istanza di cui fa parte la variabile che implementa questo metodo.

class descr:
    def __get__(self,obj,objtype=None):
        print(f"self\t= {self}\nobj\t= {obj}\nobjtype\t= {objtype}")
		'''Ad ogni accesso, il conteggio totale delle letture viene incrementato'''
        obj.accesscount += 1
        return obj.accesscount
		
	def __set__(self,obj,initc):
        '''Semplicemente re-inizializza il counter degli accessi'''
        print("SET: inizializzo il counter degli accessi")
        obj.accesscount = initc
		
class app:
    v = descr()
    print(v)	# <__main__.descr object at 0x1089acc70>
 
c = app()
print(c)		# <__main__.app object at 0x1089aca90>
 
c.v
# self	= <__main__.descr object at 0x1089acc70>
# obj	= <__main__.app object at 0x1089aca90>
# objtype	= <class '__main__.app'>
 
# '__get__ called'

__set__(self, obj, value) Come ci si aspetterebbe, viene chiamato quando si vuole assegnare un valore all’attributo.

__delete__(self, obj) L’attributo __delete__ viene chiamato quando l’oggetto viene eliminato tramite la keyword del.

a = 2
a	# stampa 2
del a
# a non esiste più

property

property è un particolare descrittore. Nell’init si possono passare 4 Keyword arguments, che verranno utilizzate come wrapped fuctions:

  • fget: __get__
  • fset: __set__
  • fdel: __delete__
  • doc: __doc__ DocString

Quindi tutte le funzioni di base (__get__, …) agiscono da funzioni wrapper per fget ecc.

Dal momento che le funzioni wrapper fanno dei controlli prima di chiamare le nostre funzioni assegnate, la signature delle funzioni custom può essere più corta.

  • fget(self)
  • fset(self, value)
  • fdel(self)

ATTENZIONE La funzione fget è una funzione vera e propria definita nell’oggetto di tipo property (non un bound method), quindi la chiamata è self.fget(obj), e non fa il bound di self come primo parametro.

10x property

Oltre a creare il descrittore usando saldo = property(fget=_get,fset=_set), si può fare una cosa più elegante e pulita, ovvero utilizzare property come decoratore.

@property
def saldo(self):
    # Dopo l'esecuzione di questo codice, saldo è un attributo il cui valore
    # è un descrittore in cui è definita solo la funzione corrispondente a __get__
    # Sappiamo allora che saldo.setter è una funzione che restituisce il
    # descrittore modificato con l'aggiuta di una funzione per __set__
    return self._saldo_disponibile
 
@saldo.setter
def saldo(self, importo):
    # Dopo l'esecuzione di questo codice saldo è un attributo il cui valore
    # è un descrittore in cui sono definite le funzioni __get__ e __set__
    if importo>0:
        self._saldo_disponibile = importo
        self.mostra_saldo()
    else:
        print(f"Il massimo prelevabile è {self._saldo_disponibile:.2f}")

Con @name.setter si attribuisce a property la funzione di __set__.

python_book7