Programmare Python FULL A4
Programmare Python FULL A4
Allen Downey
Jeffrey Elkner
Chris Meyers
Edizioni:
Di David Beazley
Inoltre non voglio sprecare mezzo semestre a risolvere oscuri problemi di sintassi,
cercando di capire messaggi del compilatore generalmente incomprensibili o di
vi Introduzione
Di Jeff Elkner
Questo libro deve la sua esistenza alla collaborazione resa possibile da Internet
e dal movimento free software. I suoi tre autori, un professore universitario,
un docente di scuola superiore ed un programmatore professionista, non si sono
ancora incontrati di persona, ma ciononostante sono riusciti a lavorare insieme
a stretto contatto, aiutati da molte persone che hanno donato il proprio tempo
e le loro energie per rendere questo libro migliore.
Noi pensiamo che questo libro rappresenti la testimonianza dei benefici e delle
future possibilità di questo tipo di collaborazione, la cui struttura è stata creata
da Richard Stallman e dalla Free Software Foundation.
zione reale più solida della mia. Il libro rimase incompiuto per buona parte
dell’anno, finché la comunità open source ancora una volta fornı̀ i mezzi per il
suo completamento.
Ricevetti un’email da Chris Meyers che esprimeva interesse per il libro. Chris
è un programmatore professionista che aveva iniziato a tenere un corso di pro-
grammazione con Python l’anno precedente presso il Lane Community College
di Eugene, Oregon. La prospettiva di tenere il corso aveva portato il libro alla
conoscenza di Chris, cosı̀ che quest’ultimo cominciò ad aiutarci immediatamente.
Prima della fine dell’anno aveva creato un progetto parallelo chiamato Python
for Fun sul sito http://www.ibiblio.org/obp e stava lavorando con alcuni dei
miei studenti più avanzati guidandoli dove io non avrei potuto portarli.
void main()
{
cout << "Hello, World!" << endl;
}
Nella versione Python diventa:
print "Hello, World!"
I vantaggi di Python saltano subito all’occhio anche in questo esempio banale.
Il corso di informatica I a Yorktown non necessita di prerequisiti, cosı̀ molti
studenti vedendo questo esempio stanno in realtà guardando il loro primo pro-
gramma. Qualcuno di loro è sicuramente un po’ nervoso, avendo saputo che
la programmazione è difficile da imparare. La versione in C++ mi ha sempre
costretto a scegliere tra due opzioni ugualmente insoddisfacenti: o spiegare le
istruzioni #include, void main(), { e }, rischiando di intimidire e mettere in
confusione qualcuno degli studenti già dall’inizio, o dire loro “Non preoccupa-
tevi di questa roba per adesso; ne parleremo più avanti” e rischiare di ottenere
lo stesso risultato. Gli obiettivi a questo punto del corso sono quelli di intro-
durre gli studenti all’idea di istruzione di programma e di portarli a scrivere il
loro primo programma, cosı̀ da introdurli nell’ambiente della programmazione.
Python ha esattamente ciò che è necessario per fare questo e niente di più.
Confrontare il testo esplicativo del programma in ognuna delle due versioni del
libro illustra ulteriormente ciò che questo significa per lo studente alle prime ar-
mi: ci sono tredici paragrafi nella spiegazione di “Hello, world!” nella versione
C++ e solo due in quella Python. Da notare che gli undici paragrafi aggiuntivi
x Prefazione
Jeffrey Elkner
Yorktown High School
Arlington, Virginia
Lista dei collaboratori
Questo libro è nato grazie ad una collaborazione che non sarebbe stata pos-
sibile senza la GNU Free Documentation License. Vorremmo ringraziare la
Free Software Foundation per aver sviluppato questa licenza e per avercela resa
disponibile.
Vorremmo anche ringraziare il centinaio di lettori che ci hanno spedito sugge-
rimenti e correzioni nel corso degli ultimi due anni. Nello spirito del software
libero abbiamo deciso di esprimere la nostra gratitudine aggiungendo la lista dei
collaboratori. Sfortunatamente la lista non è completa, ma stiamo facendo del
nostro meglio per tenerla aggiornata.
Se avrai modo di scorrere lungo la lista riconoscerai che ognuna di queste persone
ha risparmiato a te e agli altri lettori la confusione derivante da errori tecnici o
da spiegazioni non troppo chiare.
Anche se sembra impossibile dopo cosı̀ tante correzioni, ci possono essere ancora
degli errori in questo libro. Se per caso dovessi trovarne uno, speriamo tu possa
spendere un minuto per farcelo sapere. L’indirizzo email al quale comunicarcelo
è [email protected]. Se faremo qualche cambiamento a seguito del
tuo suggerimento anche tu sarai inserito nella lista dei collaboratori, sempre che
tu non chieda altrimenti. Grazie!
• Lee Harr, per aver sottoposto una serie di correzioni che sarebbe troppo
lungo esporre qui. Dovrebbe essere citato come uno dei maggiori revisori
del libro.
• James Kaylin è uno studente che ha usato il libro ed ha sottoposto nume-
rose correzioni.
• David Kershaw, per aver reso funzionante del codice nella sezione 3.10.
• Eddie Lam, per aver spedito numerose correzioni ai primi tre capitoli, per
aver sistemato il makefile cosı̀ da creare un indice alla prima compilazione
e per averci aiutato nella gestione delle versioni.
• Man-Yong Lee, per aver spedito una correzione al codice di esempio nella
sezione 2.4.
• David Mayo, per una correzione grammaticale al capitolo 1.
• Chris McAloon, per le correzioni nelle sezioni 3.9 e 3.10.
• Matthew J. Moelter, per essere stato uno dei collaboratori al progetto, e
per aver contribuito con numerose correzioni e commenti.
• Simon Dicon Montford, per aver fatto notare una mancata definizione
di funzione e numerosi errori di battitura nel capitolo 3 e per aver aver
trovato gli errori nella funzione Incrementa nel capitolo 13.
• Michael Schmitt, per una correzione nel capitolo sui file e le eccezioni.
• Robin Shaw, per aver trovato un errore nella sezione 13.1 dove una fun-
zione veniva usata senza essere stata preventivamente definita.
• Paul Sleigh, per aver trovato un errore nel capitolo 7, ed un altro nello
script Perl per la generazione dell’HTML.
• Craig T. Snydal, che sta usando il testo in un corso alla Drew University.
Ha contribuito con numerosi suggerimenti e correzioni.
• Ian Thomas ed i suoi studenti che hanno usato il testo in un corso di
programmazione. Sono stati i primi a controllare i capitoli nella seconda
parte del libro, fornendo numerose correzioni ed utili suggerimenti.
• Hayden McAfee, per aver notato una potenziale incoerenza tra due esempi.
• Dr. Michele Alzetta, per aver corretto un errore nel capitolo 8 e aver
inviato una serie di utili commenti e suggerimenti concernenti Fibonacci
e Old Maid.
Di Alessandro Pocaterra
Una nota che invece riguarda la notazione numerica. Chiunque abbia mai preso
in mano una calcolatrice si sarà accorto che la virgola dei decimali tanto cara
alla nostra maestra delle elementari si è trasformata in un punto. Naturalmente
questo cambio non è casuale: nei paesi anglosassoni l’uso di virgola e punto
nei numeri è esattamente l’opposto di quello cui siamo abituati: se per noi ha
senso scrivere 1.234.567,89 (magari con il punto delle migliaia in alto), in ingle-
se questo numero viene scritto come 1,234,567.89. In informatica i separatori
delle migliaia sono di solito trascurati e per la maggior parte dei linguaggi di
programmazione considerati illegali: per il nostro computer lo stesso numero
sarà quindi 1234567.89. Un po’ di pratica e ci si fa l’abitudine. In relazione al
codice presente nel testo, per non uscire dai margini del documento, sono state
spezzate le righe che davano problemi con l’inserimento del carattere \ come
fine riga. Siete quindi fin d’ora avvertiti che, ove trovaste quel carattere, in
realtà la riga andrebbe scritta comprendendo anche quella successiva. In altri
casi piuttosto evidenti è stato omesso il carattere \.
xviii Note sulla traduzione
Ringraziamenti
Naturalmente ringrazio i tre autori del testo originale Allen Downey, Jeffrey
Elkner e Chris Meyers, senza i quali questo libro non avrebbe mai visto la luce.
Devo ringraziare per l’aiuto mia moglie Sara che si è volonterosamente prestata
alla rilettura e correzione del libro. Ringrazio in modo particolare Ferdinando
Ferranti che si è prodigato nella revisione, ma soprattutto nel rivedere il codice
LaTeX in ogni sua parte, aiutandomi a suddividere il documento cosı̀ come
nell’originale, correggendo gli errori di compilazione che il codice restituiva e
realizzando cosı̀ anche una versione HTML funzionante. Oltre a questo ha
anche modificato l’impaginazione ed il Makefile ottenendo cosı̀ una versione del
documento la cui stampa è più funzionale rispetto all’originale, pensato per
formati di carta differenti. Ringrazio anche Nicholas Wieland, “Pang” e Nicola
La Rosa per il loro aiuto insostituibile in fase di revisione. Ringrazio tutti
quelli, Dario Cavedon e Giovanni Panozzo in testa, che mi hanno fatto scoprire
il mondo Linux, il Free Software e la Free Documentation. Un ringraziamento
particolare a tutti quelli che si sono rimboccati le maniche ed hanno dato vita
a quell’incredibile strumento che è LaTeX .
La traduzione di questo libro è stata un passatempo ed un divertimento. Dato
che sicuramente qualcosa non gira ancora come dovrebbe, vi chiedo di mandarmi
i vostri commenti a riguardo all’indirizzo [email protected]. In caso di
refusi o imprecisioni ricordate di citare sempre la pagina e la versione di questo
documento (versione 1.0b).
Nelle versioni successive si cercherà per quanto possibile di tenere il passo con
la bibliografia: qualsiasi indicazione al riguardo sarà sempre bene accetta.
Buona fortuna!
Alessandro Pocaterra
Indice
Introduzione v
Prefazione vii
1 Imparare a programmare 1
1.1 Il linguaggio di programmazione Python . . . . . . . . . . . . . 1
1.2 Cos’è un programma? . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Cos’è il debug? . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 Linguaggi formali e naturali . . . . . . . . . . . . . . . . . . . . 6
1.5 Il primo programma . . . . . . . . . . . . . . . . . . . . . . . . 8
1.6 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Funzioni 21
3.5 Composizione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.13 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.9 Ricorsione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.13 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Indice xxi
5 Funzioni produttive 45
5.1 Valori di ritorno . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.2 Sviluppo del programma . . . . . . . . . . . . . . . . . . . . . . 46
5.3 Composizione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.4 Funzioni booleane . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.5 Ancora ricorsione . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.6 Accettare con fiducia . . . . . . . . . . . . . . . . . . . . . . . . 52
5.7 Un esempio ulteriore . . . . . . . . . . . . . . . . . . . . . . . . 53
5.8 Controllo dei tipi . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.9 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6 Iterazione 57
6.1 Assegnazione e confronto . . . . . . . . . . . . . . . . . . . . . . 57
6.2 L’istruzione while . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.3 Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.4 Tabelle bidimensionali . . . . . . . . . . . . . . . . . . . . . . . 62
6.5 Incapsulamento e generalizzazione . . . . . . . . . . . . . . . . 62
6.6 Ancora incapsulamento . . . . . . . . . . . . . . . . . . . . . . . 63
6.7 Variabili locali . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.8 Ancora generalizzazione . . . . . . . . . . . . . . . . . . . . . . 64
6.9 Funzioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.10 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
7 Stringhe 69
7.1 Tipi di dati composti . . . . . . . . . . . . . . . . . . . . . . . . 69
7.2 Lunghezza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.3 Elaborazione trasversale e cicli for . . . . . . . . . . . . . . . . 70
7.4 Porzioni di stringa . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.5 Confronto di stringhe . . . . . . . . . . . . . . . . . . . . . . . . 72
7.6 Le stringhe sono immutabili . . . . . . . . . . . . . . . . . . . . 72
7.7 Funzione Trova . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.8 Cicli e contatori . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.9 Il modulo string . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.10 Classificazione dei caratteri . . . . . . . . . . . . . . . . . . . . 75
7.11 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
xxii Indice
8 Liste 77
8.11 Alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.15 Matrici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
8.17 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
9 Tuple 89
9.6 Conteggio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
9.9 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Indice xxiii
10 Dizionari 97
16 Ereditarietà 149
16.1 Ereditarietà . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
16.2 Una mano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
16.3 Distribuire le carte . . . . . . . . . . . . . . . . . . . . . . . . . 151
16.4 Stampa di una mano . . . . . . . . . . . . . . . . . . . . . . . . 151
16.5 La classe GiocoDiCarte . . . . . . . . . . . . . . . . . . . . . . 152
16.6 Classe ManoOldMaid . . . . . . . . . . . . . . . . . . . . . . . . 153
16.7 Classe GiocoOldMaid . . . . . . . . . . . . . . . . . . . . . . . . 155
16.8 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
18 Pile 169
18.1 Tipi di dati astratti . . . . . . . . . . . . . . . . . . . . . . . . . 169
18.2 Il TDA Pila . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
18.3 Implementazione delle pile con le liste di Python . . . . . . . . 170
18.4 Push e Pop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
18.5 Uso della pila per valutare espressioni postfisse . . . . . . . . . 171
18.6 Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
18.7 Valutazione postfissa . . . . . . . . . . . . . . . . . . . . . . . . 173
18.8 Clienti e fornitori . . . . . . . . . . . . . . . . . . . . . . . . . . 174
18.9 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
xxvi Indice
19 Code 175
20 Alberi 183
A Debug 195
Imparare a programmare
veloci da scrivere, più corti e facilmente leggibili, ed è più probabile che siano
corretti. In secondo luogo i linguaggi di alto livello sono portabili: portabilità
significa che essi possono essere eseguiti su tipi di computer diversi con poche
o addirittura nessuna modifica. I programmi scritti in linguaggi di basso livello
possono essere eseguiti solo su un tipo di computer e devono essere riscritti per
essere trasportati su un altro sistema.
Questi vantaggi sono cosı̀ evidenti che quasi tutti i programmi sono scritti in
linguaggi di alto livello, lasciando spazio ai linguaggi di basso livello solo in
poche applicazioni specializzate.
I programmi di alto livello vengono trasformati in programmi di basso livello
eseguibili dal computer tramite due tipi di elaborazione: l’interpretazione e
la compilazione. Un interprete legge il programma di alto livello e lo esegue,
trasformando ogni riga di istruzioni in un’azione. L’interprete elabora il pro-
gramma un po’ alla volta, alternando la lettura delle istruzioni all’esecuzione
dei comandi che le istruzioni descrivono:
La terza riga inizia con >>>: questa è l’indicazione (chiamata “prompt”) che
l’interprete usa per indicare la sua disponibilità ad accettare comandi. Noi
abbiamo inserito print 1 + 1 e l’interprete ha risposto con 2.
In alternativa alla riga di comando si può scrivere un programma in un file (detto
script) ed usare l’interprete per eseguire il contenuto del file. Nell’esempio
seguente abbiamo usato un editor di testi per creare un file chiamato pippo.py:
print 1 + 1
Per convenzione, i file contenenti programmi Python hanno nomi che terminano
con .py.
Per eseguire il programma dobbiamo dire all’interprete il nome dello script:
$ python pippo.py
2
Che ci si creda o meno, questo è più o meno tutto quello che c’è. Ogni program-
ma che hai usato per quanto complesso possa sembrare (anche il tuo videogioco
preferito) è costituito da istruzioni che assomigliano a queste. Possiamo af-
fermare che la programmazione altro non è che la suddivisione di un compito
grande e complesso in una serie di sotto-compiti via via più piccoli, finché que-
sti sono sufficientemente semplici da essere eseguiti da una di queste istruzioni
fondamentali.
Questo concetto può sembrare un po’ vago, ma lo riprenderemo quando parle-
remo di algoritmi.
Gli errori in esecuzione sono rari nei semplici programmi che vedrai nei primis-
simi capitoli, cosı̀ potrebbe passare un po’ di tempo prima che tu ne incontri
uno.
Come esercizio crea quella che può sembrare una frase in italiano
con dei token non riconoscibili. Poi scrivi un’altra frase con tutti i
token validi ma con una struttura non valida.
Poesia: le parole sono usate tanto per il loro suono che per il loro significa-
to, e la poesia nel suo complesso crea un effetto o una risposta emotiva.
L’ambiguità è non solo frequente, ma spesso addirittura cercata.
• Ricorda che i linguaggi formali sono molto più ricchi di significato dei
linguaggi naturali, cosı̀ è necessario più tempo per leggerli e comprenderli.
Hello, World!
1.6 Glossario
Soluzione di problemi: il processo di formulare un problema, trovare una
soluzione ed esprimerla.
Eseguibile: altro nome per indicare il codice oggetto pronto per essere eseguito.
Linguaggio formale: ognuno dei linguaggi che sono stati progettati per scopi
specifici, quali la rappresentazione di idee matematiche o programmi per
computer (tutti i linguaggi per computer sono linguaggi formali).
Variabili, espressioni ed
istruzioni
>>> type("17")
<type ’string’>
>>> type("3.2")
<type ’string’>
Quando scrivi numeri grandi puoi essere tentato di usare dei punti per delimitare
i gruppi di tre cifre, come in 1.000.000. Questa in effetti non è una cosa
consentita in Python ed il valore numerico in questo caso non è valido. È invece
corretta una scrittura del tipo
>>> print 1,000,000
1 0 0
...anche se probabilmente questo risultato non è quello che ci si aspettava! Py-
thon interpreta 1,000,000 come una lista di tre valori da stampare (1, 0 e 0).
Ricordati di non inserire virgole nei tuoi interi.
2.2 Variabili
Una delle caratteristiche più potenti in un linguaggio di programmazione è la
capacità di manipolare variabili. Una variabile è un nome che si riferisce ad
un valore.
L’istruzione di assegnazione crea nuove variabili e assegna loro un valore:
>>> messaggio = "Come va?"
>>> n = 17
>>> pi = 3.14159
Questo esempio effettua tre assegnazioni. La prima assegna la stringa Come va?
ad una nuova variabile chiamata messaggio. La seconda assegna l’intero 17 alla
variabile n e la terza assegna il valore in virgola mobile 3.14159 alla variabile
pi.
Un modo comune di rappresentare le variabili sulla carta è scriverne il nome con
una freccia che punta al valore della variabile. Questo tipo di figura è chiamato
diagramma di stato perché mostra lo stato in cui si trova la variabile. Questo
diagramma mostra il risultato dell’istruzione di assegnazione:
>>> type(message)
<type ’string’>
>>> type(n)
<type ’int’>
>>> type(pi)
<type ’float’>
76strumenti è illegale perché non inizia con una lettera. milione$ è illegale
perché contiene un carattere non valido (il segno di dollaro $). Ma cosa c’è di
sbagliato in class?
class è una delle parole riservate di Python. Le parole riservate definiscono
le regole del linguaggio e della struttura e non possono essere usate come nomi
di variabili.
Python ha 28 parole riservate:
14 Variabili, espressioni ed istruzioni
2.4 Istruzioni
Un’istruzione è un’operazione che l’interprete Python può eseguire. Abbiamo
già visto due tipi di istruzioni: istruzioni di stampa 1 e di assegnazione.
Quando scrivi un’istruzione sulla riga di comando, Python la esegue e se pre-
visto stampa il risultato a video. Un’istruzione di assegnazione di per sé non
produce risultati visibili mentre il risultato di un’istruzione di stampa è un valore
mostrato a video.
Uno script di solito contiene una sequenza di istruzioni: se sono presenti più
istruzioni i loro risultati appariranno via via che le singole istruzioni saranno
eseguite.
Per esempio lo script:
print 1
x = 2
print x
produce questa stampa:
1
2
17
3.2
"Hello, World!"
1 + 1
>>> minuti = 59
>>> minuti/60
0
16 Variabili, espressioni ed istruzioni
Quando entrambi gli operandi sono numeri interi il risultato è sempre un numero
intero e per convenzione la divisione tra numeri interi restituisce sempre un
numero arrotondato all’intero inferiore (arrotondamento verso il basso), anche
nel caso in cui il risultato sia molto vicino all’intero superiore.
Una possibile soluzione a questo problema potrebbe essere il calcolo della per-
centuale, piuttosto che del semplice valore decimale:
>>> minuti*100/60
98
• Gli operatori con la stessa priorità sono valutati da sinistra verso destra,
cosı̀ che nell’espressione minuti*100/60, la moltiplicazione è valutata per
prima, ottenendo 5900/60, che a sua volta restituisce 98. Se le opera-
zioni fossero state valutate da destra a sinistra il risultato sarebbe stato
sbagliato: 59*1=59.
2.8 Operazioni sulle stringhe 17
frutta = "banana"
verdura = " pomodoro"
print frutta + verdura
Anche l’operatore * lavora sulle stringhe pur con un significato diverso rispetto
a quello matematico: infatti causa la ripetizione della stringa. Per fare un
esempio, "Casa"*3 è "CasaCasaCasa". Uno degli operandi deve essere una
stringa, l’altro un numero intero.
2.9 Composizione
Finora abbiamo guardato agli elementi di un programma (variabili, espressioni
e istruzioni) prendendoli isolatamente, senza parlare di come combinarli.
Sappiamo già sommare e stampare dei numeri e possiamo fare le due operazioni
nello stesso momento:
>>> print 17 + 3
20
18 Variabili, espressioni ed istruzioni
In realtà l’addizione è stata portata a termine prima della stampa, cosı̀ che le
due operazioni non stanno avvenendo contemporaneamente. Qualsiasi opera-
zione che ha a che fare con i numeri, le stringhe e le variabili può essere usata
all’interno di un’istruzione di stampa. Hai già visto un esempio a riguardo:
2.10 Commenti
Man mano che il programma cresce di dimensioni diventa sempre più difficile
da leggere. I linguaggi formali sono ricchi di significato, e può risultare difficile
capire a prima vista cosa fa un pezzo di codice o perché è stato scritto in un
certo modo.
Per questa ragione è una buona idea aggiungere delle note ai tuoi programmi
per spiegare con un linguaggio naturale cosa sta facendo il programma nelle sue
varie parti. Queste note sono chiamate commenti, e sono marcati dal simbolo
#:
In questo caso il commento appare come una linea a sé stante. Puoi eventual-
mente inserire un commento alla fine di una riga:
Qualsiasi cosa scritta dopo il simbolo # e fino alla fine della riga viene trascurata
nell’esecuzione del programma. Il commento serve al programmatore o ai futuri
programmatori che dovranno usare questo codice. In questo ultimo esempio il
commento ricorda al lettore che ci potrebbe essere un comportamento inatteso
dovuto all’uso della divisione tra numeri interi.
2.11 Glossario
Valore: numero o stringa (o altri tipi di dato che vedremo in seguito) che può
essere memorizzato in una variabile o usato in una espressione.
2.11 Glossario 19
Tipo: formato di un valore che determina come esso possa essere usato nelle
espressioni. Finora hai visto i numeri interi (tipo int), i numeri in virgola
mobile (tipo float) e le stringhe (tipo string).
Virgola mobile: formato di dati che rappresenta i numeri con parte decimale;
è anche detto “floating-point”.
Divisione tra numeri interi: operazione che divide un numero intero per un
altro intero.
Funzioni
>>> type("32")
<type ’string’>
>>> id(3)
134882108
>>> betty = 3
>>> id(betty)
134882108
>>> minuti = 59
>>> minuti / 60.0
0.983333333333
Per chiamare una funzione di un modulo dobbiamo specificare il nome del mo-
dulo che la contiene e il nome della funzione separati da un punto. Questo
formato è chiamato notazione punto.
>>> gradi = 45
>>> angolo = gradi * 2 * math.pi / 360.0
>>> math.sin(angolo)
3.5 Composizione
Cosı̀ come in matematica anche in Python le funzioni possono essere composte,
facendo in modo che il risultato di una possa essere usato come argomento di
un’altra:
>>> x = math.exp(math.log(10.0))
Puoi usare qualsiasi nome per una funzione, fatta eccezione per le parole riserva-
te di Python. La lista dei parametri di una funzione specifica quali informazioni,
sempre che ne sia prevista qualcuna, desideri fornire alla funzione per poterla
usare.
All’interno della funzione sono naturalmente presenti delle istruzioni e queste
devono essere indentate rispetto al margine sinistro. Di solito il rientro è di
un paio di spazi, ma questa è solo una convenzione: per questioni puramente
estetiche potresti volerne usare di più. Mentre nella maggior parte dei linguaggi
il rientro è facoltativo e dipende da come il programmatore vuole organizzare
visivamente il suo codice, in Python il rientro è obbligatorio. Questa scelta
può sembrare un vincolo forzoso, ma ha il vantaggio di garantire una certa
uniformità di stile e per quanto disordinato possa essere un programmatore il
codice conserverà sempre un minimo di ordine.
La prima coppia di funzioni che stiamo per scrivere non ha parametri e la sintassi
è:
def UnaRigaVuota():
print
Prima riga.
Seconda riga.
Nota lo spazio tra le due righe. Cosa avresti dovuto fare se c’era bisogno di più
spazio? Ci sono varie possibilità. Avresti potuto chiamare più volte la funzione:
o avresti potuto creare una nuova funzione chiamata TreRigheVuote che stampa
tre righe vuote:
def TreRigheVuote():
UnaRigaVuota()
26 Funzioni
UnaRigaVuota()
UnaRigaVuota()
Questa funzione contiene tre istruzioni, tutte indentate di due spazi proprio
per indicare che fanno parte della definizione della funzione. Dato che dopo la
definizione, alla fine del terzo UnaRigaVuota(), la riga successiva,
print "Prima riga." non ha più indentazione, ciò significa che questa non fa
più parte della definizione e che la definizione deve essere considerata conclusa.
Puoi notare alcune cose riguardo questo programma:
2. Una funzione può chiamare altre funzioni al suo interno: in questo caso
TreRigheVuote chiama UnaRigaVuota.
Può non essere ancora chiaro perché sia il caso di creare tutte queste nuove
funzioni. Effettivamente di ragioni ce ne sono tante, qui ne indichiamo due:
def UnaRigaVuota():
print
def TreRigheVuote():
UnaRigaVuota()
UnaRigaVuota()
3.8 Flusso di esecuzione 27
UnaRigaVuota()
Alcune funzioni prendono due o più parametri: pow si aspetta due argomenti
che sono la base e l’esponente in un’operazione di elevamento a potenza. Den-
tro la funzione i valori che sono passati vengono assegnati a variabili chiamate
parametri.
def Stampa2Volte(Valore):
print Valore, Valore
La funzione Stampa2Volte funziona per ogni tipo di dato che può essere stam-
pato:
>>> Stampa2Volte(’Pippo’)
Pippo Pippo
>>> Stampa2Volte(5)
5 5
>>> Stampa2Volte(3.14159)
3.14159 3.14159
Le stesse regole per la composizione che sono state descritte per le funzioni
predefinite valgono anche per le funzioni definite da te, cosı̀ che possiamo usare
una qualsiasi espressione valida come argomento per Stampa2Volte:
3.10 Variabili e parametri sono locali 29
>>> Stampa2Volte("Pippo"*4)
PippoPippoPippoPippo PippoPippoPippoPippo
>>> Stampa2Volte(math.cos(math.pi))
-1.0 -1.0
Una nota per quanto riguarda le stringhe: le stringhe possono essere racchiuse
sia da virgolette "ABC" che da apici ’ABC’. Il tipo di delimitatore NON usato per
delimitare la stringa, l’apice se si usano le virgolette, le virgolette se si usa l’api-
ce, può essere usato all’interno della stringa. Ad esempio sono valide le stringhe
"apice ’ nella stringa" e ’virgoletta " nella stringa’, ma non lo so-
no ’apice ’ nella stringa’ e "virgoletta " nella stringa", dato che in
questo caso l’interprete non riesce a stabilire quale sia il fine stringa desiderato
dal programmatore.
Il nome della variabile che passiamo come argomento (Messaggio) non ha niente
a che fare con il nome del parametro nella definizione della funzione (Valore).
Non ha importanza conoscere il nome originale con cui sono stati identificati i
parametri durante la definizione della funzione.
Questa lista temporale delle chiamate delle funzioni è detta traccia. La traccia
ti dice in quale file è avvenuto l’errore, che riga all’interno del file si stava
eseguendo in quel momento ed il riferimento alla funzione che ha causato l’errore.
Nota che c’è una notevole somiglianza tra traccia e diagramma di stack e questa
somiglianza non è certamente una coincidenza.
1. Cosa succede se chiami una funzione e non fai niente con il risultato che
viene restituito (per esempio non lo assegni ad una variabile e non lo usi
come parte di una espressione)?
2. Cosa succede se usi una funzione che non produce risultato come parte di
un’espressione (per esempio UnaRigaVuota() + 7)?
3.13 Glossario
Chiamata di funzione: istruzione che esegue una funzione. Consiste di un
nome di funzione seguito da una serie di argomenti racchiuso tra parentesi.
Definizione della funzione: istruzioni che creano una nuova funzione, speci-
ficandone il nome, i parametri e le operazioni che essa deve eseguire.
Parametro: nome usato all’interno della funzione per riferirsi al valore passato
come argomento.
Istruzioni condizionali e
ricorsione
>>> Quoziente = 7 / 3
>>> print Quoziente
2
>>> Resto = 7 % 3
>>> print Resto
1
Inoltre può essere usato per estrarre la cifra più a destra di un numero: x%10
restituisce la cifra più a destra in base 10. Allo stesso modo x%100 restituisce
le ultime due cifre.
>>> 5 == 5
1
>>> 5 == 6
0
Nella prima riga i due operandi sono uguali, cosı̀ l’espressione vale 1 (vero); nella
seconda riga 5 e 6 non sono uguali, cosı̀ otteniamo 0 (falso).
L’operatore == è uno degli operatori di confronto; gli altri sono:
x != y # x è diverso da y?
x > y # x è maggiore di y?
x < y # x è minore di y?
x >= y # x è maggiore o uguale a y?
x <= y # x è minore o uguale a y?
Sebbene queste operazioni ti possano sembrare familiari, i simboli Python sono
diversi da quelli usati comunemente in matematica. Un errore comune è quello
di usare il simbolo di uguale (=) invece del doppio uguale (==): ricorda che = è
un operatore di assegnazione e == un operatore di confronto. Inoltre in Python
non esistono simboli del tipo =< e =>, ma solo gli equivalenti <= e >=.
if x > 0:
print "x e’ positivo"
INTESTAZIONE:
PRIMA RIGA DI ISTRUZIONI
...
ULTIMA RIGA DI ISTRUZIONI
L’intestazione inizia su di una nuova riga e termina con il segno di due punti. La
serie di istruzioni indentate che seguono sono chiamate blocco di istruzioni.
La prima riga di istruzioni non indentata marca la fine del blocco di istruzioni
e non ne fa parte. Un blocco di istruzioni all’interno di un’istruzione composta
è anche chiamato corpo dell’istruzione.
Non c’è un limite al numero di istruzioni che possono comparire nel corpo di un’i-
struzione if ma deve sempre essercene almeno una. In qualche occasione può
essere utile avere un corpo vuoto, ad esempio quando il codice corrispondente
non è ancora stato scritto ma si desidera ugualmente poter provare il program-
ma. In questo caso puoi usare l’istruzione pass, che è solo un segnaposto e non
fa niente:
if x > 0:
pass
if x%2 == 0:
print x, "e’ pari"
else:
print x, "e’ dispari"
Se il resto della divisione intera di x per 2 è zero allora sappiamo che x è pari e
il programma mostra il messaggio corrispondente. Se la condizione è falsa viene
36 Istruzioni condizionali e ricorsione
eseguita la serie di istruzioni descritta dopo la riga else (che in inglese significa
“altrimenti”).
def StampaParita(x):
if x%2 == 0:
print x, "e’ pari"
else:
print x, "e’ dispari"
>>> StampaParita(17)
>>> StampaParita(y+1)
if x < y:
print x, "e’ minore di", y
elif x > y:
print x, "e’ maggiore di", y
else:
print x, "e", y, "sono uguali"
elif è l’abbreviazione di “else if”, che in inglese significa “altrimenti se”. Anche
in questo caso solo uno dei rami verrà eseguito, a seconda del confronto tra x
e y. Non c’è alcun limite al numero di istruzioni elif ma è eventualmente
possibile inserire un’unica istruzione else che deve essere l’ultima dell’elenco e
che rappresenta l’azione da eseguire quando nessuna delle condizioni precedenti
è stata soddisfatta. La presenza di un’istruzione else è facoltativa.
if scelta == ’A’:
FunzioneA()
elif scelta == ’B’:
FunzioneB()
elif scelta == ’C’:
FunzioneC()
else:
4.7 Condizioni annidate 37
if x == y:
print x, "e", y, "sono uguali"
else:
if x < y:
print x, "e’ minore di", y
else:
print x, "e’ maggiore di", y
if 0 < x:
if x < 10:
print "x e’ un numero positivo."
Questo tipo di condizione è cosı̀ frequente che Python permette di usare una
forma semplificata che ricorda da vicino quella corrispondente usata in mate-
matica:
A tutti gli effetti i tre esempi sono equivalenti per quanto riguarda la semantica
(il significato) del programma.
import math
def StampaLogaritmo(x):
if x <= 0:
print "Inserire solo numeri positivi!"
return
risultato = math.log(x)
print "Il logaritmo di",x,"e’", risultato
Ricorda che dovendo usare una funzione del modulo math è necessario importare
il modulo.
4.9 Ricorsione
Abbiamo detto che è perfettamente lecito che una funzione ne chiami un’altra e
di questo hai avuto modo di vedere parecchi esempi. Abbiamo invece trascurato
di dirti che è anche lecito che una funzione possa chiamare sé stessa. Può non
essere immediatamente ovvio il motivo per cui questo sia utile, ma questa è una
delle cose più interessanti che un programma possa fare. Per fare un esempio
dai un’occhiata a questa funzione:
def ContoAllaRovescia(n):
if n == 0:
print "Partenza!"
else:
print n
ContoAllaRovescia(n-1)
4.9 Ricorsione 39
>>> ContoAllaRovescia(3)
3
2
1
Partenza!
def UnaRigaVuota():
print
def TreRigheVuote():
UnaRigaVuota()
UnaRigaVuota()
UnaRigaVuota()
def NRigheVuote(n):
if n > 0:
print
NRigheVuote(n-1)
40 Istruzioni condizionali e ricorsione
Come al solito il livello superiore dello stack è il frame per main . Questo
frame è vuoto perché in questo caso non abbiamo creato alcuna variabile locale
e non abbiamo passato alcun parametro.
I quattro frame di ContoAllaRovescia hanno valori diversi per il parametro n.
Il livello inferiore dello stack, quando n=0, è chiamato lo stato di base. Esso
non effettua ulteriori chiamate ricorsive, cosı̀ non ci sono ulteriori frame.
def Ricorsione():
Ricorsione()
Nella maggior parte degli ambienti un programma con una ricorsione infinita
non viene eseguito senza fine, dato che ogni chiamata ad una funzione impegna
un po’ di memoria del computer e questa memoria prima o poi finisce. Python
stampa un messaggio d’errore quando è stato raggiunto il massimo livello di
ricorsione possibile:
File "<stdin>", line 2, in Ricorsione
...
File "<stdin>", line 2, in Ricorsione
RuntimeError: Maximum recursion depth exceeded
Questa traccia è un po’ più lunga di quella che abbiamo visto nel capitolo
precedente. Quando è capitato l’errore c’erano moltissime ricorsioni nello stack.
4.13 Glossario
Operatore modulo: operatore matematico denotato con il segno di percen-
tuale (%) che restituisce il resto della divisione tra due operandi interi.
Espressione booleana: espressione che è o vera o falsa.
Operatore di confronto: uno degli operatori che confrontano due valori: ==,
!=, >, <, >= e <=.
Operatore logico: uno degli operatori che combina le espressioni booleane:
and, or e not.
Istruzione condizionale: istruzione che controlla il flusso di esecuzione del
programma a seconda del verificarsi di certe condizioni.
Condizione: espressione booleana in una istruzione condizionale che determina
quale ramificazione debba essere seguita dal flusso di esecuzione.
Istruzione composta: istruzione che consiste di un’intestazione terminante
con i due punti (:) e di un corpo composto di una o più istruzioni indentate
rispetto all’intestazione.
Blocco: gruppo di istruzioni consecutive con la stessa indentazione.
Corpo: blocco che segue l’intestazione in un’istruzione composta.
Annidamento: particolare struttura di programma interna ad un’altra, co-
me nel caso di una istruzione condizionale inserita all’interno di un’altra
istruzione condizionale.
Ricorsione: richiamo di una funzione che è già in esecuzione.
Stato di base: ramificazione di un’istruzione condizionale posta in una fun-
zione ricorsiva e che non esegue alcuna chiamata ricorsiva.
Ricorsione infinita: funzione che chiama sé stessa ricorsivamente senza mai
raggiungere lo stato di base. L’occupazione progressiva della memoria che
avviene ad ogni successiva chiamata causa ad un certo punto un errore in
esecuzione.
4.13 Glossario 43
Prompt: suggerimento visivo che specifica il tipo di dati atteso come inseri-
mento da tastiera.
Capitolo 5
Funzioni produttive
e = math.exp(1.0)
Altezza = Raggio * math.sin(Angolo)
Nessuna delle funzioni che abbiamo scritto sino a questo momento ha ritornato
un valore.
In questo capitolo scriveremo funzioni che ritornano un valore e che chiamiamo
funzioni produttive. Il primo esempio è AreaDelCerchio che ritorna l’area
di un cerchio per un dato raggio:
import math
def AreaDelCerchio(Raggio):
temp = math.pi * Raggio**2
return temp
Abbiamo già visto l’istruzione return, ma nel caso di una funzione produttiva
questa istruzione prevede un valore di ritorno. Questa istruzione significa:
“ritorna immediatamente da questa funzione a quella chiamante e usa questa
espressione come valore di ritorno”. L’espressione che rappresenta il valore di
ritorno può essere anche complessa, cosı̀ che l’esempio visto in precedenza può
essere riscritto in modo più conciso:
def AreaDelCerchio(raggio):
return math.pi * Raggio**2
D’altra parte una variabile temporanea come temp spesso rende il programma
più leggibile e ne semplifica il debug.
46 Funzioni produttive
def ValoreAssoluto(x):
if x < 0:
return -x
else:
return x
Dato che queste istruzioni return sono in rami diversi della condizione solo una
di esse verrà effettivamente eseguita.
Il codice che è posto dopo un’istruzione return, o in ognuno dei posti dove non
può essere raggiunto dal flusso di esecuzione, è denominato codice morto.
In una funzione produttiva è una buona idea assicurarci che ognuna delle rami-
ficazioni possibili porti ad un’uscita dalla funzione con un’istruzione di return.
Per esempio:
def ValoreAssoluto(x):
if x < 0:
return -x
elif x > 0:
return x
Questo programma non è corretto in quanto non è prevista un’uscita con return
nel caso x sia 0. In questo caso il valore di ritorno è un valore speciale chiamato
None:
distanza = (x2 − x1 )2 + (y2 − y1 )2 (5.1)
Ovviamente questa prima versione non calcola distanze, in quanto ritorna sem-
pre 0. Ma è già una funzione sintatticamente corretta e può essere eseguita: è il
caso di eseguire questo primo test prima di procedere a renderla più complessa.
Per testare la nuova funzione proviamo a chiamarla con dei semplici valori:
>>> DistanzaTraDuePunti(1, 2, 4, 6)
0.0
Abbiamo scelto questi valori cosı̀ che la loro distanza orizzontale è 3 e quella
verticale è 4. Con il teorema di Pitagora è facile vedere che il valore atteso è pari
a 5 (5 è la lunghezza dell’ipotenusa di un triangolo rettangolo i cui cateti sono
3 e 4). Quando testiamo una funzione è sempre utile conoscere il risultato di
qualche caso particolare per verificare se stiamo procedendo sulla strada giusta.
A questo punto abbiamo verificato che la funzione è sintatticamente corretta e
possiamo cosı̀ cominciare ad aggiungere linee di codice. Dopo ogni aggiunta la
testiamo ancora per vedere che non ci siano problemi evidenti. Dovesse presen-
tarsi un problema almeno sapremo che questo è dovuto alle linee inserite dopo
l’ultimo test che ha avuto successo.
Un passo logico per risolvere il nostro problema è quello di trovare le differenze
x2 − x1 e y2 − y1 . Memorizzeremo queste differenze in variabili temporanee
chiamate dx e dy e le stamperemo a video.
quello atteso, dovremo concentrarci solo sulle poche righe aggiunte dall’ultimo
test e non sull’intera funzione.
Proseguiamo con il calcolo della somma dei quadrati di dx e dy:
def DistanzaTraDuePunti(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
DistQuadrata = dx**2 + dy**2
print "DistQuadrata vale ", DistQuadrata
return 0.0
Nota come i due print che avevamo usato prima siano stati rimossi in quanto ci
sono serviti per testare quella parte di programma ma adesso sarebbero inutili.
Un codice come questo è chiamato codice temporaneo perché è utile durante
la costruzione del programma ma alla fine deve essere rimosso in quanto non fa
parte delle funzioni richieste alla versione definitiva della nostra funzione.
Ancora una volta eseguiamo il programma. Se tutto funziona dovremmo trovare
un risultato pari a 25 (la somma dei quadrati costruiti sui cateti di lato 3 e 4).
Non ci resta che calcolare la radice quadrata. Se abbiamo importato il modulo
matematico math possiamo usare la funzione sqrt per elaborare il risultato:
def DistanzaTraDuePunti(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
DistQuadrata = dx**2 + dy**2
Risultato = math.sqrt(DistQuadrata)
return Risultato
Stavolta se tutto va bene abbiamo finito. Potresti anche stampare il valore di
Risultato prima di uscire dalla funzione con return.
Soprattutto all’inizio non dovresti mai aggiungere più di poche righe di pro-
gramma alla volta. Man mano che la tua esperienza di programmatore cresce
ti troverai a scrivere pezzi di codice sempre più grandi. In ogni caso nelle prime
fasi il processo di sviluppo incrementale ti farà risparmiare un bel po’ di tempo.
Ecco gli aspetti chiave del processo di sviluppo incrementale:
1. Inizia con un programma funzionante e fai piccoli cambiamenti: questo ti
permetterà di scoprire facilmente dove siano localizzati gli eventuali errori.
2. Usa variabili temporanee per memorizzare i valori intermedi, cosı̀ da po-
terli stampare e controllare.
3. Quando il programma funziona perfettamente rimuovi le istruzioni tempo-
ranee e consolida le istruzioni in espressioni composite, sempre che questo
non renda il programma difficile da leggere.
Esercizio: usa lo sviluppo incrementale per scrivere una funzione
chiamata Ipotenusa che ritorna la lunghezza dell’ipotenusa di un
triangolo rettangolo, passando i due cateti come parametri. Registra
ogni passo del processo di sviluppo man mano che esso procede.
5.3 Composizione 49
5.3 Composizione
È possibile chiamare una funzione dall’interno di un’altra funzione. Questa
capacità è chiamata composizione.
Scriveremo ora una funzione che accetta come parametri il centro ed un punto
sulla circonferenza di un cerchio e calcola l’area del cerchio.
Risultato = AreaDelCerchio(Raggio)
return Risultato
Esercizio: scrivi una funzione Pendenza(x1, y1, x2, y2) che ri-
torna il valore della pendenza della retta passante per i punti (x1, y1)
e (x2, y2). Poi usa questa funzione in una seconda funzione chia-
mata IntercettaY(x1, y1, x2, y2) che ritorna il valore delle or-
dinate quando la retta determinata dagli stessi punti ha X uguale a
zero.
Possiamo rendere le funzioni ancora più concise avvantaggiandoci del fatto che
la condizione nell’istruzione if è anch’essa di tipo booleano:
>>> Divisibile(6, 4)
0
>>> Divisibile(6, 3)
1
if Divisibile(x, y):
print x, "e’ divisibile per", y
else:
print x, "non e’ divisibile per", y
Il valore di ritorno (2) è moltiplicato per n (3) e il risultato (6) diventa il valore
di ritorno della funzione che ha fatto partire l’intero processo.
Questo è il diagramma di stack per l’intera serie di funzioni:
Lo stesso si può dire per le funzioni che scrivi tu stesso: quando abbiamo scritto
la funzione Divisibile, che controlla se un numero è divisibile per un altro,
e abbiamo verificato che la funzione è corretta controllando il codice possiamo
usarla senza doverla ricontrollare ancora.
Quando hai chiamate ricorsive invece di seguire il flusso di programma puoi par-
tire dal presupposto che la chiamata ricorsiva funzioni (producendo il risultato
corretto) chiedendoti in seguito: “Supponendo che si riesca a trovare il fattoriale
di n − 1, posso calcolare il fattoriale di n?” In questo caso è chiaro che puoi
farlo moltiplicandolo per n. È certamente strano partire dal presupposto che
una funzione lavori correttamente quando non è ancora stata finita, non è vero?
def Fattoriale(n):
if n == 0:
return 1
else:
return n * Fattoriale(n-1)
f ibonacci(0) = 1
f ibonacci(1) = 1
f ibonacci(n) = f ibonacci(n − 1) + f ibonacci(n − 2);
Tradotta in Python:
Con una funzione del genere il flusso di esecuzione diventa praticamente impos-
sibile da seguire anche per piccoli valori di n. In questo caso ed in casi analoghi
vale la pena di adottare l’accettazione con fiducia partendo dal presupposto che
le due chiamate ricorsive funzionino correttamente e che quindi la somma dei
loro valori di ritorno sia corretta.
54 Funzioni produttive
-1
5.9 Glossario
Funzione produttiva: funzione che produce un valore.
Codice morto: parte di un programma che non può mai essere eseguita, spesso
perché compare dopo un’istruzione di return.
Valore None: valore speciale ritornato da una funzione che non ha un’istruzione
return, o se l’istruzione return non specifica un valore di ritorno.
Codice temporaneo: codice inserito solo nella fase di sviluppo del programma
e che non è richiesto nella versione finale.
Iterazione
Numero = 5
print Numero,
Numero = 7
print Numero
a = 5
b = a # a e b sono uguali
a = 3 # ora a e b sono diversi
def ContoAllaRovescia(n):
while n > 0:
print n
n = n-1
print "Partenza!"
La chiamata ricorsiva è stata rimossa e quindi questa funzione ora non è più
ricorsiva.
Puoi leggere il programma con l’istruzione while come fosse scritto in un lin-
guaggio naturale: “Finché (while) n è più grande di 0 stampa il valore di n e
poi diminuiscilo di 1. Quando arrivi a 0 stampa la stringa Partenza!”.
Il corpo del ciclo while consiste di tutte le istruzioni che seguono l’intestazione
e che hanno la stessa indentazione.
Questo tipo di flusso è chiamato ciclo o loop. Nota che se la condizione è falsa
al primo controllo, le istruzioni del corpo non sono mai eseguite.
6.3 Tabelle 59
Il corpo del ciclo dovrebbe cambiare il valore di una o più variabili cosı̀ che la
condizione possa prima o poi diventare falsa e far cosı̀ terminare il ciclo. In caso
contrario il ciclo si ripeterebbe all’infinito, determinando un ciclo infinito.
Nel caso di ContoAllaRovescia possiamo essere certi che il ciclo è destinato a
terminare visto che n è finito ed il suo valore diventa via via più piccolo cosı̀
da diventare, prima o poi, pari a zero. In altri casi può non essere cosı̀ facile
stabilire se un ciclo avrà termine:
def Sequenza(n):
while n != 1:
print n,
if n%2 == 0: # se n e’ pari
n = n/2
else: # se n e’ dispari
n = n*3+1
6.3 Tabelle
Una delle cose per cui sono particolarmente indicati i cicli è la generazione di
tabulati. Prima che i computer fossero comunemente disponibili si dovevano
calcolare a mano logaritmi, seni, coseni e i valori di tante altre funzioni mate-
matiche. Per rendere più facile il compito i libri di matematica contenevano
lunghe tabelle di valori la cui stesura comportava enormi quantità di lavoro
molto noioso e grosse possibilità di errore.
Quando apparvero i computer l’idea iniziale fu quella di usarli per generare
tabelle prive di errori. La cosa che non si riuscı̀ a prevedere fu il fatto che i
computer sarebbero diventati cosı̀ diffusi e disponibili a tutti da rendere quei
lunghi tabulati cartacei del tutto inutili. Per alcune operazioni i computer usano
ancora tabelle simili in modo del tutto nascosto dall’operatore: vengono usate
per ottenere risposte approssimate che poi vengono rifinite per migliorarne la
precisione. In qualche caso ci sono stati degli errori in queste tabelle “interne”, il
60 Iterazione
più famoso dei quali ha avuto come protagonista il Pentium Intel con un errore
nel calcolo delle divisioni in virgola mobile.
Sebbene la tabella dei logaritmi non sia più utile come lo era in passato rimane
tuttavia un buon esempio di iterazione. Il programma seguente stampa una
sequenza di valori nella colonna di sinistra e il loro logaritmo in quella di destra:
x = 1.0
while x < 10.0:
print x, ’\t’, math.log(x)
x = x + 1.0
1.0 0.0
2.0 0.69314718056
3.0 1.09861228867
4.0 1.38629436112
5.0 1.60943791243
6.0 1.79175946923
7.0 1.94591014906
8.0 2.07944154168
9.0 2.19722457734
Se questi valori sembrano strani ricorda che la funzione log usa il logaritmo dei
numeri naturali e. Dato che le potenze di due sono cosı̀ importanti in informatica
possiamo avere la necessità di calcolare il logaritmo in base 2. Per farlo usiamo
questa formula:
loge x
log2 x = (6.1)
loge 2
otteniamo:
1.0 0.0
2.0 1.0
3.0 1.58496250072
6.3 Tabelle 61
4.0 2.0
5.0 2.32192809489
6.0 2.58496250072
7.0 2.80735492206
8.0 3.0
9.0 3.16992500144
Possiamo vedere che 1, 2, 4 e 8 sono potenze di due perché i loro logaritmi in
base 2 sono numeri interi.
Per continuare con le modifiche, invece di sommare qualcosa a x ad ogni ciclo
e ottenere cosı̀ una serie aritmetica, possiamo moltiplicare x per qualcosa otte-
nendo una serie geometrica. Se vogliamo trovare il logaritmo di altre potenze
di due possiamo modificare ancora il programma:
x = 1.0
while x < 100.0:
print x, ’\t’, math.log(x)/math.log(2.0)
x = x * 2.0
Il risultato in questo caso è:
1.0 0.0
2.0 1.0
4.0 2.0
8.0 3.0
16.0 4.0
32.0 5.0
64.0 6.0
Il carattere di tabulazione fa in modo che la posizione della seconda colonna
non dipenda dal numero di cifre del valore nella prima.
Anche se i logaritmi possono non essere più cosı̀ utili per un informatico, cono-
scere le potenze di due è fondamentale.
Esercizio: modifica questo programma per fare in modo che esso
produca le potenze di due fino a 65536 (cioè 216 ). Poi stampale e
imparale a memoria!
Il carattere di backslash ’\’ in ’\t’ indica l’inizio di una sequenza di escape.
Le sequenze di escape sono usate per rappresentare caratteri invisibili come la
tabulazione (’\t’) e il ritorno a capo (’\n’). Può comparire in qualsiasi punto
di una stringa: nell’esempio appena visto la tabulazione è l’unica cosa presente
nella stringa del print.
Secondo te, com’è possibile rappresentare un carattere di backslash in una
stringa?
Esercizio: scrivi una stringa singola che quando stampata
produca
questo
risultato.
62 Iterazione
Un buon modo per iniziare è scrivere un ciclo che stampa i multipli di 2 tutti
su di una stessa riga:
i = 1
while i <= 6:
print 2*i, ’ ’,
i = i + 1
print
La prima riga inizializza una variabile chiamata i che agisce come contatore
o indice del ciclo. Man mano che il ciclo viene eseguito i passa da 1 a 6.
Quando i è 7 la condizione non è più soddisfatta ed il ciclo termina. Ad ogni
ciclo viene mostrato il valore di 2*i seguito da tre spazi.
Ancora una volta vediamo come la virgola in print faccia in modo che il cursore
rimanga sulla stessa riga evitando un ritorno a capo. Quando il ciclo sui sei
valori è stato completato una seconda istruzione print ha lo scopo di portare
il cursore a capo su una nuova riga.
2 4 6 8 10 12
def StampaMultipli(n):
i = 1
while i <= 6:
print n*i, ’\t’,
i = i + 1
print
6.6 Ancora incapsulamento 63
Per incapsulare dobbiamo solo aggiungere la prima linea che dichiara il nome
della funzione e la lista dei parametri. Per generalizzare dobbiamo sostituire il
valore 2 con il parametro n.
Se chiamiamo la funzione con l’argomento 2 otteniamo lo stesso risultato di
prima. Con l’argomento 3 il risultato è:
3 6 9 12 15 18
Con l’argomento 4:
4 8 12 16 20 24
Avrai certamente indovinato come stampare la tabella della moltiplicazione.
Chiamiamo ripetutamente StampaMultipli con argomenti diversi all’interno di
un secondo ciclo:
i = 1
while i <= 6:
StampaMultipli(i)
i = i + 1
Nota come siano simili questo ciclo e quello all’interno di StampaMultipli: tutto
quello che abbiamo fatto è stato sostituire l’istruzione print con una chiamata
di funzione.
Il risultato di questo programma è la tabella della moltiplicazione:
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36
7 14 21 28 35 42
Il risultato è corretto fatta eccezione per il fatto che sarebbe meglio avere lo stes-
so numero di righe e di colonne. Per farlo dobbiamo modificare StampaMultipli
per specificare quante colonne la tabella debba avere.
Tanto per essere originali chiamiamo anche questo parametro Grandezza, di-
mostrando ancora una volta che possiamo avere parametri con lo stesso nome
all’interno di funzioni diverse. Ecco l’intero programma:
def TabellaMoltiplicazioneGenerica(Grandezza):
i = 1
while i <= Grandezza:
StampaMultipli(i, Grandezza)
i = i + 1
1 2 3 4 5 6 7
2 4 6 8 10 12 14
3 6 9 12 15 18 21
4 8 12 16 20 24 28
5 10 15 20 25 30 35
6 12 18 24 30 36 42
7 14 21 28 35 42 49
Quando generalizzi una funzione nel modo più appropriato, spesso ottieni ca-
pacità che inizialmente non erano state previste. Per esempio dato che ab = ba,
tutti i numeri compresi nella tabella (fatta eccezione per quelli della diagona-
le) sono presenti due volte. In caso di necessità puoi modificare una linea in
TabellaMoltiplicazioneGenerica per stamparne solo metà. Cambia :
66 Iterazione
StampaMultipli(i, Grandezza)
in
StampaMultipli(i, i)
per ottenere
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
6 12 18 24 30 36
7 14 21 28 35 42 49
6.9 Funzioni
Abbiamo già menzionato i motivi per cui è consigliato l’uso delle funzioni, sen-
za però entrare nel merito. Adesso ti starai chiedendo a che cosa ci stessimo
riferendo. Eccone qualcuno:
6.10 Glossario
Assegnazione ripetuta: assegnazione alla stessa variabile di più valori nel
corso del programma.
Ciclo infinito: ciclo nel quale la condizione di terminazione non è mai soddi-
sfatta.
6.10 Glossario 67
Cursore: marcatore non visibile che tiene traccia di dove andrà stampato il
prossimo carattere.
Sequenza di escape: carattere (\\) seguito da uno o più caratteri, usato per
designare dei caratteri non stampabili.
Stringhe
7.2 Lunghezza
La funzione len ritorna il numero di caratteri di una stringa:
>>> Frutto = "banana"
>>> len(Frutto)
6
Per ottenere l’ultimo carattere di una stringa potresti essere tentato di fare
qualcosa di simile a:
Lunghezza = len(Frutto)
Ultimo = Frutto[Lunghezza] # ERRORE!
ma c’è qualcosa che non va: infatti ottieni un errore IndexError: string
index out of range dato che stai facendo riferimento all’indice 6 quando quelli
validi vanno da 0 a 5. Per ottenere l’ultimo carattere dovrai quindi scrivere:
Lunghezza = len(Frutto)
Ultimo = Frutto[Lunghezza-1]
In alternativa possiamo usare indici negativi che in casi come questo sono più
comodi, contando a partire dalla fine della stringa: l’espressione Frutto[-1]
ritorna l’ultimo carattere della stringa, Frutto[-2] il penultimo e cosı̀ via.
Esercizio: scrivi una funzione che prende una stringa come argomen-
to e la stampa un carattere per riga partendo dall’ultimo carattere.
Ad ogni ciclo, Lettera assume il valore del prossimo carattere della stringa
Frutto, cosı̀ che Frutto viene attraversata completamente finché non rimangono
più caratteri da analizzare.
L’esempio seguente mostra come usare il concatenamento e un ciclo for per
generare una serie alfabetica, e cioè una lista di valori nei quali gli elementi
appaiono in ordine alfabetico. Per esempio nel libro Make Way for Ducklings di
Robert McCloskey i nomi dei protagonisti sono Jack, Kack, Lack, Mack, Nack,
Ouack, Pack e Quack. Questo ciclo restituisce i nomi in ordine:
Prefissi = "JKLMNOPQ"
Suffisso = "ack"
Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack
Non è del tutto corretto dato che Ouack e Quack sono scritti in modo errato.
Se non è specificato il primo indice (prima dei due punti :) la porzione parte
dall’inizio della stringa. Senza il secondo indice la porzione finisce con il termine
della stringa:
>>> Frutto = "banana"
>>> Frutto[:3]
’ban’
>>> Frutto[3:]
’ana’
Secondo te cosa significa Frutto[:]?
if Parola == "BANANA":
print "stai parlando di un frutto!"
Altri operatori di confronto sono utili per mettere le parole in ordine alfabetico:
if Parola < "BANANA":
print "la tua parola" + Parola + "viene prima di BANANA."
elif Parola > "BANANA":
print "la tua parola" + Parola + "viene dopo BANANA."
else:
print "hai inserito la parola BANANA"
Devi comunque fare attenzione al fatto che Python non gestisce le parole maiu-
scole e minuscole come facciamo noi in modo intuitivo: in un confronto le lettere
maiuscole vengono sempre prima delle minuscole, cosı̀ che:
"BANANA" < "BAnana" < "Banana" < "bANANA" < "banana"
"ZEBRA" < "banana"
Un modo pratico per aggirare il problema è quello di convertire le stringhe ad
un formato standard (tutto maiuscole o tutto minuscole) prima di effettuare il
confronto.
Saluto = "Ciao!"
Saluto[0] = ’M’ # ERRORE!
print Saluto
Le stringhe sono infatti immutabili e ciò significa che non puoi cambiare una
stringa esistente. L’unica cosa che puoi eventualmente fare è creare una nuova
stringa come variante di quella originale:
Saluto = "Ciao!"
NuovoSaluto = ’M’ + Saluto[1:]
print NuovoSaluto
Frutto = "banana"
Conteggio = 0
for Carattere in Frutto:
if Carattere == ’a’:
Conteggio = Conteggio + 1
print Conteggio
La variabile Conteggio è inizializzata a 0 e poi incrementata ogni volta che è
trovata una ’a’ (incrementare significa aumentare di 1; è l’opposto di decre-
mentare). Al termine del ciclo Conteggio contiene il risultato e cioè il numero
totale di lettere a nella stringa.
In questo esempio la ricerca fallisce perché la lettera ’b’ non appare nel dominio
definito dagli indici 1 e 2 (da 1 incluso fino a 2 escluso).
Possiamo usare queste costanti e la funzione find per classificare i caratteri. Per
esempio se find(string.lowercase, Carattere) ritorna un valore diverso da
-1 allora Carattere è minuscolo (un valore diverso da -1 indicherebbe infatti
la posizione del carattere trovato):
def Minuscolo(Carattere):
return string.find(string.lowercase, Carattere) != -1
def Minuscolo(Carattere):
return Carattere in string.lowercase
def Minuscolo(Carattere):
return ’a’ <= Carattere <= ’z’
Se Carattere è compreso tra ’a’ e ’z’ deve per forza trattarsi di una lettera
minuscola.
Un’altra costante definita nel modulo string può sorprenderti quando provi a
stamparla:
7.11 Glossario
Tipo di dati composto: un tipo di dati costruito con componenti che sono
essi stessi dei valori.
Mutabile: tipo di dati composto al quale possono essere assegnati nuovi valori.
Liste
Il primo esempio è una lista di quattro interi, il secondo una lista di tre stringhe.
Gli elementi di una stessa lista non devono necessariamente essere tutti dello
stesso tipo. Questa lista contiene una stringa, un numero in virgola mobile, un
intero ed un’altra lista:
>>> range(1,5)
[1, 2, 3, 4]
La funzione range prende due argomenti e ritorna una lista che contiene tutti
gli interi a partire dal primo (incluso) fino al secondo (escluso).
Ci sono altre due forme per range. Con un solo argomento crea una lista a
partire da 0:
78 Liste
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Se è presente un terzo argomento questo specifica l’intervallo tra valori succes-
sivi, chiamato passo. Questo esempio mostra come ottenere una stringa dei
numeri dispari tra 1 e 10:
>>> range(1, 10, 2)
[1, 3, 5, 7, 9]
Infine esiste una lista speciale che non contiene alcun elemento: è chiamata lista
vuota ed è indicata da [].
Con tutti questi modi di creare liste sarebbe un peccato non poter variare il
contenuto di una lista o poter passare liste come parametri di funzioni. Infatti
entrambe queste cose possono essere fatte:
>>> Vocabolario = ["amico", "casa", "telefono"]
>>> Numeri = [17, 123]
>>> ListaVuota = []
>>> print Vocabolario, Numeri, ListaVuota
[’amico’, ’casa’, ’telefono’] [17, 123] []
>>> Numeri[-1]
5
>>> Numeri[-2]
17
>>> Numeri[-3]
IndexError: list index out of range
i = 0
while i < 4:
print Squadre[i]
i = i + 1
Questo ciclo while conta da 0 a 4: quando l’indice del ciclo i vale 4 la condizione
diventa falsa e il ciclo termina. Il corpo del ciclo è eseguito solo quando i è 0,
1, 2 e 3.
Ad ogni ciclo la variabile i è usata come indice della lista: questo tipo di elabora-
zione è chiamata elaborazione trasversale di una lista o attraversamento
di una lista.
i = 0
while i < len(Squadre):
print Squadre[i]
i = i + 1
Possiamo rimuovere elementi da una lista assegnando loro una lista vuota:
Possono essere aggiunti elementi ad una lista inserendoli in una porzione vuota
nella posizione desiderata:
Come puoi facilmente immaginare del gestisce anche gli indici negativi e avvisa
con messaggio d’errore se l’indice è al di fuori dei limiti ammessi.
Puoi usare una porzione come indice di del:
Come abbiamo già visto la porzione indica tutti gli elementi a partire dal primo
indice incluso fino al secondo indice escluso.
Nel primo caso a e b si riferiscono a due diverse “cose” che hanno lo stesso valore.
Nel secondo caso si riferiscono alla stessa “cosa”. Queste “cose” hanno un nome:
oggetti. Un oggetto è un qualcosa cui può far riferimento una variabile.
Ogni oggetto ha un identificatore unico che possiamo ricavare con la funzione
id. Stampando l’identificatore di a e di b possiamo dire subito se le due variabili
si riferiscono allo stesso oggetto:
>>> id(a)
135044008
>>> id(b)
135044008
Otteniamo lo stesso identificatore e ciò significa che Python ha creato in memoria
un’unica stringa cui fanno riferimento entrambe le variabili a e b.
In questo ambito le liste si comportano diversamente dalle stringhe, dato che
quando creiamo due liste queste sono sempre oggetti diversi:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> id(a)
135045528
>>> id(b)
135041704
Il diagramma di stato in questo caso è
84 Liste
8.11 Alias
Dato che le variabili si riferiscono ad oggetti quando assegniamo una variabile
ad un’altra entrambe le variabili si riferiscono allo stesso oggetto:
>>> a = [1, 2, 3]
>>> b = a
La stessa lista in questo caso ha due nomi differenti, a e b, e diciamo che questi
sono due alias. Dato che l’oggetto cui entrambi si riferiscono è lo stesso è
indifferente quale degli alias si usi per effettuare un’elaborazione:
>>> b[0] = 5
>>> print a
[5, 2, 3]
Il modo più semplice per clonare una lista è quello di usare l’operatore porzione:
>>> a = [1, 2, 3]
>>> b = a[:]
>>> print b
[1, 2, 3]
8.13 Parametri di tipo lista 85
Il fatto di prendere una porzione di a crea una nuova lista. In questo caso la por-
zione consiste degli elementi dell’intera lista, dato che non sono stati specificati
gli indici iniziale e finale.
Ora siamo liberi di modificare b senza doverci preoccupare di a:
>>> b[0] = 5
>>> print a
[1, 2, 3]
Dato che l’oggetto lista è condiviso da due frame l’abbiamo disegnato a cavallo
di entrambi.
Se una funzione modifica una lista passata come parametro, viene modificata la
lista stessa e non una sua copia. Per esempio CancellaTesta rimuove il primo
elemento da una lista:
def CancellaTesta(Lista):
del Lista[0]
Ecco com’è usata CancellaTesta:
>>> Numeri = [1, 2, 3]
>>> CancellaTesta(Numeri)
>>> print Numeri
[2, 3]
86 Liste
Quando una funzione ritorna una lista in realtà viene ritornato un riferimento
alla lista stessa. Per esempio Coda ritorna una lista che contiene tutti gli elementi
di una lista a parte il primo:
def Coda(Lista):
return Lista[1:]
Ecco com’è usata Coda:
>>> Numeri = [1, 2, 3]
>>> Resto = Coda(Numeri)
>>> print Resto
[2, 3]
Dato che il valore ritornato è stato creato con l’operatore porzione stiamo
restituendo una nuova lista. La creazione di Resto ed ogni suo successivo
cambiamento non ha alcun effetto sulla lista originale Numeri.
8.15 Matrici
Le liste annidate sono spesso usate per rappresentare matrici. Per esempio la
matrice
1 2 3
4 5 6
7 8 9
può essere rappresentata come
8.16 Stringhe e liste 87
Matrice è una lista di tre elementi dove ciascuno è una riga della matrice.
Possiamo selezionare una singola riga nel solito modo:
>>> Matrice[1]
[4, 5, 6]
>>> Matrice[1][1]
5
Può anche essere usato un argomento opzionale per specificare quale debba
essere il delimitatore da considerare. In questo esempio usiamo la stringa el
come delimitatore:
Come nel caso di split, join accetta un argomento opzionale che rappresenta
il delimitatore da inserire tra gli elementi. Il delimitatore di default è uno spazio
ma può essere cambiato:
8.17 Glossario
Lista: collezione di oggetti identificata da un nome dove ogni oggetto è selezio-
nabile grazie ad un indice.
Indice: variabile intera o valore che indica un elemento all’interno di una lista.
Sequenza: ognuno dei tipi di dati che consiste in una lista ordinata di elementi
identificati da un indice.
Alias: più variabili che si riferiscono allo stesso oggetto con nomi diversi.
Delimitatore: carattere o stringa usati per indicare dove una stringa deve
essere spezzata.
Capitolo 9
Tuple
C’è un altro tipo di dati in Python, simile alla lista eccetto per il fatto che è
immutabile: la tupla. La tupla è una lista di valori separati da virgole:
Per creare una tupla con un singolo elemento dobbiamo aggiungere la virgola
finale dopo l’elemento:
>>> t1 = (’a’,)
>>> type(t1)
<type ’tuple’>
Senza la virgola, infatti, Python tratterebbe (’a’) come una stringa tra paren-
tesi:
>>> t2 = (’a’)
>>> type(t2)
<type ’string’>
Sintassi a parte le operazioni sulle tuple sono identiche a quelle sulle liste.
L’operatore indice seleziona un elemento dalla tupla:
90 Tuple
Scambia(a, b)
import random
for i in range(10):
x = random.random()
print x
Per generare un numero casuale (lo chiameremo cosı̀ d’ora in poi, anche se è
sottinteso che la casualità ottenuta non è assoluta) compreso tra 0.0 (compreso)
ed un limite superiore Limite (escluso) moltiplica x per Limite.
def ListaCasuale(Lungh):
s = [0] * Lungh
for i in range(Lungh):
s[i] = random.random()
return s
Testiamo la funzione generando una lista di otto elementi: per poter controllare
i programmi è sempre bene partire con insiemi di dati molto piccoli.
>>> ListaCasuale(8)
[0.11421081445000203, 0.38367479346590505, 0.16056841528993915,
0.29204721527340882, 0.75201663462563095, 0.31790165552578986,
0.43858231029411354, 0.27749268689939965]
9.6 Conteggio
Conteggio = 0
for Carattere in Frutto:
if Carattere == ’a’:
Conteggio = Conteggio + 1
print Conteggio
Il primo passo è quello di sostituire Frutto con Lista e Carattere con Numero.
Questo non cambia il programma ma semplicemente lo rende più leggibile.
Conteggio = 0
for Numero in Lista
if LimiteInferiore < Numero < LimiteSuperiore:
Conteggio = Conteggio + 1
print Conteggio
ma con quattro intervalli è facile commettere errori sia nel calcolo dei limiti sia
nella battitura dei numeri:
Possiamo usare un ciclo per calcolare i limiti inferiore e superiore per ciascun
intervallo, usando i come indice del ciclo da 0 a NumIntervalli-1:
da 0.0 a 0.125
da 0.125 a 0.25
da 0.25 a 0.375
da 0.375 a 0.5
da 0.5 a 0.625
da 0.625 a 0.75
da 0.75 a 0.875
da 0.875 a 1.0
Puoi vedere come ogni intervallo sia della stessa ampiezza, come tutta la gamma
da 0.0 a 1.0 sia presente e come non ci siano intervalli che si sovrappongono.
Creiamo la lista dei conteggi all’esterno del ciclo dato che la dobbiamo creare una
sola volta (e non ad ogni ciclo). All’interno del ciclo chiameremo ripetutamente
NellIntervallo e aggiorneremo l’i-esimo elemento della lista dei conteggi:
NumIntervalli = 8
Conteggio = [0] * NumIntervalli
AmpiezzaIntervallo = 1.0 / NumIntervalli
for i in range(NumIntervalli):
LimiteInferiore = i * AmpiezzaIntervallo
LimiteSuperiore = LimiteInferiore + AmpiezzaIntervallo
Conteggio[i] = NellIntervallo(Lista, LimiteInferiore, \
LimiteSuperiore)
print Conteggio
Con una lista di 1000 valori questo programma produce una lista di conteggi di
questo tipo:
Ci aspettavamo per ogni intervallo un valore medio di 125 (1000 numeri divisi per
8 intervalli) ed in effetti ci siamo andati abbastanza vicini da poter affermare che
il generatore di numeri casuali si comporta in modo sufficientemente realistico.
Esercizio: prova questa funzione con liste più lunghe per vedere se
il conteggio di valori in ogni intervallo tende o meno a livellarsi
(maggiore è il numero di prove più i valori dovrebbero diventare
simili).
9.9 Glossario
Tipo immutabile: tipo in cui i singoli elementi non possono essere modificati.
L’operazione di assegnazione ad elementi o porzioni produce un errore.
Tipo mutabile: tipo di dati in cui gli elementi possono essere modificati. Liste
e dizionari sono mutabili; stringhe e tuple non lo sono.
Tupla: tipo di sequenza simile alla lista con la differenza di essere immutabile.
Le tuple possono essere usate dovunque serva un tipo immutabile, per
esempio come chiave in un dizionario.
Assegnazione ad una tupla: assegnazione di tutti gli elementi della tupla
usando un’unica istruzione di assegnazione.
Programma deterministico: programma che esegue le stesse operazioni ogni
volta che è eseguito.
Pseudocasuale: sequenza di numeri che sembra essere casuale ma in realtà è
il risultato di un’elaborazione deterministica.
Istogramma: lista di interi in cui ciascun elemento conta il numero di volte in
cui una determinata condizione si verifica.
Pattern matching: piano di sviluppo del programma che consiste nell’identi-
ficare un tracciato di elaborazione già visto e modificarlo per ottenere la
soluzione di un problema simile.
Capitolo 10
Dizionari
I tipi di dati composti che abbiamo visto finora (stringhe, liste e tuple) usano
gli interi come indici. Qualsiasi tentativo di usare altri tipi di dati produce un
errore.
I dizionari sono simili agli altri tipi composti ma si differenziano per il fatto
di poter usare qualsiasi tipo di dato immutabile come indice. Se desideriamo
creare un dizionario per la traduzione di parole dall’inglese all’italiano è utile
poter usare la parola inglese come indice di ricerca della corrispondente italiana.
Gli indici usati sono in questo caso delle stringhe.
Un modo per creare un dizionario è partire con un dizionario vuoto e aggiungere
via via gli elementi. Il dizionario vuoto è indicato da {}:
>>> Eng2Ita = {}
>>> Eng2Ita[’one’] = ’uno’
>>> Eng2Ita[’two’] = ’due’
>>> Eng2Ita.keys()
[’one’, ’three’, ’two’]
Questa forma di notazione punto specifica il nome della funzione keys ed il nome
dell’oggetto cui applicare la funzione Eng2Ita. Le parentesi vuote indicano che
questo metodo non prende parametri.
>>> Eng2Ita.values()
[’uno’, ’tre’, ’due’]
Il metodo items ritorna entrambi nella forma di una lista di tuple, una per ogni
coppia chiave-valore:
>>> Eng2Ita.items()
[(’one’,’uno’), (’three’, ’tre’), (’two’, ’due’)]
>>> Eng2Ita.has_key(’one’)
1
>>> End2Ita.has_key(’deux’)
0
>>> has_key(’one’)
NameError: has_key
Purtroppo il messaggio d’errore a volte, come in questo caso, non è del tutto
chiaro: Python cerca di dirci che la funzione has key non esiste, dato che con
questa sintassi abbiamo chiamato la funzione has key e non invocato il metodo
has key dell’oggetto.
0 0 0 1 0
0 0 0 0 0
0 2 0 0 0
0 0 0 0 0
0 0 0 3 0
In questo caso abbiamo solo 3 coppie chiave-valore, una per ciascun elemento
diverso da zero nella matrice. Ogni chiave è una tupla ed ogni valore un intero.
>>> Matrice[(0,3)]
1
>>> Matrice[0,3] # questa sintassi e’ equivalente
1
Nota come la sintassi per la rappresentazione della matrice sotto forma di dizio-
nario sia diversa da quella della lista di liste: invece di due valori indice usiamo
un unico indice che è una tupla di interi.
>>> Matrice[1,3]
KeyError: (1, 3)
>>> Matrice.get((0,3), 0)
1
>>> Matrice.get((1,3), 0)
0
10.5 Suggerimenti
Se hai fatto qualche prova con la funzione di Fibonacci nella sezione 5.7 avrai
notato che man mano che l’argomento passato alla funzione cresce il tempo
trascorso prima di ottenere il risultato aumenta molto rapidamente. Mentre
Fibonacci(20) termina quasi istantaneamente Fibonacci(30) impiega qualche
secondo e Fibonacci(40) impiega un tempo lunghissimo.
Un grafico delle chiamate mostra una serie di frame (uno per ogni funzione)
con linee che collegano ciascun frame alle funzioni chiamate. A iniziare dall’alto
Fibonacci con n=4 chiama Fibonacci con n=3 e n=2. A sua volta Fibonacci
con n=3 chiama Fibonacci con n=2 e n=1. E cosı̀ via.
Una buona soluzione è quella di tenere traccia in un dizionario di tutti i valori già
calcolati per evitare il ricalcolo in tempi successivi. Un valore che viene memoriz-
zato per un uso successivo è chiamato suggerimento. Ecco un’implementazione
di Fibonacci fatta usando i “suggerimenti”:
def Fibonacci(n):
if Precedenti.has_key(n):
return Precedenti[n]
else:
NuovoValore = Fibonacci(n-1) + Fibonacci(n-2)
Precedenti[n] = NuovoValore
return NuovoValore
>>> Fibonacci(50)
OverflowError: integer addition
Ci sono due modi per creare un valore intero lungo. Il primo consiste nello
scrivere un intero immediatamente seguito da una L maiuscola:
>>> type(1L)
<type ’long int’>
Il secondo è l’uso della funzione long per convertire un valore in intero lungo.
long può accettare qualsiasi tipo di numero e anche una stringa di cifre:
>>> long(1)
1L
>>> long(3.9)
3L
>>> long(’57’)
57L
Questo tipo di istogramma può essere utile per comprimere un file di testo: dato
che le lettere compaiono con frequenza diversa possiamo usare codici brevi per
le lettere più frequenti e codici via via più lunghi per le meno frequenti.
I dizionari permettono di realizzare istogrammi in modo elegante:
>>> ConteggioLettere = {}
>>> for Lettera in "Mississippi":
... ConteggioLettere [Lettera] = ConteggioLettere.get \
(Lettera, 0) + 1
...
>>> ConteggioLettere
{’M’: 1, ’s’: 4, ’p’: 2, ’i’: 4}
Siamo partiti con un dizionario vuoto e per ogni lettera della stringa abbia-
mo incrementato il corrispondente conteggio. Alla fine il dizionario contiene
coppie formate da lettera e frequenza e queste coppie rappresentano il nostro
istogramma.
Può essere più interessante mostrare l’istogramma in ordine alfabetico, e in
questo caso facciamo uso dei metodi items e sort:
Abbiamo visto già il metodo items ma sort è il primo metodo che incontriamo
ad essere applicabile alle liste. Ci sono parecchi altri metodi applicabili alle liste
(tra gli altri append, extend e reverse). Puoi consultare la documentazione di
Python per avere ulteriori informazioni a riguardo.
10.8 Glossario
Dizionario: collezione di coppie chiave-valore dove si associa ogni chiave ad
un valore. Le chiavi devono essere immutabili; i valori possono essere di
qualsiasi tipo.
File ed eccezioni
>>>
La funzione che segue copia un file leggendo e scrivendo fino a 50 caratteri per
volta. Il primo argomento è il nome del file originale, il secondo quello della
copia:
def CopiaFile(Originale, Copia):
f1 = open(Originale, "r")
f2 = open(Copia, "w")
while 1:
Testo = f1.read(50)
11.1 File di testo 107
if Testo == "":
break
f2.write(Testo)
f1.close()
f2.close()
return
>>> f = open("test.dat","w")
>>> f.write("linea uno\nlinea due\nlinea tre\n")
>>> f.close()
>>> f = open("test.dat","r")
>>> print f.readline()
linea uno
>>>
In questo caso il risultato è in formato lista e ciò significa che le stringhe appaiono
racchiuse tra apici e i caratteri di ritorno a capo come sequenze di escape.
Alla fine del file readline ritorna una stringa vuota e readlines una lista
vuota:
>>> x = 52
>>> f.write (str(x))
>>> NumAuto = 52
>>> "%d" % NumAuto
’52’
11.2 Scrittura delle variabili 109
Il risultato è la stringa ’52’ che non deve essere confusa con il valore intero 52.
Una sequenza di formato può comparire dovunque all’interno di una stringa di
formato cosı̀ possiamo inserire un valore in una frase qualsiasi:
>>> NumAuto = 52
>>> "In luglio abbiamo venduto %d automobili." % NumAuto
’In luglio abbiamo venduto 52 automobili.’
>>> "%6d" % 62
’ 62’
>>> "%12f" % 6.1
’ 6.100000’
>>> "%-6d" % 62
’62 ’
Nel caso dei numeri in virgola mobile possiamo anche specificare quante cifre
devono comparire dopo il punto decimale:
si devono stampare dei valori contabili in forma tabellare con il punto decimale
allineato.
Immaginiamo un dizionario che contiene il nome (la chiave) e tariffa oraria (il
valore) per una serie di lavoratori. Ecco una funzione che stampa il contenuto
del dizionario in modo formattato:
def Report(Tariffe) :
Lavoratori = Tariffe.keys()
Lavoratori.sort()
for Lavoratore in Lavoratori:
print "%-20s %12.02f" % (Lavoratore, Tariffe[Lavoratore])
11.3 Directory
Quando crei un nuovo file aprendolo e scrivendoci qualcosa, questo viene me-
morizzato nella directory corrente e cioè in quella dalla quale sei partito per
eseguire l’interprete Python. Allo stesso modo se richiedi la lettura di un file
Python lo cercherà nella directory corrente.
Se vuoi aprire il file da qualche altra parte dovrai anche specificare il percorso
per raggiungerlo, e cioè il nome della directory di appartenenza:
>>> f = open("/usr/share/dict/words","r")
>>> print f.readline()
Aarhus
In questo esempio apriamo un file chiamato words che risiede in una directory
chiamata dict che risiede in un’altra directory chiamata share che a sua volta
risiede in usr. Quest’ultima risiede nella directory principale del sistema, /,
secondo il formato Linux.
Non puoi usare / come parte di un nome di file proprio perché questo è un
carattere delimitatore che viene inserito tra i vari nomi delle directory nella
definizione del percorso.
11.4 Pickling 111
11.4 Pickling
Abbiamo visto come per mettere dei valori in un file di testo li abbiamo dovuti
preventivamente convertire in stringhe. Hai già visto come farlo usando str:
>>> f.write (str(12.3))
>>> f.write (str(4.567))
>>> f.write (str([1,2,3]))
Il problema è che quando cerchi di recuperare il valore dal file ottieni una stringa,
e non l’informazione originale che avevi memorizzato. Oltretutto non c’è nem-
meno il modo di sapere dove inizia o termina di preciso la stringa che definisce
il valore nel file:
>>> f.readline()
’12.34.567[1, 2, 3]’
La soluzione è il pickling (che letteralmente significa “conservazione sotto ve-
tro”) chiamato cosı̀ perché “preserva” le strutture dei dati. Il modulo pickle
contiene tutti i comandi necessari. Importiamo il modulo e poi apriamo il file
nel solito modo:
>>> import pickle
>>> f = open("test.pck","w")
Per memorizzare una struttura di dati usa il metodo dump e poi chiudi il file:
>>> pickle.dump(12.3, f)
>>> pickle.dump(4.567, f)
>>> pickle.dump([1,2,3], f)
>>> f.close()
Puoi riaprire il file e caricare le strutture di dati memorizzati con il metodo
load:
>>> f = open("test.pck","r")
>>> x = pickle.load(f)
>>> x
12.3
>>> type(x)
<type ’float’>
>>> x2 = pickle.load(f)
>>> x2
4.567
>>> type(x2)
<type ’float’>
>>> y = pickle.load(f)
>>> y
[1, 2, 3]
>>> type(y)
<type ’list’>
Ogni volta che invochiamo load otteniamo un singolo valore completo del suo
tipo originale.
112 File ed eccezioni
11.5 Eccezioni
Se il programma si blocca a causa di un errore in esecuzione viene creata
un’eccezione: l’interprete si ferma e mostra un messaggio d’errore.
Le eccezioni più comuni per i programmi che hai visto finora possono essere:
>>> a = []
>>> print a[5]
IndexError: list index out of range
>>> b = {}
>>> print b[’pippo’]
KeyError: pippo
L’istruzione try esegue le istruzioni nel suo blocco. Se non si verificano eccezioni
(e cioè se le istruzioni del blocco try sono eseguite senza errori) l’istruzione
except ed il blocco corrispondente vengono saltate ed il flusso del programma
prosegue dalla prima istruzione presente dopo il blocco except. Nel caso si
verifichi qualche eccezione (nel nostro caso la più probabile è che il file richiesto
11.6 Glossario 113
non esiste) viene interrotto immediatamente il flusso del blocco try ed eseguito
il blocco except.
Possiamo incapsulare questa capacità in una funzione: FileEsiste prende un
nome di un file e ritorna vero se il file esiste, falso se non esiste.
def FileEsiste(NomeFile):
try:
f = open(NomeFile)
f.close()
return 1
except:
return 0
Puoi anche usare blocchi di except multipli per gestire diversi tipi di eccezioni.
Vedi a riguardo il Python Reference Manual.
Con try/except possiamo fare in modo di continuare ad eseguire un program-
ma in caso di errore. Possiamo anche “sollevare” delle eccezioni nel corso del
programma con l’istruzione raise in modo da generare un errore in esecuzione
quando qualche condizione non è verificata:
def InputNumero() :
x = input (’Dimmi un numero: ’)
if x > 16 :
raise ’ErroreNumero’, ’mi aspetto numeri minori di 17!’
return x
In questo caso viene generato un errore in esecuzione quando è introdotto un
numero maggiore di 16.
L’istruzione raise prende due argomenti: il tipo di eccezione e l’indicazione
specifica del tipo di errore. ErroreNumero è un nuovo tipo di eccezione che
abbiamo inventato appositamente per questa applicazione.
Se la funzione che chiama InputNumero gestisce gli errori il programma continua,
altrimenti Python stampa il messaggio d’errore e termina l’esecuzione:
>>> InputNumero()
Dimmi un numero: 17
ErroreNumero: mi aspetto numeri minori di 17!
Il messaggio di errore include l’indicazione del tipo di eccezione e l’informazione
aggiuntiva che è stata fornita.
11.6 Glossario
File: entità identificata da un nome solitamente memorizzata su hard disk,
floppy disk o CD-ROM, e contenente una serie di dati.
114 File ed eccezioni
File di testo: file che contiene solo caratteri stampabili organizzati come serie
di righe separate da caratteri di ritorno a capo.
Istruzione break: istruzione che causa l’interruzione immediata del flusso del
programma e l’uscita da un ciclo.
Istruzione continue: istruzione che causa l’immediato ritorno del flusso del
programma all’inizio del ciclo senza completarne il corpo.
Classi e oggetti
class Punto:
pass
P1 = Punto()
12.2 Attributi
Possiamo aggiungere un nuovo dato ad un’istanza usando la notazione punto:
Questa sintassi è simile a quella usata per la selezione di una variabile apparte-
nente ad un modulo, tipo math.pi e string.uppercase. In questo caso stiamo
selezionando una voce da un’istanza e queste voci che fanno parte dell’istanza
sono dette attributi.
Puoi usare la notazione punto all’interno di ogni espressione cosı̀ che le istruzioni
proposte di seguito sono a tutti gli effetti perfettamente lecite:
12.4 Uguaglianza
La parola “uguale” sembra cosı̀ intuitiva che probabilmente non hai mai pensato
più di tanto a cosa significa veramente.
Quando dici “Alberto ed io abbiamo la stessa auto” naturalmente vuoi dire che
entrambi possedete un’auto dello stesso modello ed è sottinteso che stai parlando
di due auto diverse e non di una soltanto. Se dici “Alberto ed io abbiamo la
stessa madre” è sottinteso che la madre è la stessa e voi siete fratelli 1 . L’idea
stessa di uguaglianza dipende quindi dal contesto.
Quando parli di oggetti abbiamo la stessa ambiguità: se due oggetti di tipo
Punto sono gli stessi, significa che hanno semplicemente gli stessi dati (coordi-
nate) o che si sta parlando di un medesimo oggetto?
Per vedere se due riferimenti fanno capo allo stesso oggetto usa l’operatore ==:
1 Non tutte le lingue soffrono di questa ambiguità: per esempio il tedesco ha parole diverse
per indicare tipi diversi di similarità: “la stessa auto” in questo contesto è traducibile con
“gleiche Auto” e “la stessa madre” con “selbe Mutter”.
118 Classi e oggetti
>>> P1 = Punto()
>>> P1.x = 3
>>> P1.y = 4
>>> P2 = Punto()
>>> P2.x = 3
>>> P2.y = 4
>>> P1 == P2
0
>>> P2 = P1
>>> P1 == P2
1
Se creiamo due differenti oggetti che contengono gli stessi dati possiamo ora
usare StessoPunto per verificare se entrambi rappresentano lo stesso punto:
>>> P1 = Punto()
>>> P1.x = 3
>>> P1.y = 4
>>> P2 = Punto()
>>> P2.x = 3
>>> P2.y = 4
>>> StessoPunto(P1, P2)
1
Logicamente se le due variabili si riferiscono allo stesso punto e sono alias l’una
dell’altra allo stesso tempo garantiscono l’uguaglianza debole e quella forte.
12.5 Rettangoli
Se volessimo creare una classe per rappresentare un rettangolo quali informazioni
dovremmo fornire per specificarlo in modo univoco? Per rendere le cose più
semplici partiremo con un rettangolo orientato lungo gli assi.
Ci sono poche possibilità tra cui scegliere: potremmo specificare il centro del
rettangolo e le sue dimensioni (altezza e larghezza); oppure specificare un angolo
di riferimento e le dimensioni (ancora altezza e larghezza); o ancora specificare le
12.6 Istanze come valori di ritorno 119
Rett.Larghezza = Rett.Larghezza + 50
Rett.Altezza = Rett.Altezza + 100
>>> R1 = Rettangolo()
>>> R1.Larghezza = 100.0
>>> R1.Altezza = 200.0
>>> R1.AltoSinistra = Punto()
>>> R1.AltoSinistra.x = 0.0;
>>> R1.AltoSinistra.y = 0.0;
>>> AumentaRettangolo(R1, 50, 100)
12.8 Copia
Abbiamo già visto che gli alias possono rendere il programma difficile da leggere
perché una modifica può cambiare il valore di variabili che apparentemente non
hanno nulla a che vedere con quelle modificate. Man mano che le dimensioni del
programma crescono diventa difficile tenere a mente quali variabili si riferiscano
ad un dato oggetto.
La copia di un oggetto è spesso una comoda alternativa all’alias. Il modulo copy
contiene una funzione copy che permette di duplicare qualsiasi oggetto:
>>> P1.x = 3
>>> P1.y = 4
>>> P2 = copy.copy(P1)
>>> P1 == P2
0
>>> StessoPunto(P1, P2)
1
Dopo avere importato il modulo copy possiamo usare il metodo copy in esso
contenuto per creare un nuovo oggetto Punto. P1 e P2 non solo sono lo stesso
punto ma contengono gli stessi dati.
Per copiare un semplice oggetto come Punto che non contiene altri oggetti al
proprio interno copy è sufficiente. Questa è chiamata copia debole:
>>> Punto2 = copy.copy(Punto1)
Quando abbiamo a che fare con un Rettangolo che contiene al proprio interno
un riferimento ad un altro oggetto Punto, copy non lavora come ci si aspetta
dato che viene copiato il riferimento a Punto cosı̀ che sia il vecchio che il nuovo
Rettangolo si riferiscono allo stesso oggetto invece di averne uno proprio per
ciascuno.
Se creiamo il rettangolo R1 nel solito modo e ne facciamo una copia R2 usando
copy il diagramma di stato risultante sarà:
Quasi certamente non è questo ciò che vogliamo. In questo caso, invocando
AumentaRettangolo su uno dei rettangoli non si cambieranno le dimensioni
dell’altro, ma MuoviRettangolo sposterà entrambi! Questo comportamento
genera parecchia confusione e porta facilmente a commettere errori.
Fortunatamente il modulo copy contiene un altro metodo chiamato deepcopy
che copia correttamente non solo l’oggetto ma anche gli eventuali oggetti presenti
al suo interno:
>>> Oggetto2 = copy.deepcopy(Oggetto1)
Ora Oggetto1 e Oggetto2 sono oggetti completamente separati e occupano
diverse zone di memoria.
Possiamo usare deepcopy per riscrivere completamente AumentaRettangolo
cosı̀ da non cambiare il Rettangolo originale ma restituire una copia con le
nuove dimensioni:
def AumentaRettangolo(Rettangolo, AumentoLargh, AumentoAlt) :
import copy
NuovoRett = copy.deepcopy(Rettangolo)
122 Classi e oggetti
12.9 Glossario
Classe: tipo di dato composto definito dall’utente.
Oggetto: tipo di dato composto che è spesso usato per definire un concetto o
una cosa del mondo reale.
Copia forte: copia sia del contenuto di un oggetto che degli eventuali oggetti
interni e degli oggetti eventualmente contenuti in essi; è realizzata dalla
funzione deepcopy del modulo copy.
Capitolo 13
Classi e funzioni
13.1 Tempo
Definiamo ora una classe chiamata Tempo che permette di registrare un’ora del
giorno:
class Tempo:
pass
Possiamo creare un nuovo oggetto Tempo assegnando gli attributi per le ore, i
minuti e i secondi:
Time = Tempo()
Time.Ore = 11
Time.Minuti = 59
Time.Secondi = 30
Esercizio: scrivi una funzione booleana Dopo che prende come argo-
menti due oggetti Tempo (Tempo1 e Tempo2) e ritorna vero se Tempo1
segue cronologicamente Tempo2 e falso in caso contrario.
124 Classi e funzioni
return Somma
13.3 Modificatori
Ci sono dei casi in cui è utile una funzione che possa modificare gli oggetti
passati come suoi parametri. Quando questo si verifica la funzione è detta
modificatore.
La funzione Incremento che somma un certo numero di secondi a Tempo può
essere scritta in modo molto intuitivo come modificatore. La prima stesura
potrebbe essere questa:
Tempo.Minuti = Tempo.Minuti + 1
def ConverteInSecondi(Orario):
Minuti = Orario.Ore * 60 + Orario.Minuti
Secondi = Minuti * 60 + Orario.Secondi
return Secondi
Tutto quello che ci serve è ora un modo per convertire da un intero ad un oggetto
Tempo:
def ConverteInTempo(Secondi):
Orario = Tempo()
Orario.Ore = Secondi/3600
Secondi = Secondi - Orario.Ore * 3600
Orario.Minuti = Secondi/60
Secondi = Secondi - Orario.Minuti * 60
Orario.Secondi = Secondi
return Orario
Forse dovrai pensarci un po’ su per convincerti che questa tecnica per convertire
un numero da una base all’altra è formalmente corretta. Comunque ora puoi
usare queste funzioni per riscrivere SommaTempi:
Questa versione è molto più concisa dell’originale e ed è molto più facile dimo-
strare la sua correttezza.
13.6 Generalizzazione
Sicuramente la conversione numerica da base 10 a base 60 e viceversa è meno
intuitiva da capire, data la sua astrazione. Il nostro intuito ci aveva portato a
lavorare con i tempi in un modo molto più comprensibile anche se meno efficace.
Malgrado lo sforzo iniziale abbiamo progettato il nostro programma facendo in
modo di trattare i tempi come numeri in base 60, il tempo investito nello scri-
vere le funzioni di conversione viene abbondantemente recuperato quando riu-
sciamo a scrivere un programma molto più corto, facile da leggere e correggere,
e soprattutto più affidabile.
Se il programma è progettato in modo oculato è anche più facile aggiungere
nuove caratteristiche. Per esempio immagina di sottrarre due tempi per deter-
minare l’intervallo trascorso. L’approccio iniziale avrebbe portato alla necessità
di dover implementare una sottrazione con il prestito. Con le funzioni di conver-
sione, scritte una sola volta ma riutilizzate in varie funzioni, è molto più facile
e rapido avere un programma funzionante anche in questo caso.
128 Classi e funzioni
13.7 Algoritmi
Quando trovi una soluzione ad una classe di problemi, invece che ad un singolo
problema, hai a che fare con un algoritmo. Abbiamo già usato questa parola
in precedenza ma non l’abbiamo propriamente definita ed il motivo risiede nel
fatto che non è facile trovare una definizione. Proveremo un paio di approcci.
Ma se sei stato “pigro” probabilmente hai trovato delle scorciatoie che ti hanno
permesso di alleggerire il lavoro. Per fare un esempio per moltiplicare n per 9
potevi scrivere n − 1 come prima cifra, seguito da 10 − n come seconda cifra.
Questo sistema è una soluzione generale per moltiplicare ogni numero di una
cifra maggiore di zero per 9: in questo caso ci troviamo a che fare con un
algoritmo.
Le varie tecniche che hai imparato per calcolare la somma col riporto, la sot-
trazione con il prestito, la moltiplicazione, la divisione sono tutti algoritmi.
Una delle caratteristiche degli algoritmi è che non richiedono intelligenza per
essere eseguiti in quanto sono processi meccanici nei quali ogni passo segue il
precedente secondo un insieme di regole più o meno semplice.
Alcune delle cose più semplici che facciamo naturalmente, senza difficoltà o
pensiero cosciente, sono tra le cose più difficili da esprimere sotto forma di
algoritmo. Comprendere un linguaggio naturale è un buon esempio: lo sappiamo
fare tutti ma finora nessuno è stato in grado di spiegare come ci riusciamo
esprimendolo sotto forma di algoritmo.
13.8 Glossario
Funzione pura: funzione che non modifica gli oggetti ricevuti come parametri.
La maggior parte delle funzioni pure sono produttive.
Modificatore: funzione che cambia uno o più oggetti ricevuti come parametri.
La maggior parte dei modificatori non restituisce valori di ritorno.
Sviluppo pianificato: tipo di sviluppo del programma che prevede uno studio
preventivo del problema da risolvere.
Algoritmo: serie di passi per risolvere una classe di problemi in modo mecca-
nico.
Capitolo 14
Classi e metodi
• I metodi sono definiti all’interno della definizione di classe per rendere più
esplicita la relazione tra la classe ed i metodi corrispondenti.
Nelle prossime sezioni prenderemo le funzioni scritte nei due capitoli precedenti
e le trasformeremo in metodi. Questa trasformazione è puramente meccanica e
puoi farla seguendo una serie di semplici passi: se sei a tuo agio nel convertire
tra funzione e metodo e viceversa riuscirai anche a scegliere di volta in volta la
forma migliore.
14.2 StampaTempo
Nel capitolo 13 abbiamo definito una classe chiamata Tempo e scritto una fun-
zione StampaTempo:
class Tempo:
pass
def StampaTempo(Orario):
print str(Orario.Ore) + ":" +
str(Orario.Minuti) + ":" +
str(Orario.Secondi)
Per rendere StampaTempo un metodo tutto quello che dobbiamo fare è muovere la
definizione della funzione all’interno della definizione della classe. Fai attenzione
al cambio di indentazione:
class Tempo:
def StampaTempo(Orario):
print str(Orario.Ore) + ":" + \
str(Orario.Minuti) + ":" + \
str(Orario.Secondi)
>>> OraAttuale.StampaTempo()
14.3 Un altro esempio 133
Come sempre l’oggetto su cui il metodo è invocato appare prima del punto ed
il nome del metodo subito dopo.
L’oggetto su cui il metodo è invocato è automaticamente assegnato al primo
parametro, quindi nel caso di OraAttuale è assegnato a Orario.
Per convenzione il primo parametro di un metodo è chiamato self, traducibile
in questo caso come “l’oggetto stesso”.
Come nel caso di StampaTempo(OraAttuale), la sintassi di una chiamata di
funzione tradizionale suggerisce che la funzione sia l’agente attivo: equivale
pressappoco a dire “StampaTempo! C’è un oggetto per te da stampare!”
Nella programmazione orientata agli oggetti sono proprio gli oggetti ad essere
considerati l’agente attivo: un’invocazione del tipo OraAttuale.StampaTempo()
significa “OraAttuale! Invoca il metodo per stampare il tuo valore!”
Questo cambio di prospettiva non sembra cosı̀ utile ed effettivamente negli esem-
pi che abbiamo visto finora è cosı̀. Comunque lo spostamento della responsa-
bilità dalla funzione all’oggetto rende possibile scrivere funzioni più versatili e
rende più immediati il mantenimento ed il riutilizzo del codice.
D’ora in poi i tre punti di sospensione ... all’interno del codice indicheranno
che è stata omessa per questioni di leggibilità una parte del codice già definito
in precedenza.
La trasformazione, come abbiamo già detto, è puramente meccanica: abbiamo
spostato la definizione di una funzione all’interno di una definizione di classe e
cambiato il nome del primo parametro.
Ora possiamo invocare Incremento come metodo.
OraAttuale.Incremento(500)
134 Classi e metodi
class Tempo:
...
def Dopo(self, Tempo2):
if self.Ore > Tempo2.Ore:
return 1
if self.Ore < Tempo2.Ore:
return 0
if TempoCottura.Dopo(OraAttuale):
print "Il pranzo e’ pronto"
Indice = Indice + 1
return -1
Questa è la versione aggiornata e migliorata:
def Trova(Stringa, Carattere, Inizio=0):
Indice = Inizio
while Inizio < len(Stringa):
if Stringa[Indice] == Carattere:
return Indice
Indice = Indice + 1
return -1
Il terzo parametro, Inizio, è opzionale perché abbiamo fornito il valore 0 di
default. Se invochiamo Trova con solo due argomenti usiamo il valore di default
per il terzo cosı̀ da iniziare la ricerca dall’inizio della stringa:
>>> Trova("Mela", "l")
2
Se forniamo un terzo parametro questo sovrascrive il valore di default:
>>> Trova("Mela", "l", 3)
-1
class Punto:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return ’(’ + str(self.x) + ’, ’ + str(self.y) + ’)’
>>> P = Punto(3, 4)
>>> str(P)
’(3, 4)’
14.8 Ridefinizione di un operatore 137
>>> P = Punto(3, 4)
>>> print P
(3, 4)
Quando scriviamo una nuova classe iniziamo quasi sempre scrivendo init
(la funzione che rende più facile istanziare oggetti) e str (utile per il debug).
class Punto:
...
def __add__(self, AltroPunto):
return Punto(self.x + AltroPunto.x, self.y + AltroPunto.y)
>>> P1 = Punto(3, 4)
>>> P2 = Punto(5, 7)
>>> P3 = P1 + P2
>>> print P3
(8, 11)
>>> P1 = Punto(3, 4)
>>> P2 = Punto(5, 7)
>>> print P1 * P2
43
>>> print 2 * P2
(10, 14)
>>> print P2 * 2
AttributeError: ’int’ object has no attribute ’x’
Per un trattato più esauriente sulla ridefinizione degli operatori vedi l’appendi-
ce B.
14.9 Polimorfismo
La maggior parte dei metodi che abbiamo scritto finora lavorano solo per un tipo
specifico di dati. Quando crei un nuovo oggetto scrivi dei metodi che lavorano
su oggetti di quel tipo.
Ci sono comunque operazioni che vorresti poter applicare a molti tipi come
ad esempio le operazioni matematiche che abbiamo appena visto. Se più tipi
di dato supportano lo stesso insieme di operazioni puoi scrivere funzioni che
lavorano indifferentemente con ciascuno di questi tipi.
14.9 Polimorfismo 139
Questo metodo lavorerà per tutti i valori di x e y che possono essere moltiplicati
e per ogni valore di z che può essere sommato al prodotto.
Possiamo invocarla con valori numerici:
>>> MoltSomma(3, 2, 1)
7
>>> P1 = Punto(3, 4)
>>> P2 = Punto(5, 7)
>>> print MoltSomma(2, P1, P2)
(11, 15)
>>> print MoltSomma(P1, P2, 1)
44
Nel primo caso il punto P1 è moltiplicato per uno scalare e il prodotto è poi
sommato a un altro punto (P2). Nel secondo caso il prodotto punto produce un
valore numerico al quale viene sommato un altro valore numerico.
Una funzione che accetta parametri di tipo diverso è chiamata polimorfica.
Come esempio ulteriore consideriamo il metodo DirittoERovescio che stampa
due volte una stringa, prima direttamente e poi all’inverso:
def DirittoERovescio(Stringa):
import copy
Rovescio = copy.copy(Stringa)
Rovescio.reverse()
print str(Stringa) + str(Rovescio)
Dato che il metodo reverse è un modificatore si deve fare una copia della stringa
prima di rovesciarla: in questo modo il metodo reverse non modificherà la lista
originale ma solo una sua copia.
Ecco un esempio di funzionamento di DirittoERovescio con le liste:
14.10 Glossario
Linguaggio orientato agli oggetti: linguaggio che è dotato delle caratteri-
stiche che facilitano la programmazione orientata agli oggetti, tipo la
possibilità di definire classi e l’ereditarietà.
Programmazione orientata agli oggetti: stile di programmazione nel qua-
le i dati e le operazioni che li manipolano sono organizzati in classi e
metodi.
Metodo: funzione definita all’interno di una definizione di classe invocata su
istanze di quella classe.
Ridefinire: rimpiazzare un comportamento o un valore di default, scrivendo
un metodo con lo stesso nome o rimpiazzando un parametro di default
con un valore particolare.
Metodo di inizializzazione: metodo speciale invocato automaticamente nel
momento in cui viene creato un nuovo oggetto e usato per inizializzare gli
attributi dell’oggetto stesso.
Ridefinizione dell’operatore: estensione degli operatori predefiniti (+, -, *,
>, <, ecc.) per farli lavorare con i tipi definiti dall’utente.
Prodotto punto: operazione definita nell’algebra lineare che moltiplica due
punti e produce un valore numerico.
Moltiplicazione scalare: operazione definita nell’algebra lineare che moltipli-
ca ognuna delle coordinate di un punto per un valore numerico.
Funzione polimorfica: funzione che può operare su più di un tipo di dati. Se
tutte le operazioni in una funzione possono essere applicate ad un tipo di
dato allora la funzione può essere applicata al tipo.
Capitolo 15
Insiemi di oggetti
15.1 Composizione
Uno dei primi esempi di composizione che hai visto è stato l’uso di un’invocazio-
ne di un metodo all’interno di un’espressione. Un altro esempio è stata la strut-
tura di istruzioni annidate, con un if all’interno di un ciclo while all’interno di
un altro if e cosı̀ via.
Dopo aver visto questo modo di operare e aver analizzato le liste e gli oggetti,
non dovresti essere sorpreso del fatto che puoi anche creare liste di oggetti.
Non solo: puoi creare oggetti che contengono liste come attributi, o liste che
contengono liste, oggetti che contengono oggetti e cosı̀ via.
In questo capitolo e nel prossimo vedremo alcuni esempi di queste combinazioni
usando l’oggetto Carta.
Picche → 3
Cuori → 2
Quadri → 1
Fiori → 0
Un utile effetto pratico di questa mappatura è il fatto che possiamo confrontare
i semi tra di loro determinando subito quale vale di più. La mappatura per
il rango è abbastanza ovvia: per le carte numeriche il rango è il numero della
carta mentre per le carte figurate usiamo queste associazioni:
Asso → 1
Jack → 11
Regina → 12
Re → 13
Cominciamo con il primo abbozzo di definizione di Carta e come sempre for-
niamo anche un metodo di inizializzazione dei suoi attributi:
class Carta:
def __init__(self, Seme=0, Rango=0):
self.Seme = Seme
self.Rango = Rango
TreDiFiori = Carta(0, 3)
dove il primo argomento (0) rappresenta il seme fiori ed il secondo (3) il rango
della carta.
class Carta:
def __str__(self):
return (self.ListaRanghi[self.Rango] + " di " +
self.ListaSemi[self.Seme])
Le due liste sono in questo caso degli attributi di classe che sono definiti
all’esterno dei metodi della classe e possono essere utilizzati da qualsiasi metodo
della classe.
Con i metodi che abbiamo scritto finora possiamo già creare e stampare le carte:
Gli attributi di classe come ListaSemi sono condivisi da tutti gli oggetti Carta.
Il vantaggio è che possiamo usare qualsiasi oggetto Carta per accedere agli
attributi di classe:
# controlla il seme
if self.Seme > Altro.Seme: return 1
if self.Seme < Altro.Seme: return -1
15.5 Mazzi
Ora che abbiamo oggetti per rappresentare le carte il passo più logico è quello
di definire una classe per rappresentare il Mazzo. Il mazzo è composto di carte
cosı̀ ogni oggetto Mazzo conterrà una lista di carte come attributo.
15.6 Stampa del mazzo 145
class Mazzo:
def __init__(self):
self.Carte = []
for Seme in range(4):
for Rango in range(1, 14):
self.Carte.append(Carta(Seme, Rango))
Il modo più semplice per creare un mazzo è per mezzo di un ciclo annidato: il
ciclo esterno numera i semi da 0 a 3, quello interno i ranghi da 1 a 13. Dato che
il ciclo esterno viene eseguito 4 volte e quello interno 13 il corpo è eseguito un
totale di 52 volte (4 per 13). Ogni iterazione crea una nuova istanza di Carta
con seme e rango correnti ed aggiunge la carta alla lista Carte.
Il metodo append lavora sulle liste ma non sulle tuple (che sono immutabili).
class Mazzo:
...
def StampaMazzo(self):
for Carta in self.Carte:
print Carta
Ecco una versione di str che ritorna una rappresentazione di un Mazzo come
stringa. Tanto per aggiungere qualcosa facciamo anche in modo di indentare
ogni carta rispetto alla precedente:
class Mazzo:
...
def __str__(self):
s = ""
for i in range(len(self.Carte)):
s = s + " "*i + str(self.Carte[i]) + "\n"
return s
random.randrange(0, len(self.Carte))
Un modo utile per mescolare un mazzo è scambiare ogni carta con un’altra scelta
a caso. È possibile che la carta possa essere scambiata con se stessa ma questa
situazione è perfettamente accettabile. Infatti se escludessimo questa possibilità
l’ordine delle carte sarebbe meno casuale:
class Mazzo:
...
def Mescola(self):
import random
NumCarte = len(self.Carte)
for i in range(NumCarte):
j = random.randrange(i, NumCarte)
self.Carte[i], self.Carte[j] = self.Carte[j], self.Carte[i]
Piuttosto che partire dal presupposto che le carte del mazzo siano sempre 52
abbiamo scelto di ricavare la lunghezza della lista e memorizzarla in NumCarte.
Per ogni carta del mazzo abbiamo scelto casualmente una carta tra quelle non
ancora mescolate. Poi abbiamo scambiato la carta corrente (i) con la carta
selezionata (j). Per scambiare le due carte abbiamo usato un’assegnazione di
una tupla, come si è già visto nella sezione 9.2:
self.Carte[i], self.Carte[j] = self.Carte[j], self.Carte[i]
Esercizio: riscrivi questa riga di codice senza usare un’assegnazione
di una tupla.
class Mazzo:
...
def PrimaCarta(self):
return self.Carte.pop()
In realtà pop rimuove l’ultima carta della lista, cosı̀ stiamo in effetti togliendo
dal fondo del mazzo, ma dal nostro punto di vista questa anomalia è indifferente.
Una operazione che può essere utile è la funzione booleana EVuoto che ritorna
vero (1) se il mazzo non contiene più carte:
class Mazzo:
...
def EVuoto(self):
return (len(self.Carte) == 0)
15.9 Glossario
Mappare: rappresentare un insieme di valori usando un altro insieme di valori
e costruendo una mappa di corrispondenza tra i due insiemi.
Ereditarietà
16.1 Ereditarietà
La caratteristica più frequentemente associata alla programmazione ad oggetti
è l’ereditarietà che è la capacità di definire una nuova classe come versione
modificata di una classe già esistente.
D’altro canto l’ereditarietà può rendere più difficile la lettura del programma,
visto che quando si invoca un metodo non è sempre chiaro dove questo sia
stato definito (se all’interno del genitore o delle classi da questo derivate) con
il codice che deve essere rintracciato all’interno di più moduli invece che essere
in un unico posto ben definito. Molte delle cose che possono essere fatte con
l’ereditarietà possono essere di solito gestite elegantemente anche senza di essa,
ed è quindi il caso di usarla solo se la struttura del problema la richiede: se
usata nel momento sbagliato può arrecare più danni che apportare benefici.
La mano si differenzia dal mazzo perché, a seconda del gioco, possiamo avere la
necessità di effettuare su una mano alcuni tipi di operazioni che per un mazzo
non avrebbero senso: nel poker posso avere l’esigenza di classificare una mano
(full, colore, ecc.) o confrontarla con un’altra mano mentre nel bridge devo
poter calcolare il punteggio di una mano per poter effettuare una puntata.
Nella definizione della classe figlia il nome del genitore compare tra parentesi:
class Mano(Mazzo):
pass
Questa istruzione indica che la nuova classe Mano eredita dalla classe già esistente
Mazzo.
Il costruttore Mano inizializza gli attributi della mano, che sono il Nome e le
Carte. La stringa Nome identifica la mano ed è probabilmente il nome del
giocatore che la sta giocando: è un parametro opzionale che per default è una
stringa vuota. Carte è la lista delle carte nella mano, inizializzata come lista
vuota:
class Mano(Mazzo):
def __init__(self, Nome=""):
self.Carte = []
self.Nome = Nome
In quasi tutti i giochi di carte è necessario poter aggiungere e rimuovere carte dal-
la mano. Della rimozione ce ne siamo già occupati, dato che Mano eredita imme-
diatamente RimuoviCarta da Mazzo. Dobbiamo invece scrivere AggiungeCarta:
class Mano(Mazzo):
def AggiungeCarta(self,Carta) :
self.Carte.append(Carta)
Il metodo di lista append aggiunge una nuova carta alla fine della lista di carte.
16.3 Distribuire le carte 151
2 di Picche
3 di Picche
4 di Picche
Asso di Cuori
9 di Fiori
Anche se è comodo ereditare da metodi esistenti può essere necessario modifi-
care il metodo str nella classe Mano per aggiungere qualche informazione,
ridefinendo il metodo omonimo ereditato dalla classe Mazzo:
class Mano(Mazzo)
...
def __str__(self):
s = "La mano di " + self.Nome
if self.EVuoto():
s = s + " e’ vuota\n"
else:
s = s + " contiene queste carte:\n"
return s + Mazzo.__str__(self)
s è una stringa che inizialmente indica chi è il proprietario della mano. Se la
mano è vuota vengono aggiunte ad s le parole "e’ vuota" e viene ritornata s.
IN caso contrario vengono aggiunte le parole "contiene queste carte" e la
rappresentazione della mano sotto forma di stringa già vista in Mazzo, elaborata
invocando il metodo str della classe Mazzo su self.
Potrebbe sembrarti strano il fatto di usare self, che si riferisce alla mano cor-
rente, con un metodo appartenente alla classe Mazzo: ricorda che Mano è un tipo
di Mazzo. Gli oggetti Mano possono fare qualsiasi cosa di cui è capace Mazzo e
cosı̀ è legale invocare un metodo Mazzo con la mano self.
In genere è sempre legale usare un’istanza di una sottoclasse invece di un’istanza
della classe genitore.
class ManoOldMaid(Mano):
def RimuoveCoppie(self):
Conteggio = 0
CarteOriginali = self.Carte[:]
for CartaOrig in CarteOriginali:
CartaDaCercare = Carta(3-CartaOrig.Seme, CartaOrig.Rango)
if CartaDaCercare in self.Carte:
self.Carte.remove(CartaOrig)
self.Carte.remove(CartaDaCercare)
print "Mano di %s : %s elimina %s" %
(self.Nome,CartaOrig,CartaDaCercare)
Conteggio = Conteggio + 1
return Conteggio
Iniziamo facendo una copia della lista di carte, cosı̀ da poter attraversare la
copia finché non rimuoviamo l’originale: dato che self.Carte viene modificata
154 Ereditarietà
Per ogni carta della mano andiamo a controllare se quella che la elimina è
presente nella stessa mano. La carta “eliminante” ha lo stesso rango e l’altro
seme dello stesso colore di quella “eliminabile”: l’espressione 3-Carta.Seme
serve proprio a trasformare una carta di Fiori (seme 0) in Picche (seme 3) e
viceversa; una carta di Quadri (seme 1) in Cuori (seme 2) e viceversa.
>>> Mano1.RimuoveCoppie()
Mano di Franco: 7 di Picche elimina 7 di Fiori
Mano di Franco: 8 di Picche elimina 8 di Fiori
Mano di Franco: 10 di Quadri elimina 10 di Cuori
>>> print Mano1
La mano di Franco contiene queste carte:
Asso di Picche
2 di Quadri
6 di Cuori
Regina di Fiori
7 di Quadri
5 di Fiori
Jack di Quadri
class GiocoOldMaid(GiocoDiCarte):
Alcuni dei passi della partita sono stati separati in metodi singoli per ragioni di
chiarezza anche se dal punto di vista del programma questo non era strettamente
necessario.
RimuoveTutteLeCoppie attraversa la lista di mani e invoca RimuoveCoppie su
ognuna:
class GiocoOldMaid(GiocoDiCarte):
...
def RimuoveTutteLeCoppie(self):
156 Ereditarietà
Conteggio = 0
for Mano in self.Mani:
Conteggio = Conteggio + Mano.RimuoveCoppie()
return Conteggio
class GiocoOldMaid(GiocoDiCarte):
...
def GiocaUnTurno(self, Giocatore):
if self.Mani[Giocatore].EVuoto():
return 0
Vicino = self.TrovaVicino(Giocatore)
CartaScelta = self.Mani[Vicino].PrimaCarta()
self.Mani[Giocatore].AggiungeCarta(CartaScelta)
print "Mano di", self.Mani[Giocatore].Nome, \
": scelta", CartaScelta
Conteggio = self.Mani[Giocatore].RimuoveCoppie()
self.Mani[Giocatore].Mescola()
return Conteggio
class GiocoOldMaid(GiocoDiCarte):
...
def TrovaVicino(self, Giocatore):
NumMani = len(self.Mani)
for Prossimo in range(1,NumMani):
Vicino = (Giocatore + Prossimo) % NumMani
16.7 Classe GiocoOldMaid 157
if not self.Mani[Vicino].EVuoto():
return Vicino
Se TrovaVicino dovesse effettuare un giro completo dei giocatori senza trovare
qualcuno con delle carte in mano tornerebbe None e causerebbe un errore da
qualche parte del programma. Fortunatamente possiamo provare che questo
non succederà mai, sempre che la condizione di fine partita sia riconosciuta
correttamente.
Abbiamo omesso il metodo StampaMani dato che puoi scriverlo tu senza proble-
mi.
La stampa che mostriamo in seguito mostra una partita effettuata usando le
sole quindici carte di valore più elevato (i 10, i jack, le regine ed i re), ed è
stata ridotta per questioni di spazio. La partita ha visto come protagonisti tre
giocatori: Allen, Jeff e Chris. Con un mazzo cosı̀ piccolo il gioco si ferma dopo
aver rimosso 7 coppie invece delle consuete 25.
>>> import Carte
>>> Gioco = Carte.GiocoOldMaid()
>>> Gioco.Partita(["Allen","Jeff","Chris"])
---------- Le carte sono state distribuite
La mano di Allen contiene queste carte:
Re di Cuori
Jack di Fiori
Regina di Picche
Re di Picche
10 di Quadri
10 di Quadri
16.8 Glossario
Ereditarietà: capacità di definire una nuova classe come versione modificata
di una classe precedentemente definita.
Classe genitore: classe da cui si deriva un’altra classe.
Classe figlia: nuova classe creata derivandola da una classe già esistente; è
anche chiamata “sottoclasse”.
Capitolo 17
Liste linkate
class Nodo:
def __init__(self, Contenuto=None, ProssimoNodo=None):
self.Contenuto = Contenuto
self.ProssimoNodo = ProssimoNodo
160 Liste linkate
def __str__(self):
return str(self.Contenuto)
Per rendere il tutto più interessante abbiamo bisogno di una lista che contiene
più di un nodo:
Questo codice crea tre nodi ma non siamo in presenza di una lista dato che
questi nodi non sono linkati (collegati uno all’altro). Il diagramma di stato in
questo caso è:
Per linkare i nodi dobbiamo fare in modo che il primo si riferisca al secondo, ed
il secondo al terzo:
Il riferimento del terzo nodo è None e questo indica che ci troviamo alla fine
della lista. Ecco il nuovo diagramma di stato:
Ora sai come creare nodi e come linkarli in liste. Ciò che probabilmente è meno
chiaro è il motivo per cui questo possa rivelarsi utile.
17.3 Liste come collezioni 161
Per passare una lista di questo tipo come parametro ad una funzione dobbiamo
passare quindi soltanto il riferimento al suo primo nodo. Per fare un esempio, la
funzione StampaLista prende un singolo nodo come argomento, considerandolo
l’inizio della lista e stampa il contenuto di ogni nodo finché non viene raggiunta
la fine della lista:
def StampaLista(Nodo):
while Nodo:
print Nodo,
Nodo = Nodo.ProssimoNodo
print
>>> StampaLista(Nodo1)
1 2 3
1. Separa la lista in due parti: il primo nodo (chiamato testa) ed il resto (la
coda).
3. Stampa la testa.
Tutto ciò di cui abbiamo bisogno è un caso base ed un modo per verificare che
per ogni tipo di lista riusciremo ad arrivare al caso base per interrompere la
serie di chiamate ricorsive. Data la definizione ricorsiva della lista un caso base
intuitivo è la lista vuota, rappresentata da None:
def StampaInversa(Lista):
if Lista == None: return
Testa = Lista
Coda = Lista.ProssimoNodo
StampaInversa(Coda)
print Testa,
La prima riga gestisce il caso base senza fare niente. Le due righe successive
dividono la lista in due parti (Testa e Coda). Le ultime due righe stampano la
lista. Ricorda che la virgola alla fine del print evita la stampa del ritorno a
capo tra un nodo e l’altro.
>>> StampaInversa(Nodo1)
3 2 1
Non c’è nulla che vieti ad un nodo di fare riferimento ad un nodo precedente
della lista o addirittura a se stesso. Questa figura mostra una lista di due nodi
ognuno dei quali si riferisce a se stesso:
17.6 Il teorema dell’ambiguità fondamentale 163
def RimuoviSecondo(Lista):
if Lista == None: return
Primo = Lista
Secondo = Lista.ProssimoNodo
# il primo nodo deve riferirsi al terzo
Primo.ProssimoNodo = Secondo.ProssimoNodo
# separa il secondo nodo dal resto della lista
Secondo.ProssimoNodo = None
return Secondo
Ancora una volta abbiamo usato delle variabili temporanee per rendere il codice
più leggibile. Ecco come usare questo metodo:
>>> StampaLista(Nodo1)
1 2 3
>>> Rimosso = RimuoviSecondo(Nodo1)
>>> StampaLista(Rimosso)
2
>>> StampaLista(Nodo1)
1 3
Cosa succede se invochi questo metodo e passi una lista composta da un solo
elemento (elemento singolo)? Cosa succede se passi come argomento una lista
17.8 Metodi contenitore e aiutante 165
vuota? C’è una precondizione per questo metodo? Se esiste riscrivi il metodo
per gestire gli eventuali problemi.
def StampaInversaFormato(Lista) :
print "[",
if Lista != None :
Testa = Lista
Coda = Lista.ProssimoNodo
StampaInversa(Coda)
print Testa,
print "]",
Ancora una volta è una buona idea testare questo metodo per vedere se funziona
correttamente anche in casi particolari, quando cioè una lista è vuota o composta
da un solo elemento.
Quando usiamo questo metodo da qualche parte nel programma invochiamo di-
rettamente StampaInversaFormato e questa invoca a sua volta StampaInversa.
In questo senso StampaInversaFormato agisce come un contenitore che usa
StampaInversa come aiutante.
class ListaLinkata:
def __init__(self) :
self.Lunghezza = 0
self.Testa = None
Una cosa positiva per quanto concerne la classe ListaLinkata è che fornisce un
posto naturale dove inserire funzioni contenitore quale StampaInversaFormato
e che possiamo far diventare metodi della classe:
166 Liste linkate
class ListaLinkata:
...
def StampaInversa(self):
print "[",
if self.Testa != None:
self.Testa.StampaInversa()
print "]",
class Nodo:
...
def StampaInversa(self):
if self.ProssimoNodo != None:
Coda = self.ProssimoNodo
Coda.StampaInversa()
print self.Contenuto,
Per rendere le cose più interessanti, rinominiamo StampaInversaFormato. Ora
abbiamo due metodi chiamati StampaInversa: quello nella classe Nodo che è
l’aiutante e quello nella classe ListaLinkata che è il contenitore. Quando il
metodo contenitore invoca self.Testa.StampaInversa sta in effetti invocando
l’aiutante dato che self.Testa è un oggetto di tipo Nodo.
Un altro beneficio della classe ListaLinkata è che rende semplice aggiungere
o rimuovere il primo elemento di una lista. AggiuntaPrimo è il metodo di
ListaLinkata per aggiungere un contenuto all’inizio di una lista:
class ListaLinkata:
...
def AggiuntaPrimo(self, Contenuto):
NodoAggiunto = Nodo(Contenuto)
NodoAggiunto.ProssimoNodo = self.Testa
self.Testa = NodoAggiunto
self.Lunghezza = self.Lunghezza + 1
Come sempre occorre verificare che questo codice funzioni correttamente anche
nel caso di liste speciali: cosa succede se la lista è inizialmente vuota?
17.10 Invarianti
Alcune liste sono “ben formate” mentre altre non lo sono. Se una lista contiene
un anello questo può creare problemi a un certo numero dei nostri metodi, tanto
che potremmo richiedere solo liste che non contengono anelli al loro interno.
Un altro prerequisito è che il valore di Lunghezza nell’oggetto ListaLinkata
corrisponda sempre al numero di nodi della lista.
Prerequisiti come questi sono chiamati invarianti perché dovrebbero essere
sempre verificati in ogni momento per ogni oggetto della classe. Specificare
gli invarianti degli oggetti è una pratica di programmazione molto indicata in
quanto consente di rendere molto più facile la verifica del codice, il controllo
dell’integrità delle strutture e il riconoscimento degli errori.
17.11 Glossario 167
Una cosa che può rendere confusi per quanto riguarda gli invarianti è che a volte
i prerequisiti che essi rappresentano possono essere violati, anche se solo tempo-
raneamente: nel metodo AggiungiPrimo, dopo aver aggiunto il nodo ma prima
di avere aggiornato Lunghezza, il prerequisito invariante non è soddisfatto. Que-
sto tipo di violazione è accettabile, dato che spesso è impossibile modificare un
oggetto senza violare un invariante almeno per un breve istante. Normalmente
richiediamo che qualsiasi metodo che si trovi a violare un invariante lo ripristini
non appena possibile.
Se l’invariante è violato in una parte significativa del codice è molto importante
commentare questo comportamento anomalo per evitare che, anche a distanza
di tempo, possano essere richieste delle operazioni che dipendono dall’integrità
dei dati proprio dove questi dati non sono corretti.
17.11 Glossario
Riferimento interno: riferimento depositato in un attributo di un oggetto.
Lista linkata: struttura di dati che implementa una collezione usando una
sequenza di nodi linkati.
Invariante: condizione che deve essere vera per un oggetto in ogni momento,
con l’unica eccezione degli istanti in cui l’oggetto è in fase di modifica.
Capitolo 18
Pile
• Ci sono molti modi per implementare un TDA e può essere utile scrive-
re un solo algoritmo in grado di funzionare per ciascuna delle possibili
implementazioni.
• TDA molto ben conosciuti, tipo la Pila (o Stack) che vedremo in questo
capitolo, sono spesso implementati nelle librerie standard dei vari linguaggi
di programmazione cosı̀ da poter essere usati da molti programmatori
senza dover essere reinventati ogni volta.
Quando parliamo di TDA spesso distinguiamo il codice che usa il TDA (cliente)
dal codice che lo implementa (fornitore).
170 Pile
Una pila è spesso chiamata struttura di dati LIFO (“last in/first out”, ultimo
inserito, primo fuori) perché l’ultimo elemento inserito in ordine di tempo è il
primo ad essere rimosso: un esempio è una serie di piatti da cucina sovrapposti,
ai quali aggiungiamo ogni ulteriore piatto appoggiandolo sopra agli altri, ed è
proprio dall’alto che ne preleviamo uno quando ci serve.
class Pila:
def __init__(self):
self.Elementi = []
def Pop(self):
return self.Elementi.pop()
def EVuota(self):
return (self.Elementi == [])
18.4 Push e Pop 171
Questo esempio mostra uno dei vantaggi della notazione postfissa: non sono
necessarie parentesi per controllare l’ordine delle operazioni. Per ottenere lo
stesso risultato con la notazione infissa avremmo dovuto scrivere (1 + 2) * 3.
18.6 Parsing
Per implementare l’algoritmo di valutazione dell’espressione dobbiamo essere
in grado di attraversare una stringa e di dividerla in una serie di operandi e
operatori. Questo processo è un esempio di parsing e il risultato è una serie di
elementi chiamati token. Abbiamo già visto questi termini all’inizio del libro.
Python fornisce un metodo split in due moduli, sia in string (per la gestione
delle stringhe) che in re (per le espressioni regolari). La funzione string.split
divide una stringa scomponendola in una lista di token e usando un singolo
carattere come delimitatore. Per esempio:
>>> import re
>>> re.split("([^0-9])", "123+456*/")
[’123’, ’+’, ’456’, ’*’, ’’, ’/’, ’’]
La lista risultante include gli operandi 123 e 456, e gli operatori * e /. Include
inoltre due stringhe vuote inserite dopo gli operandi.
def ValutaPostfissa(Espressione):
import re
ListaToken = re.split("([^0-9])", Espressione)
Pila = Pila()
for Token in ListaToken:
if Token == ’’ or Token == ’ ’:
continue
if Token == ’+’:
Somma = Pila.Pop() + Pila.Pop()
Pila.Push(Somma)
elif Token == ’*’:
Prodotto = Pila.Pop() * Pila.Pop()
Pila.Push(Prodotto)
else:
Pila.Push(int(Token))
return Pila.Pop()
La prima condizione tiene a bada gli spazi e le stringhe vuote. Le due condi-
zioni successive gestiscono gli operatori, partendo dal presupposto che qualsiasi
altra cosa sia un operatore valido. Logicamente dovremo controllare la validità
dell’espressione da valutare ed eventualmente mostrare un messaggio di errore
se ci fossero dei problemi, ma questo lo faremo più avanti.
18.9 Glossario
Tipo di dato astratto (TDA): tipo di dato (solitamente una collezione di
oggetti) definito da una serie di operazioni e che può essere implementato
in una varietà di modi diversi.
Interfaccia: insieme di operazioni che definiscono un TDA.
Implementazione: codice che soddisfa i prerequisiti di sintassi e semantica di
un’interfaccia.
Cliente: programma (o persona che scrive un programma) che usa un TDA.
Fornitore: programma (o persona che scrive un programma) che implementa
un TDA.
Maschera: definizione di classe che implementa un TDA con definizioni di
metodi che sono invocazioni di altri metodi, talvolta con l’apporto di sem-
plici trasformazioni. Le maschere non fanno un lavoro significativo, ma
migliorano o standardizzano l’interfaccia usata dal cliente.
Struttura di dati generica: struttura di dati che può contenere dati di ogni
tipo.
Notazione infissa: modo di scrivere espressioni matematiche con gli operatori
tra gli operandi, eventualmente con l’uso di parentesi.
Notazione postfissa: modo di scrivere espressioni matematiche con gli ope-
ratori posti dopo gli operandi (detta anche “notazione polacca inversa”).
Parsing: lettura di una stringa di caratteri per l’analisi dei token e della strut-
tura grammaticale.
Token: serie di caratteri che viene trattata come un’unità nell’operazione di
parsing, allo stesso modo delle parole in un linguaggio naturale.
Delimitatore: carattere usato per separare i token, allo stesso modo della
punteggiatura in un linguaggio naturale.
Capitolo 19
Code
Questo capitolo presenta due tipi di dati astratti (TDA): la Coda e la Coda con
priorità. Nella vita reale un esempio di coda può essere la linea di clienti in
attesa di un servizio di qualche tipo. Nella maggior parte dei casi il primo cliente
della fila è quello che sarà servito per primo, anche se ci possono essere delle
eccezioni. All’aeroporto ai clienti il cui volo sta per partire può essere concesso
di passare davanti a tutti, indipendentemente dalla loro posizione nella fila. Al
supermercato un cliente può scambiare per cortesia il suo posto con qualcuno
che deve pagare solo pochi prodotti.
La regola che determina chi sarà il prossimo ad essere servito si chiama politica
di accodamento. Quella più semplice è la FIFO (“first in, first out”) dove il
primo che arriva è il primo ad essere servito. La politica di accodamento più
generale è l’ accodamento con priorità dove a ciascun cliente è assegnata una
priorità ed il cliente con la massima priorità viene servito per primo indipen-
dentemente dall’ordine di arrivo. Diciamo che questa politica di accodamento è
la più generale perché la priorità può essere basata su qualsiasi fattore: l’orario
di partenza dell’aereo, la quantità di prodotti da pagare ad una cassa, l’impor-
tanza del cliente (!), la gravità dello stato di un paziente al pronto soccorso.
Logicamente non tutte le politiche di accodamento sono “giuste”...
I tipi di dati astratti Coda e Coda con priorità condividono lo stesso insieme
di operazioni. La differenza sta soltanto nella loro semantica: una Coda usa la
politica FIFO, mentre la Coda con priorità, come suggerisce il nome stesso, usa
la politica di accodamento con priorità.
class Coda:
def __init__(self):
self.Lunghezza = 0
self.Testa = None
def EVuota(self):
return (self.Lunghezza == 0)
def Rimozione(self):
Contenuto = self.Testa.Contenuto
self.Testa = self.Testa.ProssimoNodo
self.Lunghezza = self.Lunghezza - 1
return Contenuto
Vogliamo inserire nuovi elementi alla fine della lista: se la coda è vuota facciamo
in modo che Testa si riferisca al nuovo nodo.
Ci sono due invarianti per un oggetto Coda ben formato: il valore di Lunghezza
dovrebbe essere il numero di nodi nella coda e l’ultimo nodo dovrebbe avere l’at-
tributo ProssimoNodo uguale a None. Prova a studiare il metodo implementato
verificando che entrambi gli invarianti siano sempre soddisfatti.
19.3 Performance
Normalmente quando invochiamo un metodo non ci interessa quali siano i detta-
gli della sua implementazione. Ma c’è uno di questi dettagli che invece dovrebbe
interessarci: le performance del metodo. Quanto impiega ad essere eseguito?
Come cambia il tempo di esecuzione man mano che la collezione aumenta di
dimensioni?
Diamo un’occhiata a Rimozione. Non ci sono cicli o chiamate a funzione, e
ciò suggerisce che il tempo di esecuzione sarà lo stesso ogni volta. Questo tipo
di metodo è definito operazione a tempo costante. In realtà il metodo
potrebbe essere leggermente più veloce quando la lista è vuota dato che tutto il
corpo della condizione viene saltato, ma la differenza in questo caso non è molto
significativa e può essere tranquillamente trascurata.
La performance di Inserimento è molto diversa. Nel caso generale dobbiamo
attraversare completamente la lista per trovarne l’ultimo elemento.
Questo attraversamento impiega un tempo che è proporzionale alla grandezza
della lista: dato che il tempo di esecuzione in funzione lineare rispetto alla lun-
ghezza, diciamo che questo metodo è un’operazione a tempo lineare. Se con-
frontato ad un’operazione a tempo costante il suo comportamento è decisamente
peggiore.
class CodaMigliorata:
def __init__(self):
178 Code
self.Lunghezza = 0
self.Testa = None
self.UltimoNodo = None
def EVuota(self):
return (self.Lunghezza == 0)
Finora l’unico cambiamento riguarda l’aggiunta dell’attributo UltimoNodo.
Questo attributo è usato dai metodi Inserimento e Rimozione:
class CodaMigliorata:
...
def Inserimento(self, Contenuto):
NodoAggiunto = Nodo(Contenuto)
NodoAggiunto.ProssimoNodo = None
if self.Lunghezza == 0:
# se la lista e’ vuota il nuovo nodo e’
# sia la testa che la coda
self.Testa = self.UltimoNodo = NodoAggiunto
else:
# trova l’ultimo nodo
Ultimo = self.UltimoNodo
# aggiunge il nuovo nodo
Ultimo.ProssimoNodo = NodoAggiunto
self.UltimoNodo = NodoAggiunto
self.Lunghezza = self.Lunghezza + 1
Dato che UltimoNodo tiene traccia dell’ultimo nodo non dobbiamo più attraver-
sare la lista per cercarlo. Come risultato abbiamo fatto diventare questo metodo
un’operazione a tempo costante.
Comunque dobbiamo pagare un prezzo per questa modifica: quando dobbiamo
rimuovere l’ultimo nodo con Rimozione dovremo assegnare None a UltimoNodo:
class CodaMigliorata:
...
def Rimozione(self):
Contenuto = self.Testa.Contenuto
self.Testa = self.Testa.ProssimoNodo
self.Lunghezza = self.Lunghezza - 1
if self.Lunghezza == 0:
self.UltimoNodo = None
return Contenuto
Questa implementazione è più complessa di quella della coda linkata ed è più
difficile dimostrare che è corretta, Il vantaggio che abbiamo comunque ottenuto
è l’aver reso sia Inserimento che Rimozione operazioni a tempo costante.
Per esempio se gli elementi nella coda sono delle stringhe potremmo estrarle
in ordine alfabetico. Se sono punteggi del bowling dal più alto al più basso, e
viceversa nel caso del golf. In ogni caso possiamo rimuovere l’elemento con la
priorità più alta da una coda soltanto se i suoi elementi sono confrontabili tra
di loro.
Questa è un’implementazione di una coda con priorità che usa una lista Python
come attributo per contenere gli elementi della coda:
class CodaConPriorita:
def __init__(self):
self.Elementi = []
def EVuota(self):
return self.Elementi == []
class CodaConPriorita:
...
def Rimozione(self):
Indice = 0
for i in range(1,len(self.Elementi)):
if self.Elementi[i] > self.Elementi[Indice]:
Indice = i
Elemento = self.Elementi[Indice]
self.Elementi[Indice:Indice+1] = []
return Elemento
180 Code
def __str__(self):
return "%-16s: %d" % (self.Nome, self.Punteggio)
str usa l’operatore di formato per stampare i nomi ed i punteggi in forma
tabellare su colonne ordinate.
Poi definiamo una versione di cmp dove il punteggio minore ottiene la priorità
più alta: come abbiamo già visto in precedenza cmp ritorna 1 se self è più
grande di Altro, -1 se self è minore di Altro, e 0 se i due valori sono uguali.
19.7 Glossario 181
class Golf:
...
def __cmp__(self, Altro):
if self.Punteggio < Altro.Punteggio: return 1
if self.Punteggio > Altro.Punteggio: return -1
return 0
Ora siamo pronti a testare la coda con priorità sulla classe Golf:
19.7 Glossario
Coda: insieme di oggetti in attesa di un servizio di qualche tipo; abbiamo
implementato un TDA Coda che esegue le comuni operazioni su una coda.
FIFO: “First In, First Out” (primo inserito, primo rimosso) politica di accoda-
mento nella quale il primo elemento a essere rimosso è il primo ad essere
stato inserito.
Coda con priorità: politica di accodamento nella quale ogni elemento ha una
priorità determinata da fattori esterni. L’elemento con la priorità più
alta è il primo ad essere rimosso. Abbiamo implementato un TDA Coda
con priorità che definisce le comuni operazioni richieste da una coda con
priorità.
Coda linkata: implementazione di una coda realizzata usando una lista linka-
ta.
182 Code
Alberi
Come nel caso delle altre liste linkate, un albero è costituito di nodi. Un tipo
comune di albero è l’ albero binario nel quale ciascun nodo fa riferimento a due
altri nodi che possono anche avere valore None (in questo caso è prassi comune
non indicarli nei diagrammi di stato). Questi riferimenti vengono normalmente
chiamati “rami” (o “sottoalberi”) sinistro e destro.
Come nel caso dei nodi degli altri tipi di lista anche in questo caso un nodo
possiede un contenuto. Ecco un diagramma di stato per un albero:
Il nodo principale dell’albero è chiamato radice, gli altri nodi rami e quelli
terminali foglie. Si noti come l’albero viene generalmente disegnato capovolto,
con la radice in alto e le foglie in basso.
Per rendere le cose più confuse vengono talvolta usate delle terminologie alter-
native che fanno riferimento ad un albero genealogico o alla geometria. Nel
primo caso il nodo alla sommità è detto genitore e i nodi cui esso si riferisce
figli; nodi con gli stessi genitori sono detti fratelli. Nel secondo caso parliamo
di nodi a “sinistra” e “destra”, in “alto” (verso il genitore/radice) e in “basso”
(verso i figli/foglie).
Indipendentemente dai termini usati tutti i nodi che hanno la stessa distanza
dalla radice appartengono allo stesso livello.
Come nel caso delle liste linkate gli alberi sono strutture di dati ricorsive:
184 Alberi
Un albero è:
• un albero vuoto, rappresentato da None oppure
• un nodo che contiene un riferimento ad un oggetto e due rife-
rimenti ad alberi.
def __str__(self):
return str(self.Contenuto)
Il Contenuto può essere di tipo qualsiasi ma sia Sinistra che Destra devono
essere nodi di un albero. Sinistra e Destra sono opzionali ed il loro valore di
default è None, significando con questo che non sono linkati ad altri nodi.
Come per gli altri nodi che abbiamo visto precedentemente, la stampa di un
nodo dell’albero mostra soltanto il contenuto del nodo stesso.
Un modo per costruire un albero è quello di partire dal basso verso l’alto,
allocando per primi i nodi figli:
FiglioSinistra = Albero(2)
FiglioDestra = Albero(3)
Poi creiamo il nodo genitore collegandolo ai figli:
Albero = Albero(1, FiglioSinistra, FiglioDestra);
Possiamo anche scrivere in modo più conciso questo codice invocando un co-
struttore annidato:
>>> Albero = Albero(1, Albero(2), Albero(3))
In ogni caso il risultato è l’albero presentato graficamente all’inizio del capitolo.
def Totale(Albero):
if Albero == None: return 0
return Albero.Contenuto + Totale(Albero.Sinistra) + \
Totale(Albero.Destra)
Il caso base è l’albero vuoto che non ha contenuto e che quindi ha valore 0. Il
passo successivo chiama due funzioni ricorsive per calcolare la somma dei rami
figli. Quando la serie di chiamate ricorsiva è completa la funzione ritorna il
totale.
Gli alberi di espressioni hanno molti usi tra i quali possiamo citare la rappre-
sentazione di espressioni matematiche postfisse e infisse (come abbiamo appena
visto), e le operazioni di parsing, ottimizzazione e traduzione dei programmi nei
compilatori.
186 Alberi
>>> StampaAlberoIndentato(Albero)
3
*
2
+
1
Guardando la figura dopo aver girato il foglio vedrai una versione semplificata
della figura originale.
*
+ 9
3 7
188 Alberi
def EsprProdotto(ListaToken):
a = ControllaNumero(ListaToken)
if ControllaToken(ListaToken, ’*’):
b = ControllaNumero(ListaToken)
return Albero(’*’, a, b)
else:
return a
Il secondo esempio mostra che noi consideriamo un singolo operando come una
moltiplicazione valida. Questa definizione di “prodotto” non è proprio intuitiva,
ma risulta esserci molto utile in questo caso.
*
3
*
5 13
def EsprProdotto(ListaToken):
a = ControllaNumero(ListaToken)
if ControllaToken(ListaToken, ’*’):
b = EsprProdotto(ListaToken) # questa linea e’ cambiata
return Albero(’*’, a, b)
else:
return a
190 Alberi
>>> ListaToken = [9, ’*’, ’(’, 11, ’+’, 5, ’)’, ’*’, 7, ’end’]
>>> Albero = EsprSomma(ListaToken)
>>> StampaAlberoPost(Albero)
9 11 5 + 7 * *
def ControllaNumero(ListaToken):
if ControllaToken(ListaToken, ’(’):
x = EsprSomma(ListaToken)
if not ControllaToken(ListaToken, ’)’):
raise ’BadExpressionError’, ’manca la parentesi’
return x
else:
# omettiamo il resto della funzione
All’inizio di ogni round il programma parte alla radice dell’albero e pone la prima
domanda. A seconda della risposta si muove a destra o a sinistra lungo l’albero
e continua fino a raggiungere una foglia. A questo punto tira a indovinare: se la
sua ipotesi non è corretta chiede il nome dell’animale pensato dall’operatore e
una domanda per poterlo distinguere dall’animale trovato nel nodo foglia. Poi
aggiunge il nuovo animale come nodo all’albero, assieme alla nuova domanda.
Ecco il codice:
def Animale():
# parte con una lista composta di un solo elemento
Radice = Albero("uccello")
print
if not RispostaAffermativa("Stai pensando ad un \
animale? "): break
# percorre l’albero
SottoAlbero = Radice
while SottoAlbero.RamoSinistro() != None:
Messaggio = SottoAlbero.OttieniContenuto() + "? "
if RispostaAffermativa(Messaggio):
SottoAlbero = SottoAlbero.RamoDestro()
else:
SottoAlbero = SottoAlbero.RamoSinistro()
# prova a indovinare
Ipotesi = SottoAlbero.OttieniContenuto()
Messaggio = "E’ un " + Ipotesi + "? "
if RispostaAffermativa(Messaggio):
print "Ho indovinato!"
continue
def RispostaAffermativa(Domanda):
from string import lower
Risposta = lower(raw_input(Domanda))
return (Risposta[0] == ’s’)
La condizione del ciclo esterno è 1 e questo significa che il ciclo verrà eseguito
finchè non si incontra un’istruzione break, nel caso l’operatore non stia pensando
ad un animale.
194 Alberi
Il ciclo while interno serve a percorrere l’albero dall’alto in basso, guidato dalle
risposte dell’operatore.
Se dopo aver raggiunto un nodo foglia ci troviamo a dover inserire un nuovo
animale (con la rispettiva domanda per distinguerlo da quello rappresentato dal
nodo foglia), viene effettuata una serie di operazioni:
• viene sostituito il contenuto del nodo foglia con la domanda appena inse-
rita
• il nodo originale (che era il nodo foglia) col rispettivo contenuto viene
aggiunto come figlio del nodo foglia
• il nodo rappresentato dal nuovo animale viene aggiunto come figlio allo
stesso ex-nodo foglia.
Un “piccolo” problema con questo programma è che non appena termina la sua
esecuzione tutto quello che gli abbiamo insegnato viene dimenticato...
20.8 Glossario
Albero binario: albero in cui ogni nodo si riferisce a zero, uno o due nodi
dipendenti.
Nodo radice: nodo senza genitori in un albero.
Nodo foglia: nodo senza figli in un albero.
Nodo genitore: nodo che si riferisce ad un dato nodo.
Nodo figlio: uno dei nodi cui si riferisce un altro nodo.
Nodi fratelli: nodi che hanno uno stesso genitore.
Livello: insieme dei nodi equidistanti dalla radice.
Operatore binario: operatore che prende due operandi.
Sub-espressione: espressione tra parentesi che agisce come singolo operando
in una espressione più grande.
Preordine: modo di attraversamento di un albero in cui si visita ogni nodo
prima dei suoi figli.
Notazione prefissa: notazione matematica dove ogni operatore compare pri-
ma dei suoi operandi.
Postordine: modo di attraversamento di un albero in cui si visitano i figli di
un nodo prima del nodo stesso.
Inordine: modo di attraversamento di un albero in cui si visita prima il figlio
a sinistra, poi la radice ed infine il figlio a destra.
Appendice A
Debug
• Gli errori di semantica sono i più difficili da rintracciare dato che il pro-
gramma viene eseguito ma non porta a termine le operazioni corrette.
Esempio: un’espressione può non essere valutata nell’ordine corretto tanto
da portare ad un risultato inaspettato.
Il primo passo per rimuovere un errore è capire con che tipo di errore hai a
che fare. Sebbene le sezioni seguenti siano organizzate in base al tipo di errore
alcune tecniche sono applicabili a più di una situazione.
Se malgrado i controlli non hai ottenuto risultati passa alla sezione seguente.
vede il nuovo errore di sintassi con ogni probabilità state guardando due cose
diverse.
Se questo accade, un approccio standard è quello di ricominciare da zero con un
nuovo programma tipo “Hello, World!” e controllare che questo venga eseguito.
Poi gradualmente aggiungi pezzi di codice fino ad arrivare a quello definitivo.
Ciclo infinito
Quando hai a che fare con cicli sospetti puoi sempre aggiungere alla fine del
corpo del ciclo un’istruzione print per stampare i valori delle variabili usate
nella condizione ed il valore della condizione.
Per esempio:
while x > 0 and y < 0 :
# fai qualcosa con x
# fai qualcosa con y
Ricorsione infinita
La maggior parte delle volte una ricorsione infinita porterà all’esaurimento della
memoria disponibile: il programma funzionerà finché ci sarà memoria dispo-
nibile, poi verrà mostrato un messaggio d’errore Maximum recursion depth
exceeded.
Se sospetti che una funzione o un metodo stiano causando una ricorsione infini-
ta, inizia a controllare il caso base: deve sempre essere presente una condizione
all’interno della funzione che possa ritornare senza effettuare un’ulteriore chia-
mata alla funzione stessa. Se il caso base non è presente è il caso di ripensare
l’algoritmo.
Se è presente il caso base ma sembra che il flusso di programma non lo raggiunga
mai aggiungi un’istruzione print all’inizio della funzione o metodo per stam-
parne i parametri. Se durante l’esecuzione i parametri non si muovono verso il
caso base probabilmente significa che la ricorsione non funziona.
Flusso di esecuzione
Se non sei sicuro che il flusso di esecuzione si stia muovendo lungo il programma
aggiungi un’istruzione print all’inizio di ogni funzione per stampare un mes-
saggio del tipo “entro nella funzione xxx” dove xxx è il nome della funzione.
Quando il programma è in esecuzione avrai una traccia del suo flusso.
NameError: stai provando ad usare una variabile che non esiste in questo
ambiente. Ricorda che le variabili sono locali e non puoi riferirti ad esse
al di fuori della funzione dove sono state definite.
IndexError: stai usando un indice troppo grande per accedere ad una lista, ad
una stringa o ad una tupla. Prima della posizione dell’errore aggiungi un’i-
struzione print per mostrare il valore dell’indice e la lunghezza dell’array.
L’array è della lunghezza corretta? L’indice ha il valore corretto?
Per semplificare il programma ci sono parecchie cose che puoi fare. Prima
di tutto riduci il programma per farlo lavorare su un piccolo insieme di dati.
Se stai ordinando un array usa un array piccolo. Se il programma accetta un
inserimento di dati dall’operatore cerca il più piccolo pacchetto di dati che genera
il problema.
In secondo luogo ripulisci il programma. Rimuovi il codice morto e riorganizza
il programma per renderlo il più leggibile possibile. Se sospetti che il problema
risieda in una parte profondamente annidata del codice prova a riscrivere quella
parte in modo più semplice. Se sospetti di una funzione complessa prova a
dividerla in funzioni più piccole da testare separatamente.
Spesso il solo processo di trovare un insieme di dati che causa il problema ti porta
a scoprirne la causa. Se trovi che il programma funziona in una situazione ma
non in un’altra questo ti dà un notevole indizio di cosa stia succedendo.
Riscrivere un pezzo di codice può aiutarti a trovare piccoli bug difficili da in-
dividuare soprattutto se fai un cambiamento nel codice che credi non vada ad
influire nel resto del programma e invece lo fa.
• C’è qualcosa che il programma dovrebbe fare e sembra non venga fatto?
Trova la sezione del codice incaricato di eseguire quella particolare funzione
e verifica che questo venga eseguito quando ci si aspetta.
• Succede qualcosa che non dovrebbe accadere? Trova il pezzo di codice
che esegue quella particolare funzione e controlla se esso viene eseguito
quando non dovrebbe.
A.3 Errori di semantica 201
• C’è una sezione del codice che non fa quello che ci si aspetta? Controlla
questo codice verificando di aver ben capito cosa fa, soprattutto se fa
uso di altri moduli Python. Leggi la documentazione per le funzioni che
invochi. Prova a testarle una ad una creando piccoli esempi e controllando
i risultati.
Per poter programmare devi avere un modello mentale di come il programma
lavora: se ottieni un programma che non si comporta come desideri spesso la
colpa non è nel programma in sé ma nel tuo modello mentale.
Il modo migliore per correggere il tuo modello mentale è quello di spezzare i
suoi componenti (di solito le funzioni ed i metodi) e testare ogni componente in
modo indipendente. Quando hai trovato la discrepanza tra il tuo modello e la
realtà puoi risolvere il problema.
Dovresti costruire e testare i componenti man mano che sviluppi il programma
cosı̀ ti troveresti, in caso di problemi, soltanto con piccole parti di codice da
controllare.
y = x/(2*math.pi);
Quando non sei sicuro dell’ordine di valutazione usa le parentesi. Non solo il
programma sarà corretto ma sarà anche più leggibile da parte di chi non ha
memorizzato le regole di precedenza.
return self.Mano[i].TrisRimossi()
puoi scrivere:
Conteggio = self.Mano[i].TrisRimossi()
return Conteggio
Talvolta è necessario parecchio tempo per trovare un bug ed è molto più efficace
una ricerca fatta dopo aver lasciato sgombra la mente per qualche tempo. Alcuni
tra i posti migliori per trovare i bug (senza bisogno di un computer!) sono i treni,
le docce ed il letto, appena prima di addormentarsi.
• Se c’è un messaggio d’errore che cosa e che parte del programma indica?
• Cos’è stata l’ultima cosa che hai fatto prima che si verificasse l’errore?
Quali sono le ultime righe di codice che hai scritto o quali sono i nuovi
dati che fanno fallire il test?
• Cosa hai già provato e che ipotesi hai già escluso con le tue prove?
Quando hai trovato il bug fermati un secondo e cerca di capire come avresti po-
tuto trovarlo più in fretta. La prossima volta che riscontrerai un comportamento
simile, questo sarà molto utile.
Appendice B
Le frazioni, conosciute anche come numeri razionali, sono numeri che si possono
esprimere come rapporto tra numeri interi, come nel caso di 5/6. Il numero
superiore si chiama numeratore, quello inferiore denominatore.
Iniziamo con una definizione della classe Frazione con un metodo di inizializ-
zazione che fornisce un numeratore ed un denominatore interi.
class Frazione:
def __init__(self, Numeratore, Denominatore=1):
self.Numeratore = Numeratore
self.Denominatore = Denominatore
class Frazione:
...
def __str__(self):
return "%d/%d" % (self.Numeratore, self.Denominatore)
Per testare ciò che abbiamo fatto finora scriviamo tutto in un file chiamato
frazione.py (o qualcosa di simile; l’importante è che per te abbia un nome che
206 Creazione di un nuovo tipo di dato
__radd__ = __add__
Possiamo testare questi metodi con frazioni e interi:
>>> print Frazione(5,6) + Frazione(5,6)
60/36
>>> print Frazione(5,6) + 3
23/6
208 Creazione di un nuovo tipo di dato
Questa definizione ricorsiva può essere espressa in modo conciso con una fun-
zione:
def MCD(m, n):
if m % n == 0:
return n
else:
return MCD(n, m%n)
Nella prima riga del corpo usiamo l’operatore modulo per controllare la divisi-
bilità. Nell’ultima riga lo usiamo per calcolare il resto della divisione.
Dato che tutte le operazioni che abbiamo scritto finora creano un nuovo og-
getto Frazione come risultato potremmo inserire la riduzione nel metodo di
inizializzazione:
class Frazione:
def __init__(self, Numeratore, Denominatore=1):
mcd = MCD(numeratore, Denominatore)
self.Numeratore = Numeratore / mcd
self.Denominatore = Denominatore / mcd
Quando creiamo una nuova Frazione questa sarà immediatamente ridotta alla
sua forma più semplice:
>>> Frazione(100,-36)
-25/9
Una bella caratteristica di MCD è che se la frazione è negativa il segno meno è
sempre spostato automaticamente al numeratore.
B.4 Confronto di frazioni 209
B.5 Proseguiamo
Logicamente non abbiamo ancora finito. Dobbiamo ancora implementare la sot-
trazione ridefinendo sub e la divisione con il corrispondente metodo div .
Un modo per gestire queste operazioni è quello di implementare la negazio-
ne ridefinendo neg e l’inversione con invert : possiamo infatti sottrarre
sommando al primo operando la negazione del secondo, e dividere moltiplicando
il primo operando per l’inverso del secondo. Poi dobbiamo fornire rsub e
rdiv .
Purtroppo non possiamo usare la scorciatoia già vista nel caso di addizione e
moltiplicazione dato che sottrazione e divisione non sono commutative. Non pos-
siamo semplicemente assegnare rsub e rdiv a lle corrispondenti sub
e div , dato che in queste operazioni l’ordine degli operandi fa la differenza...
Per gestire la negazione unaria, che non è altro che l’uso del segno meno con
un singolo operando (da qui il termine “unaria” usato nella definizione), sarà
necessario ridefinire il metodo neg .
Potremmo anche calcolare le potenze ridefinendo pow ma l’implementazione
in questo caso è un po’ complessa: se l’esponente non è un intero, infatti, può non
210 Creazione di un nuovo tipo di dato
B.6 Glossario
Massimo comune divisore(MCD): il più grande numero positivo intero che
divide senza resto sia il numeratore che il denominatore di una frazione.
Riduzione: trasformazione di una frazione nella sua forma più semplice, grazie
alla divisione di numeratore e denominatore per il loro MCD.
class Punto:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return ’(’ + str(self.x) + ’, ’ + str(self.y) + ’)’
def reverse(self):
self.x , self.y = self.y, self.x
def DirittoERovescio(Stringa):
import copy
Rovescio = copy.copy(Stringa)
Rovescio.reverse()
print str(Stringa) + str(Rovescio)
212 Listati dei programmi
# -------------------
# Versione funzionale
# -------------------
# t = Tempo(3,14)
# s = Tempo(8,12,15)
# print SommaTempi(s, t)
def ConverteInSecondi(Orario):
Minuti = Orario.Ore * 60 + Orario.Minuti
Secondi = Minuti * 60 + Orario.Secondi
return Secondi
def ConverteInTempo(Secondi):
Orario = Tempo()
Orario.Ore = Secondi / 3600
Secondi = Secondi - Orario.Ore * 3600
Orario.Minuti = Secondi / 60
Secondi = Secondi - Orario.Minuti * 60
Orario.Secondi = Secondi
return Orario
# ------------------
# Versione a oggetti
# ------------------
# con modifica di uno degli oggetti:
# t = Tempo(3,14)
# s = Tempo(8,12,15)
# t.AggiungiTempo(s)
# print t
# in alternativa, senza modificare t:
# a = Tempo()
# a.SommaTempi(t, s)
# print a
class Tempo:
def __init__(self, Ore=0, Minuti=0, Secondi=0):
self.Ore = Ore
self.Minuti = Minuti
self.Secondi = Secondi
def __str__(self):
return str(self.Ore) + ":" + str(self.Minuti) + ":" + \
C.3 Carte, mazzi e giochi 213
str(self.Secondi)
def ConverteInSecondi(self):
Minuti = self.Ore * 60 + self.Minuti
Secondi = Minuti * 60 + self.Secondi
return Secondi
class Carta:
def __str__(self):
return (self.ListaRanghi[self.Rango] + " di " + \
self.ListaSemi[self.Seme])
# controlla il seme
if self.Seme > Altro.Seme: return 1
if self.Seme < Altro.Seme: return -1
class Mazzo:
def __init__(self):
self.Carte = []
for Seme in range(4):
for Rango in range(1, 14):
self.Carte.append(Carta(Seme, Rango))
def StampaMazzo(self):
for Carta in self.Carte:
print Carta
def __str__(self):
s = ""
for i in range(len(self.Carte)):
s = s + " "*i + str(self.Carte[i]) + "\n"
return s
def Mischia(self):
import random
NumCarte = len(self.Carte)
for i in range(NumCarte):
j = random.randrange(i, NumCarte)
self.Carte[i], self.Carte[j] = self.Carte[j], self.Carte[i]
def PrimaCarta(self):
return self.Carte.pop()
def EVuoto(self):
return (len(self.Carte) == 0)
class Mano(Mazzo):
def AggiungiCarta(self,Carta) :
self.Carte.append(Carta)
def __str__(self):
s = "La mano di " + self.Nome
if self.EVuoto():
s = s + " e’ vuota\n"
else:
s = s + " contiene queste carte:\n"
return s + Mazzo.__str__(self)
class GiocoCarte:
def __init__(self):
self.Mazzo = Mazzo()
self.Mazzo.Mischia()
class ManoOldMaid(Mano):
def RimuoviCoppie(self):
Conteggio = 0
216 Listati dei programmi
CarteOriginali = self.Carte[:]
for CartaOrig in CarteOriginali:
CartaDaCercare = Carta(3-CartaOrig.Seme, CartaOrig.Rango)
if CartaDaCercare in self.Carte:
self.Carte.remove(CartaOrig)
self.Carte.remove(CartaDaCercare)
print "Mano di %s: %s elimina %s" % \
(self.Nome,CartaOrig,CartaDaCercare)
Conteggio = Conteggio + 1
return Conteggio
class GiocoOldMaid(GiocoCarte):
def RimuoveTutteLeCoppie(self):
Conteggio = 0
for Mano in self.Mani:
Conteggio = Conteggio + Mano.RimuoveCoppie()
return Conteggio
C.4 Liste linkate 217
def StampaInversa(Lista):
if Lista == None: return
Testa = Lista
Coda = Lista.ProssimoNodo
StampaInversa(Coda)
print Testa,
def StampaInversaFormato(Lista) :
print "[",
if Lista != None :
Testa = Lista
Coda = Lista.ProssimoNodo
StampaInversa(Coda)
print Testa,
print "]",
def RimuoviSecondo(Lista):
if Lista == None: return
Primo = Lista
Secondo = Lista.ProssimoNodo
218 Listati dei programmi
class Nodo:
def __str__(self):
return str(self.Contenuto)
def StampaInversa(self):
if self.ProssimoNodo != None:
Coda = self.ProssimoNodo
Coda.StampaInversa()
print self.Contenuto,
class ListaLinkata:
def __init__(self) :
self.Lunghezza = 0
self.Testa = None
def StampaInversa(self):
print "[",
if self.Testa != None:
self.Testa.StampaInversa()
print "]",
def Pop(self):
return self.Elementi.pop()
def EVuota(self):
return (self.Elementi == [])
def ValutaPostfissa(Espressione):
import re
ListaToken = re.split("([^0-9])", Espressione)
Pila1 = Pila()
for Token in ListaToken:
if Token == ’’ or Token == ’ ’:
continue
if Token == ’+’:
Somma = Pila1.Pop() + Pila1.Pop()
Pila1.Push(Somma)
elif Token == ’*’:
Prodotto = Pila1.Pop() * Pila1.Pop()
Pila1.Push(Prodotto)
else:
Pila1.Push(int(Token))
return Pila1.Pop()
C.6 Alberi
class Albero:
def __init__(self, Contenuto, Sinistra=None, Destra=None):
self.Contenuto = Contenuto
self.Sinistra = Sinistra
self.Destra = Destra
def __str__(self):
return str(self.Contenuto)
def Totale(Albero):
if Albero == None: return 0
return Albero.Contenuto + Totale(Albero.Sinistra) + \
Totale(Albero.Destra)
220 Listati dei programmi
def StampaAlberoPre(Albero):
if Albero == None: return
print Albero.Contenuto,
StampaAlberoPre(Albero.Sinistra)
StampaAlberoPre(Albero.Destra)
def StampaAlberoPost(Albero):
if Albero == None: return
StampaAlberoPost(Albero.Sinistra)
StampaAlberoPost(Albero.Destra)
print Albero.Contenuto,
def StampaAlberoIn(Albero):
if Albero == None: return
StampaAlberoIn(Albero.Sinistra)
print Albero.Contenuto,
StampaAlberoIn(Albero.Destra)
def ControllaNumero(ListaToken):
if ControllaToken(ListaToken, ’(’):
x = EsprSomma(ListaToken) # ricava la
# sub-espressione
if not ControllaToken(ListaToken, ’)’): # rimuove la
# parentesi chiusa
raise ’BadExpressionError’, ’manca la parentesi’
return x
else:
x = ListaToken[0]
if type(x) != type(0): return None
ListaToken[0:1] = []
return Albero(x, None, None)
def EsprProdotto(ListaToken):
a = ControllaNumero(ListaToken)
if ControllaToken(ListaToken, ’*’):
b = EsprProdotto(ListaToken)
C.7 Indovina l’animale 221
return Albero(’*’, a, b)
else:
return a
def EsprSomma(ListaToken):
a = EsprProdotto(ListaToken)
if ControllaToken(ListaToken, ’+’):
b = EsprSomma(ListaToken)
return Albero(’+’, a, b)
else:
return a
def RispostaAffermativa(Domanda):
from string import lower
Risposta = lower(raw_input(Domanda))
return (Risposta[0] == ’s’)
def Animale():
# parte con una lista composta di un solo elemento
Radice = Albero("uccello")
# percorre l’albero
SottoAlbero = Radice
while SottoAlbero.RamoSinistro() != None:
Messaggio = SottoAlbero.OttieniContenuto() + "? "
if RispostaAffermativa(Messaggio):
SottoAlbero = SottoAlbero.RamoDestro()
else:
SottoAlbero = SottoAlbero.RamoSinistro()
# prova a indovinare
Ipotesi = SottoAlbero.OttieniContenuto()
Messaggio = "E’ un " + Ipotesi + "? "
if RispostaAffermativa(Messaggio):
print "Ho indovinato!"
continue
222 Listati dei programmi
class Frazione:
def __str__(self):
return "%d/%d" % (self.Numeratore, self.Denominatore)
__radd__ = __add__
def __repr__(self):
return self.__str__()
Appendice D
Altro materiale
Ci sono moduli Python che permettono l’accesso a file remoti via ftp e
moduli che consentono di ricevere e spedire email. Python è ampiamente
usato anche per la gestione di form di introduzione dati nei web server.
• I database sono paragonabili a dei super-file dove i dati sono memorizzati
secondo schemi predefiniti e sono accessibili in vari modi. Python è dotato
di un certo numero di moduli per accedere a dati di diversi tipi di database,
sia Open Source che commerciali.
• La programmazione a thread permette di eseguire diversi flussi di program-
ma allo stesso tempo a partire da un unico programma. Se hai presente
come funziona un browser per Internet puoi farti un’idea di cosa questo
significhi: in un browser vengono caricate più pagine contemporaneamente
e mentre ne guardi una il caricamento delle altre prosegue in modo quasi
del tutto trasparente.
• Quando ci troviamo alle prese con necessità particolari ed è indispensa-
bile una maggiore velocità di esecuzione Python può essere integrato da
moduli scritti in altri linguaggi, tipo il C ed il C++. Queste estensioni for-
mano la base dei moduli presenti nelle librerie standard di Python. Anche
se le procedure per l’integrazione di questi moduli possono essere piut-
tosto complesse esiste uno strumento chiamato SWIG (Simplified Wrap-
per and Interface Generator) che permette di semplificare enormemente
l’operazione.
Per quanto concerne i libri, la bibliografia su Python, in italiano, si sta via via
ampliando. Prova a chiedere in libreria: non ha neanche tanto senso indicare
D.2 Informatica in generale 227
Per quanto concerne i libri in lingua inglese tra gli altri si distinguono quelli del
nostro Alex Martelli. Una ricerca in www.amazon.com presenta circa 200 titoli
disponibili. Tra questi consigliamo:
• Python Pocket Reference di Mark Lutz really, sebbene non cosı̀ completo
come la Python Essential Reference è un ottimo riferimento per le funzioni
usate più frequentemente.
1 Una lode meritata va al traduttore che ha dato il meglio di sé, districandosi con maestria
Preamble
The purpose of this License is to make a manual, textbook, or other written do-
cument “free” in the sense of freedom: to assure everyone the effective freedom
to copy and redistribute it, with or without modifying it, either commercially or
noncommercially. Secondarily, this License preserves for the author and publi-
sher a way to get credit for their work, while not being considered responsible
for modifications made by others.
This License is a kind of “copyleft,” which means that derivative works of the
document must themselves be free in the same sense. It complements the GNU
General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software,
because free software needs free documentation: a free program should come
with manuals providing the same freedoms that the software does. But this
License is not limited to software manuals; it can be used for any textual work,
regardless of subject matter or whether it is published as a printed book. We
recommend this License principally for works whose purpose is instruction or
reference.
230 GNU Free Documentation License
You may copy and distribute the Document in any medium, either commercially
or noncommercially, provided that this License, the copyright notices, and the
license notice saying this License applies to the Document are reproduced in
all copies, and that you add no other conditions whatsoever to those of this
License. You may not use technical measures to obstruct or control the reading
or further copying of the copies you make or distribute. However, you may
accept compensation in exchange for copies. If you distribute a large enough
number of copies you must also follow the conditions in Section 3.
You may also lend copies, under the same conditions stated above, and you may
publicly display copies.
If you publish printed copies of the Document numbering more than 100, and
the Document’s license notice requires Cover Texts, you must enclose the copies
in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts
on the front cover, and Back-Cover Texts on the back cover. Both covers must
also clearly and legibly identify you as the publisher of these copies. The front
cover must present the full title with all words of the title equally prominent
and visible. You may add other material on the covers in addition. Copying
with changes limited to the covers, as long as they preserve the title of the
Document and satisfy these conditions, can be treated as verbatim copying in
other respects.
If the required texts for either cover are too voluminous to fit legibly, you should
put the first ones listed (as many as fit reasonably) on the actual cover, and
continue the rest onto adjacent pages.
It is requested, but not required, that you contact the authors of the Document
well before redistributing any large number of copies, to give them a chance to
provide you with an updated version of the Document.
232 GNU Free Documentation License
E.4 Modifications
You may copy and distribute a Modified Version of the Document under the
conditions of Sections 2 and 3 above, provided that you release the Modified
Version under precisely this License, with the Modified Version filling the role
of the Document, thus licensing distribution and modification of the Modified
Version to whoever possesses a copy of it. In addition, you must do these things
in the Modified Version:
• Use in the Title Page (and on the covers, if any) a title distinct from that
of the Document, and from those of previous versions (which should, if
there were any, be listed in the History section of the Document). You
may use the same title as a previous version if the original publisher of
that version gives permission.
• List on the Title Page, as authors, one or more persons or entities respon-
sible for authorship of the modifications in the Modified Version, together
with at least five of the principal authors of the Document (all of its
principal authors, if it has less than five).
• State on the Title page the name of the publisher of the Modified Version,
as the publisher.
• Preserve in that license notice the full lists of Invariant Sections and
required Cover Texts given in the Document’s license notice.
• Preserve the section entitled “History,” and its title, and add to it an item
stating at least the title, year, new authors, and publisher of the Modified
Version as given on the Title Page. If there is no section entitled “History”
in the Document, create one stating the title, year, authors, and publisher
of the Document as given on its Title Page, then add an item describing
the Modified Version as stated in the previous sentence.
• Preserve the network location, if any, given in the Document for public
access to a Transparent copy of the Document, and likewise the network
locations given in the Document for previous versions it was based on.
These may be placed in the “History” section. You may omit a network
location for a work that was published at least four years before the Do-
cument itself, or if the original publisher of the version it refers to gives
permission.
E.5 Combining Documents 233
the title of each such section unique by adding at the end of it, in parentheses,
the name of the original author or publisher of that section if known, or else a
unique number. Make the same adjustment to the section titles in the list of
Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections entitled “History” in the
various original documents, forming one section entitled “History”; likewise
combine any sections entitled “Acknowledgements,” and any sections entitled
“Dedications.” You must delete all sections entitled “Endorsements.”
E.8 Translation
Translation is considered a kind of modification, so you may distribute transla-
tions of the Document under the terms of Section 4. Replacing Invariant Sec-
tions with translations requires special permission from their copyright holders,
but you may include translations of some or all Invariant Sections in addition to
the original versions of these Invariant Sections. You may include a translation
of this License provided that you also include the original English version of
E.9 Termination 235
this License. In case of a disagreement between the translation and the original
English version of this License, the original English version will prevail.
E.9 Termination
You may not copy, modify, sublicense, or distribute the Document except as
expressly provided for under this License. Any other attempt to copy, modify,
sublicense, or distribute the Document is void, and will automatically terminate
your rights under this License. However, parties who have received copies, or
rights, from you under this License will not have their licenses terminated so
long as such parties remain in full compliance.
“no Front-Cover Texts” instead of “Front-Cover Texts being LIST”; likewise for
Back-Cover Texts.
If your document contains nontrivial examples of program code, we recommend
releasing these examples in parallel under your choice of free software license,
such as the GNU General Public License, to permit their use in free software.
Indice analitico