Corso Python / Gestire metodi e proprietà con property e setter
I metodi descrivono operazioni su un oggetto; una property applica regole di lettura o scrittura mantenendo la sintassi di accesso a un attributo. Se un valore deve restare valido anche dopo la creazione dell’oggetto, puoi usare lo stesso setter durante l’inizializzazione e nelle modifiche successive.
Separare interfaccia pubblica e memorizzazione interna
Il nome pubblico amount indica il valore con cui lavora il chiamante; _amount è l’attributo usato per memorizzarlo internamente. Il prefisso con un trattino basso comunica una convenzione di uso interno, non una protezione di sicurezza: chi accede direttamente a _amount può aggirare il setter.
Nella classe ordinaria vista nella lezione su classi e istanze, un controllo in __init__ non impediva una successiva assegnazione negativa. Qui __init__ assegna attraverso self.amount, attivando la stessa regola usata dopo la costruzione.
@property definisce il getter, cioè la funzione usata quando leggi il valore. @amount.setter associa il setter, invocato dall’assegnazione. La documentazione ufficiale di property descrive questi accessi e la possibilità di definire valori di sola lettura.
Eseguire assegnazioni validate e un aggiornamento rifiutato
Salva il codice completo come properties.py, in UTF-8, in una cartella di esercizio. Dal terminale con l’interprete del progetto selezionato esegui python properties.py. Fuori dall’ambiente virtuale su macOS o Ubuntu usa python3 se è il comando dell’interprete scelto. Non servono file di dati o librerie esterne.
class Expense:
def __init__(self, amount):
self.amount = amount
@property
def amount(self):
return self._amount
@amount.setter
def amount(self, value):
if type(value) is not int or value < 0:
raise ValueError("amount must be a nonnegative integer")
self._amount = value
def add_fee(self, fee):
self.amount = self.amount + fee
item = Expense(8000)
item.add_fee(500)
print(item.amount)
try:
item.amount = -1
except ValueError:
print("rejected")
print(item.amount)
Il risultato previsto è:
8500
rejected
8500
La creazione imposta 8000 passando dal setter. Add_fee legge l’importo corrente, somma 500 e assegna 8500 di nuovo attraverso amount. Il tentativo di assegnare -1 solleva ValueError prima di cambiare _amount, perciò dopo il messaggio rejected rimane memorizzato 8500.
La regola richiede esattamente un int non negativo: rifiuta quindi anche True, pur essendo bool una sottoclasse di int, e valori come 3.5 o "8500". Zero è ammesso. Il codice non converte automaticamente una stringa in intero: un’eventuale conversione dell’ingresso deve avvenire secondo una scelta esplicita.
Evitare ricorsione e modifiche parziali
| Operazione | Comportamento nell’esempio |
|---|---|
| item.amount | Il getter restituisce _amount |
| item.amount = 100 | Il setter verifica il valore, poi lo memorizza in _amount |
| item.add_fee(500) | Il metodo calcola un nuovo importo e lo assegna tramite la property |
Il chiamante non usa parentesi per leggere una property: item.amount() tenterebbe di chiamare il numero restituito dal getter e provocherebbe TypeError. Add_fee è invece un metodo che compie un’operazione e viene chiamato con parentesi e argomento.
Nel setter non scrivere self.amount = value: richiamerebbe lo stesso setter ripetutamente fino a una RecursionError. L’assegnazione finale deve andare a _amount, dopo la verifica. Fare prima la modifica e soltanto dopo sollevare l’errore lascerebbe lo stato invalido proprio nel percorso che vuoi rifiutare.
Questa conservazione del valore precedente riguarda una singola assegnazione dell’esempio. Se un’operazione modifica diversi attributi, serve progettare la coerenza dell’intero aggiornamento; una property non costituisce automaticamente una transazione. Evita inoltre di nascondere richieste di rete o grandi elaborazioni dietro una lettura che sembra economica: un metodo esplicito comunica meglio tali operazioni.
Provare la politica e scegliere accessi di sola lettura
- Partendo dall’importo 8500, prova ad assegnare True e poi 3.5, gestendo ciascun ValueError separatamente. Dopo ogni tentativo verifica che item.amount resti 8500.
- Crea una nuova spesa con importo 0 e verifica che sia accettata. Prova separatamente una creazione con -1: anche l’inizializzazione deve essere rifiutata dal setter.
- Su una spesa nuova da 8000, prova add_fee(-500): il risultato 7500 è ancora valido e il codice attuale lo accetta. Decidi se una riduzione deve essere ammessa oppure se fee richiede una propria regola.
- Ripristina il sorgente originale e verifica nuovamente 8500, rejected, 8500.
Il metodo controlla il risultato tramite la property, non ogni significato indipendente di fee. Per esempio, l’addizione con True produce un intero e può superare il controllo finale, anche se assegnare True direttamente ad amount viene rifiutato. Se fee deve essere un intero non negativo distinto dai booleani, controllalo esplicitamente nel metodo.
Una property senza setter rifiuta l’assegnazione ordinaria attraverso quel nome, ma non rende immutabile tutto l’oggetto: altri attributi o oggetti contenuti possono ancora cambiare. Scegli una property per leggere o impostare un valore, e un metodo quando vuoi rendere evidente un’operazione con un suo contratto.




