Chapter 16 Classi e funzioniOra che sappiamo come creare dei nuovi tipi, il passo successivo è scrivere delle funzioni che prendano i tipi personalizzati come parametri e restituiscano dei risultati. In questo capitolo presenterò anche lo “stile di programmazione funzionale” e due nuove tecniche di sviluppo. Il codice degli esempi di questo capitolo è scaricabile dal sito http://thinkpython2.com/code/Time1.py. Le soluzioni degli esercizi si trovano qui: http://thinkpython2.com/code/Time1_soln.py. 16.1 TempoFacciamo un altro esempio di tipo personalizzato, creato dal programmatore, e definiamo una classe chiamata Tempo che permette di rappresentare un’ora del giorno: class Tempo: """Rappresenta un'ora del giorno. attributi: ora, minuto, secondo """ Possiamo creare un nuovo oggetto Tempo, assegnandogli tre attributi per le ore, i minuti e i secondi: tempo = Tempo() tempo.ora = 11 tempo.minuto = 59 tempo.secondo = 30 Il diagramma di stato dell’oggetto Tempo è riportato in Figura 16.1. Provate ora a scrivete una funzione di nome Scrivete poi una funzione booleana 16.2 Funzioni pureNei prossimi paragrafi scriveremo due funzioni che sommano dei valori, espressi in termini temporali. Illustreremo così due tipi di funzioni: le funzioni pure e i modificatori. Dimostreremo anche una tecnica di sviluppo che chiameremo prototipo ed evoluzioni, che è un modo di affrontare un problema complesso partendo da un prototipo semplice e trattando poi in maniera incrementale gli aspetti di maggior complessità. Ecco un semplice prototipo della funzione def somma_tempo(t1, t2): somma = Tempo() somma.ora = t1.ora + t2.ora somma.minuto = t1.minuto + t2.minuto somma.secondo = t1.secondo + t2.secondo return somma La funzione crea un nuovo oggetto Tempo, ne inizializza gli attributi, e restituisce un riferimento al nuovo oggetto. Questa è detta funzione pura, perché non modifica alcuno degli oggetti che le vengono passati come argomento e, oltre a restituire un valore, non ha effetti visibili come visualizzare valori o chiedere input all’utente. Per provare questa funzione, creiamo due oggetti Tempo: inizio che contiene l’ora di inizio di un film, come I Monty Python e il Sacro Graal, e durata che contiene la durata del film, che è un’ora e 35 minuti.
>>> inizio = Tempo() >>> inizio.ora = 9 >>> inizio.minuto = 45 >>> inizio.secondo = 0 >>> durata = Tempo() >>> durata.ora = 1 >>> durata.minuto = 35 >>> durata.secondo = 0 >>> fine = somma_tempo(inizio, durata) >>> stampa_tempo(fine) 10:80:00 Il risultato, 10:80:00 non è soddisfacente. Il problema è che questa funzione non gestisce correttamente i casi in cui la somma dei minuti e dei secondi equivale o supera sessanta. Quando questo accade, dobbiamo “riportare” i 60 secondi come minuto ulteriore, o i 60 minuti come ora ulteriore. Ecco allora una versione migliorata della funzione: def somma_tempo(t1, t2): somma = Tempo() somma.ora = t1.ora + t2.ora somma.minuto = t1.minuto + t2.minuto somma.secondo = t1.secondo + t2.secondo if somma.secondo >= 60: somma.secondo -= 60 somma.minuto += 1 if somma.minuto >= 60: somma.minuto -= 60 somma.ora += 1 return somma Sebbene questa funzione sia corretta, comincia ad essere lunga. Tra poco vedremo un’alternativa più concisa. 16.3 ModificatoriCi sono casi in cui è utile che una funzione possa modificare gli oggetti che assume come parametri. I cambiamenti risulteranno visibili anche al chiamante. Funzioni che si comportano in questo modo sono dette modificatori. incremento, che aggiunge un dato numero di secondi ad un oggetto Tempo, può essere scritta intuitivamente come modificatore. Ecco un primo abbozzo della funzione: def incremento(tempo, secondi): tempo.secondo += secondi if tempo.secondo >= 60: tempo.secondo -= 60 tempo.minuto += 1 if tempo.minuto >= 60: tempo.minuto -= 60 tempo.ora += 1 La prima riga esegue l’operazione di addizione fondamentale, mentre le successive controllano i casi particolari che abbiamo già visto prima. Questa funzione è corretta? Cosa succede se secondi è molto più grande di 60? In questo caso non è più sufficiente un unico riporto tra secondi e minuti: dobbiamo fare in modo di ripetere il controllo più volte, finché tempo.secondo diventa minore di 60. Allora, una possibile soluzione è quella di sostituire le istruzioni if con delle istruzioni while. Questo renderebbe la funzione corretta, ma non molto efficiente. Come esercizio, scrivete una versione corretta di incremento che non contenga alcun ciclo. Tutto quello che può essere fatto con i modificatori può anche essere fatto con le funzioni pure. Tanto è vero che alcuni linguaggi di programmazione prevedono unicamente l’uso di funzioni pure. Si può affermare che i programmi che utilizzano funzioni pure sono più veloci da sviluppare e meno soggetti ad errori rispetto a quelli che fanno uso dei modificatori. Ma in qualche caso i modificatori convengono, perché i programmi funzionali risultano meno efficienti. In linea generale, raccomando di usare funzioni pure quando possibile e usare i modificatori solo se c’è un evidente vantaggio nel farlo. Questo tipo di approccio può essere definito stile di programmazione funzionale. Per esercizio, scrivete una versione “pura” di incremento che crei e restituisca un nuovo oggetto Tempo anziché modificare il parametro. 16.4 Sviluppo prototipale e Sviluppo pianificatoLa tecnica di sviluppo del programma che sto illustrando in questo Capitolo è detta “prototipo ed evoluzioni”: per ogni funzione, si inizia scrivendo una versione grezza (prototipo) che effettui solo i calcoli fondamentali, provandola e via via migliorandola e correggendo gli errori. Sebbene questo approccio possa essere abbastanza efficace, specie se non avete una adeguata conoscenza del problema, può condurre a scrivere del codice inutilmente complesso (perché deve affrontare molti casi particolari) e poco affidabile (dato che è difficile essere certi che tutti gli errori siano stati rimossi). Un’alternativa è lo sviluppo pianificato, nel quale una conoscenza approfondita degli aspetti del problema da affrontare rende la programmazione molto più semplice. Nel nostro caso, questa conoscenza sta nel fatto che l’oggetto Tempo è rappresentabile da un numero a tre cifre in base numerica 60! (vedere http://it.wikipedia.org/wiki/Sistema_sessagesimale.) L’attributo secondo è la “colonna delle unità”, l’attributo minuto è la “colonna delle sessantine”, e l’attributo ora quella della “trecentosessantine”. Quando abbiamo scritto Questa osservazione ci suggerisce un altro tipo di approccio al problema: possiamo convertire l’oggetto Tempo in un numero intero e approfittare della capacità del computer di effettuare operazioni sui numeri interi. Questa funzione converte Tempo in un intero: def tempo_in_int(tempo): minuti = tempo.ora * 60 + tempo.minuto secondi = minuti * 60 + tempo.secondo return secondi E questa è la funzione inversa, che converte un intero in un Tempo (ricordate che divmod divide il primo argomento per il secondo e restituisce una tupla che contiene il quoziente e il resto). def int_in_tempo(secondi): tempo = Tempo() minuti, tempo.secondo = divmod(secondi, 60) tempo.ora, tempo.minuto = divmod(minuti, 60) return tempo Per convincervi della esattezza di queste funzioni, pensateci un po’ su e fate qualche prova. Una maniera di collaudarle è controllare che Quando vi siete convinti, potete usarle per riscrivere def somma_tempo(t1, t2): secondi = tempo_in_int(t1) + tempo_in_int(t2) return int_in_tempo(secondi) Questa versione è più concisa dell’originale e più facile da verificare. Come esercizio, riscrivete incremento usando Sicuramente, la conversione numerica da base 60 a base 10 e viceversa è più astratta e meno immediata rispetto al lavoro diretto con i tempi, che è istintivamente migliore. Ma avendo l’intuizione di trattare i tempi come numeri in base 60, e investendo il tempo necessario per scrivere le funzioni di conversione ( Risulta anche più semplice aggiungere nuove caratteristiche, in un secondo tempo. Ad esempio, immaginate di dover sottrarre due Tempi per determinare l’intervallo trascorso. L’approccio iniziale avrebbe reso necessaria l’implementazione di una sottrazione con il prestito. Invece, con le funzioni di conversione, è molto più facile e rapido avere un programma corretto. Paradossalmente, qualche volta rendere un problema più difficile (o più generale) lo rende più semplice, perché ci sono meno casi particolari da gestire e minori possibilità di errore. 16.5 DebugUn oggetto Tempo è ben impostato se i valori di minuto e secondo sono compresi tra 0 e 60 (zero incluso ma 60 escluso) e se ora è positiva. ora e minuto devono essere interi, ma potremmo anche permettere a secondo di avere una parte decimale. Requisiti come questi sono detti invarianti perché devono essere sempre soddisfatti. In altre parole, se non sono soddisfatti significa che qualcosa non è andato per il verso giusto. Scrivere del codice per controllare le invarianti può servire a trovare errori e a identificarne le cause. Per esempio, potete scrivere una funzione
def tempo_valido(tempo): if tempo.ora < 0 or tempo.minuto < 0 or tempo.secondo < 0: return False if tempo.minuto >= 60 or tempo.secondo >= 60: return False return True All’inizio di ogni funzione, potete controllare l’argomento per assicurarvi della sua validità: def somma_tempo(t1, t2): if not tempo_valido(t1) or not tempo_valido(t2): raise ValueError, 'oggetto Tempo non valido in somma_tempo' secondi = tempo_in_int(t1) + tempo_in_int(t2) return int_in_tempo(secondi) Oppure potete usare un’istruzione assert, che controlla una data invariante e solleva un’eccezione in caso di difetti: def somma_tempo(t1, t2): assert tempo_valido(t1) and tempo_valido(t2) secondi = tempo_in_int(t1) + tempo_in_int(t2) return int_in_tempo(secondi) Le istruzioni assert sono utili perché permettono di distinguere il codice che tratta le condizioni normali da quello che controlla gli errori. 16.6 Glossario
16.7 EserciziIl codice degli esempi di questo capitolo è scaricabile dal sito http://thinkpython2.com/code/Time1.py; le soluzioni degli esercizi si trovano in http://thinkpython2.com/code/Time1_soln.py.
Esercizio 1 Scrivete una funzione di nome Usate poi
Esercizio 2
Il modulo datetime fornisce l’oggetto time, simile all’oggetto Tempo di questo capitolo, ma che contiene un ricco insieme di metodi e operatori. Leggetene la documentazione sul sito http://docs.python.org/3/library/datetime.html.
Soluzione: http://thinkpython2.com/code/double.py |
ContributeIf you would like to make a contribution to support my books, you can use the button below. Thank you!
Are you using one of our books in a class?We'd like to know about it. Please consider filling out this short survey.
|