Incapsulamento
Keep it secret. Keep it safe.
Alla fine della scorsa lezione ti ho lasciato con una falla. I nostri brani ora si raccontano bene con __str__ e __repr__, ma sono indifesi: chiunque, da qualunque punto del programma, può scrivere bohemian.durata = -50 e darci un brano che dura meno di zero secondi. L’oggetto incassa il colpo senza fiatare.
Questa è esattamente la situazione che Gandalf vorrebbe evitare. Lo stato di un oggetto — i suoi dati interni — è come l’Anello: alcune cose è meglio tenerle nascoste e protette, non lasciarle in mano al primo che passa. Oggi insegniamo ai nostri oggetti a difendere il proprio stato: cosa lasciar vedere, cosa lasciar toccare, e cosa chiudere a chiave.
Lo stato spalancato
Partiamo dal problema, in concreto. Ecco il Brano come lo conosciamo, con i suoi attributi pubblici. Predici l’output prima di premere Run: cosa succede quando assegniamo valori assurdi?
Nessun errore. Python esegue tutto con la faccia impassibile di chi non vuole problemi: accetta una durata negativa, accetta un numero al posto del nome dell’artista, e ti consegna un brano che non ha alcun senso. Il guaio è che l’esplosione arriverà più tardi — venti righe dopo, quando qualcuno proverà a sommare le durate della playlist e si ritroverà un totale più corto del previsto, senza capire perché. Lo stato dell’oggetto è spalancato, e nessuno fa la guardia.
Cosa significa incapsulare
Incapsulare vuol dire, letteralmente, mettere in una capsula. Nell’OOP significa raggruppare dati e comportamento in un oggetto e — soprattutto — decidere cosa è visibile dall’esterno e cosa resta affare interno.
Ogni oggetto ben fatto ha due facce:
- L’interfaccia pubblica: i metodi e gli attributi che il mondo esterno può legittimamente usare. È il contratto che la classe offre. «Puoi chiedermi di riprodurmi, puoi leggermi il titolo, puoi votarmi da 1 a 5.»
- Lo stato interno: le variabili che servono a far funzionare la classe ma che non riguardano chi sta fuori. È il «come funziona dentro», non il «cosa puoi fare con me».
Le convenzioni: _ e __
Diversamente da Java o C++, Python non ha le parole chiave private e public. La sua filosofia è «siamo tutti adulti consenzienti»: il linguaggio non ti impedisce nulla, si limita a segnalare l’intenzione con il nome dell’attributo. Le convenzioni sono due, di severità crescente:
- Un underscore (
_durata): «convenzionalmente privato». Un cartello: questo è un dettaglio interno, non toccarlo se non sai cosa fai. Python non ti ferma; ti guarda male. - Due underscore (
__durata): «fortemente privato». Qui Python attiva il name mangling e rende l’accesso dall’esterno effettivamente scomodo.
In questa lezione usiamo il doppio underscore, perché il meccanismo che attiva è il più istruttivo da vedere. Nella prossima lezione, con le @property, scopriremo perché nel codice Python reale si preferisce spesso il singolo underscore.
Cosa fa il name mangling
Quando Python incontra un attributo che inizia con due underscore (e non finisce con due underscore — quelli sono i dunder, tutt’altra cosa), ne riscrive il nome internamente in _NomeClasse__attributo. Una rietichettatura automatica. Prova: questo blocco solleva un errore di proposito — predici quale riga lo causa.
L’attributo non è davvero inaccessibile: chiunque sia disposto a scrivere _Brano__durata può raggiungerlo. Ma è abbastanza brutto da scoraggiare l’accesso accidentale e da far capire a chi legge che sta forzando qualcosa che non dovrebbe.
Getter: leggere lo stato protetto
Ora che gli attributi sono privati, serve un modo controllato per leggerli dall’esterno: un getter, un metodo pubblico il cui scopo è restituire un attributo privato. Per convenzione si chiama get_qualcosa.
Quella riga bohemian.durata = -50 ora non fa più danni: crea un attributo-spazzatura che nessun metodo guarderà mai. Il get_durata() continua a restituire il valore vero, blindato dentro __durata. La falla con cui abbiamo aperto la lezione è chiusa.
Setter: scrivere con validazione
A volte un attributo deve poter cambiare anche dall’esterno — ma in modo controllato. Per questo serve un setter: un metodo che modifica un attributo privato dopo aver validato l’input. Aggiungiamo a Brano una valutazione a stelle, da 1 a 5.
Prova a passare valori assurdi: il setter intercetta, valida e rifiuta. Lo stato dell’oggetto resta sempre coerente — un brano non avrà mai una valutazione di 11 stelle o della stringa "cinque". Questa è la promessa che l’incapsulamento permette di mantenere.
Tre livelli di visibilità
Combinando attributi privati con la presenza o l’assenza di getter e setter, ogni attributo ricade in una di tre categorie. Sceglierla è una delle decisioni di design più importanti quando scrivi una classe.
- Solo interno — né getter né setter. Dati di servizio che riguardano solo la logica interna: un buffer, un contatore privato, un timestamp dell’ultima riproduzione. Il mondo esterno non sa nemmeno che esistono.
- Read-only — solo il getter. Identità e dati fissati alla nascita:
titolo,artista,durata. Un brano non si rinomina a metà ascolto e non cambia durata: leggerli ha senso, scriverli no. - Read/write controllato — getter e setter con validazione. Stato che può legittimamente cambiare durante la vita dell’oggetto, come la valutazione: leggibile e modificabile, ma solo entro regole precise.
Tell, don’t ask
Getter e setter portano con sé un rischio sottile: scrivere codice «interrogativo», in cui chi usa l’oggetto chiede i dati, ci ragiona sopra e rispedisce dentro il risultato. Vogliamo contare un ascolto di bohemian. In stile «ask»:
# Stile "ask": leggo, calcolo fuori, riscrivo dentro
attuali = bohemian.get_riproduzioni()
bohemian.set_riproduzioni(attuali + 1)
Funziona, ma c’è qualcosa che non va: la logica «riprodurre significa +1 ascolto» vive fuori dal brano, in chi lo usa. Il Brano è trattato come un sacco di variabili da cui pescare e su cui riscrivere — esattamente la situazione di partenza, solo con due passi di cerimonia in più. Il principio che mette ordine si chiama tell, don’t ask (dì, non chiedere): invece di interrogare l’oggetto per agire sui suoi dati da fuori, dici all’oggetto cosa fare, e la logica resta dentro la classe che possiede lo stato.
Nota che __riproduzioni ha un getter ma nessun setter: dall’esterno lo si può leggere, ma l’unico modo per farlo crescere è riproduci(). Nessuno può falsificare il conteggio degli ascolti scrivendolo a mano. E se domani la regola cambia — un ascolto sotto i 30 secondi non conta, oppure ogni riproduzione aggiorna anche un timestamp — modifichi un solo posto, dentro riproduci(), e tutto il resto del programma resta felicemente all’oscuro.
Prima e dopo
Nel «prima» il codice cliente parla direttamente con lo stato, e può corromperlo. Nel «dopo» ogni accesso passa per un metodo, che può controllare, validare, registrare. L’oggetto smette di essere un sacco di variabili e diventa un’entità con un contratto. Esattamente quello che Gandalf consiglierebbe: ciò che conta, tienilo segreto e al sicuro.
Hai un attributo privato self.__durata. Dall’esterno scrivi brano.__durata = 9999. Cosa succede?
L’attributo __riproduzioni ha un get_riproduzioni() ma nessun setter pubblico: lo si aggiorna solo con riproduci(). Perché è una buona scelta?
Quale di questi setter si guadagna il posto, invece di essere solo cerimonia inutile?