Python Learn
Python Learn
Charles R. Severance
Crediti
Cronologia di stampa
Prefazione
È abbastanza naturale per gli accademici, che si sentono dire continuamente “pub-
blica o muori”, voler sempre creare dal nulla qualcosa che sia una loro nuova crea-
zione. Questo libro è un esperimento: non partire da zero, ma invece “remixare” il
libro dal titolo “Think Python: How to Think Like a Computer Scientist” scritto
da Allen B. Downey, Jeff Elkner ed altri.
Nel dicembre del 2009 mi stavo preparando a tenere il corso “SI502 - Networked
Programming” presso l’Università del Michigan per il quinto semestre consecutivo
e ho deciso che era tempo di scrivere un libro di testo su Python che si concentrasse
sull’esplorazione dei dati invece che sulla comprensione di algoritmi ed astrazioni.
Il mio obiettivo, nel SI502, era quello di insegnare le tecniche fondamentali di
analisi dei dati utilizzando Python. Pochi dei miei studenti avevano in progetto di
diventare programmatori professionisti, altri pianificavano di diventare bibliotecari,
manager, avvocati, biologi, economisti o altro e desideravano imparare ad utilizzare
abilmente le tecnologie nei rispettivi campi professionali.
Non avendo mai trovato un libro su Python che fosse perfettamente orientato
alla gestione dei dati per il mio corso, decisi di scriverne uno. Fortunatamente,
tre settimane prima che iniziassi a lavorarci approfittando delle vacanze, in una
riunione di facoltà, il Dr. Atul Prakash mi mostrò il libro “Think Python” che lui
stesso aveva usato per il suo corso. Si tratta di un testo di Informatica ben scritto,
focalizzato su brevi spiegazioni dirette che facilitano l’apprendimento.
La struttura complessiva del libro è stata modificata per arrivare a gestire i proble-
mi di analisi dei dati il più rapidamente possibile e per fornire una serie di esercizi
ed esempi sull’analisi dei dati fin dall’inizio.
I capitoli 2-10 sono simili al libro Think Python nonostante siano state fatte im-
portanti modifiche. Gli esempi e gli esercizi orientati alla gestione di numeri sono
stati sostituiti con esercitazioni orientate ai dati. Gli argomenti sono presentati in
un ordine tale da fornire soluzioni di analisi dei dati via via sempre più sofisticate.
Alcuni argomenti come “try” e “except” sono stati anticipati e presentati come
parte del capitolo sull’esecuzione condizionale. Piuttosto che essere trattate già
dall’inizio in maniera astratta, le funzioni sono state trattate più superficialmente
sino al momento in cui sono diventate necessarie per gestire la complessità dei pro-
grammi. Quasi tutte le funzioni definibili dall’utente sono state rimosse dai codici
di esempio ed esercitazione al di fuori del Capitolo 4. La parola “ricorsivo”1 non
viene mai utilizzata in alcuna parte del libro.
Tutto il materiale nei capitoli 1 e 11-16 è nuovo di zecca, ed è focalizzato sull’uso di
Python in applicazioni del mondo reale e fornisce semplici esempi per l’analisi dei
dati, comprendendo espressioni regolari per la ricerca e l’analisi, l’automatizzazione
delle attività sul computer, il recupero dei dati attraverso la rete, il prelievo di dati
da pagine web, l’utilizzo di servizi web, l’analisi di dati in formato XML e JSON,
la creazione e l’utilizzo di database utilizzando lo Structured Query Language e la
rappresentazione di dati.
1 Ad eccezione, ovviamente, di questa riga.
iv
v
vi INDICE
2.11 Commenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.12 Scegliere nomi mnemonici delle variabili . . . . . . . . . . . . . . . 26
2.13 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.14 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.15 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3 Esecuzione condizionale 31
3.1 Espressioni booleane . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.2 Operatori logici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.3 Esecuzione condizionale . . . . . . . . . . . . . . . . . . . . . . . . 32
3.4 Esecuzione alternativa . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.5 Condizioni concatenate . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.6 Condizioni nidificate . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.7 Gestione delle eccezioni usando try ed except . . . . . . . . . . . . 36
3.8 Valutazione di un cortocircuito di espressioni logiche . . . . . . . . 38
3.9 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.10 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.11 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4 Funzioni 43
4.1 Chiamate di funzione . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.2 Funzioni integrate . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.3 Funzioni di conversione dei tipi di dato . . . . . . . . . . . . . . . . 44
4.4 Funzioni matematiche . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.5 Numeri casuali . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.6 Aggiungere nuove funzioni . . . . . . . . . . . . . . . . . . . . . . . 47
4.7 Definizioni e usi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.8 Flusso di esecuzione . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.9 Parametri e argomenti . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.10 Funzioni produttive e funzioni vuote . . . . . . . . . . . . . . . . . . 51
4.11 Perché le funzioni? . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.12 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.13 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.14 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
INDICE vii
5 Iterazione 57
5.1 Aggiornamento delle variabili . . . . . . . . . . . . . . . . . . . . . 57
5.2 L’istruzione while . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.3 Cicli infiniti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.4 “Cicli infiniti” e break . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.5 Fermare le iterazioni con continue . . . . . . . . . . . . . . . . . . 59
5.6 Cicli definiti con l’uso di for . . . . . . . . . . . . . . . . . . . . . 60
5.7 Schemi di ciclo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
5.7.1 Cicli per contare e sommare . . . . . . . . . . . . . . . . . . . 61
5.7.2 Cicli di massimo e minimo . . . . . . . . . . . . . . . . . . . 62
5.8 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.9 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.10 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6 Stringhe 65
6.1 Una stringa è una sequenza . . . . . . . . . . . . . . . . . . . . . . 65
6.2 Ottenere la lunghezza di una stringa usando len . . . . . . . . . . 66
6.3 Scorrere una stringa con un ciclo . . . . . . . . . . . . . . . . . . . 66
6.4 Segmenti di stringhe . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.5 Le stringhe sono immutabili . . . . . . . . . . . . . . . . . . . . . . 68
6.6 Cicli e conteggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.7 L’operatore in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.8 Comparazione di stringhe . . . . . . . . . . . . . . . . . . . . . . . 69
6.9 Metodi delle stringhe . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.10 Analisi delle stringhe . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.11 Operatore di formato . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.12 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.13 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.14 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7 File 77
7.1 Persistenza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.2 Aprire i file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7.3 File di testo e righe . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
7.4 Lettura dei file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
viii INDICE
8 Elenchi 89
8.1 Un elenco è una sequenza . . . . . . . . . . . . . . . . . . . . . . . 89
8.2 Gli elenchi sono mutabili . . . . . . . . . . . . . . . . . . . . . . . . 90
8.3 Scorrere un elenco . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
8.4 Operazioni sugli elenchi . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.5 Slicing degli elenchi . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
8.6 Metodi degli elenchi . . . . . . . . . . . . . . . . . . . . . . . . . . 92
8.7 Eliminazione di elementi . . . . . . . . . . . . . . . . . . . . . . . . 93
8.8 Elenchi e funzioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
8.9 Elenchi e stringhe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
8.10 Analisi di righe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
8.11 Oggetti e valori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
8.12 Alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.13 Elenchi come argomenti . . . . . . . . . . . . . . . . . . . . . . . . 98
8.14 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
8.15 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
8.16 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
9 Dizionari 107
9.1 Dizionario come insieme di contatori . . . . . . . . . . . . . . . . . 109
9.2 Dizionari e file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
9.3 Cicli e dizionari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
9.4 Analisi avanzata del testo . . . . . . . . . . . . . . . . . . . . . . . 113
9.5 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
9.6 Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
9.7 Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
INDICE ix
10 Tuple 117
10.1 Le tuple sono immutabili . . . . . . . . . . . . . . . . . . . . . . . 117
10.2 Confronto tra tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
10.3 Assegnazione di tupla . . . . . . . . . . . . . . . . . . . . . . . . . 120
10.4 Dizionari e tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
10.5 Assegnazione multipla con dizionari . . . . . . . . . . . . . . . . . 122
10.6 Le parole più comuni . . . . . . . . . . . . . . . . . . . . . . . . . . 123
10.7 Usare tuple come chiavi nei dizionari . . . . . . . . . . . . . . . . . 124
10.8 Sequenze: stringhe, elenchi e tuple - Oh cavolo! . . . . . . . . . . . 124
10.9 Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
10.10Glossario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
10.11Esercizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
A Contributi 221
A.1 Elenco dei collaboratori di Python for Everybody . . . . . . . . . . . 221
A.2 Elenco dei Traduttori di Python per tutti . . . . . . . . . . . . . . . 221
A.3 Elenco dei collaboratori per Python for Informatics . . . . . . . . . . 221
A.4 Prefazione per “Think Python” . . . . . . . . . . . . . . . . . . . . 222
A.4.1 La strana storia di “Think Python” . . . . . . . . . . . . . 222
A.4.2 Ringraziamenti per “Think Python” . . . . . . . . . . . . . 223
A.5 Elenco dei collaboratori di “Think Python” . . . . . . . . . . . . . 223
1
2 CAPITOLO 1. PERCHÉ DOVRESTI IMPARARE A PROGRAMMARE?
Per esempio leggere i primi tre paragrafi di questo capitolo ed individuare quante
volte venga ripetuta la parola più comune. Anche se sei in grado di leggere e capire
le parole in pochi secondi, contarle è piuttosto difficoltoso in quanto non è il tipo di
problema per cui la nostra mente è stata concepita. Per un computer è l’opposto:
leggere e capire il testo è difficoltoso mentre è molto più semplice contare le parole
e indicarci quante volte è ripetuta la parola più usata.
python words.py
Enter file:words.txt
to 16
Per ora, la nostra motivazione principale non è guadagnare soldi o soddisfare altri
utenti, ma piuttosto essere più efficienti nella gestione di dati e informazioni che
incontreremo. In un primo momento, sarai sia lo sviluppatore sia l’utente finale
1.2. ARCHITETTURA HARDWARE DEL COMPUTER 3
What
Software Next?
Periferiche Central
di input e Processing Rete
di output Unit
Memoria Memoria
principale secondaria
rete come ad una sorta di supporto molto lento per archiviare e recuperare
dati che potrebbe non essere sempre disponibile. Quindi, in un certo senso,
la rete è un tipo più lento e talvolta inaffidabile di memoria secondaria.
What
Software Next?
Periferiche Central
di input e Processing Rete
di output Unit
Memoria
principale Memoria
secondaria
Dovrai essere colui che risponderà alla domanda posta dalla CPU: “Quale è la
prossima istruzione?”. Ma diciamocelo: sarebbe molto scomodo rimpicciolirsi a 5
mm di altezza e introdursi nel computer solo per poter dare comandi tre miliardi di
volte al secondo. È più semplice scrivere in anticipo le istruzioni. Chiamiamo pro-
gramma queste istruzioni memorizzate e programmazione l’atto di scrivere queste
istruzioni e fare in modo che siano corrette.
print('Hello World!')
Python è pronto per conversare con voi: il prompt >>> è il modo di chiedere “Cosa
vuoi che faccia dopo?” dell’interprete Python. Tutto quello che devi sapere è come
comunicare in Python.
Supponiamo, per esempio, che tu non sappia nemmeno le più basilari parole o
frasi del linguaggio Python. Potresti voler utilizzare le linee guida che usano gli
astronauti quando, atterrati su un pianeta lontano, vogliano comunicare con gli
autoctoni:
Non sta andando molto bene: a meno che tu non pensi qualcosa velocemente, è
probabile che gli abitanti del pianeta ti pugnalino con le loro lance, ti mettano su
uno spiedo, ti arrostiscano sopra un fuoco e ti trasformino nel piatto principale
della cena.
Fortunatamente hai portato con te una copia di questo libro e hai il dito fermo in
questa pagina. Riprova:
>>> print('You must be the legendary god that comes from the sky')
You must be the legendary god that comes from the sky
>>> print('We have been waiting for you for a long time')
We have been waiting for you for a long time
>>> print('Our legend says you will be very tasty with mustard')
Our legend says you will be very tasty with mustard
>>> print 'We will have a feast tonight unless you say
File "<stdin>", line 1
print 'We will have a feast tonight unless you say
^
SyntaxError: Missing parentheses in call to 'print'
>>>
La conversazione stava andando così bene, poi quando hai fatto l’errore più piccolo
che si possa fare usando il linguaggio Python, “lui” ha tirato nuovamente fuori gli
artigli.
A questo punto, dovresti esserti reso conto che anche se Python è incredibilmente
complesso, potente e schizzinoso sulla sintassi da utilizzare per comunicare con
“lui”, non è affatto intelligente. In realtà stai solo avendo una conversazione con
te stesso, ma usando una sintassi corretta.
In un certo senso, quando utilizzi un programma scritto da qualcun altro, la con-
versazione si realizza tra te e gli altri sviluppatori, con Python come intermediario.
Per chi scrive codice Python è solo un modo di esprimere come dovrebbe procedere
la conversazione. In soli pochi altri capitoli, sarai uno di quei programmatori che
parlano agli utenti tramite il programma Python.
Prima di lasciare la nostra prima conversazione con l’interprete Python, dovresti
probabilmente conoscere il modo corretto per dire “arrivederci” alla fine di una
conversazione con gli abitanti del Pianeta Python:
>>> good-bye
Traceback (most recent call last):
8 CAPITOLO 1. PERCHÉ DOVRESTI IMPARARE A PROGRAMMARE?
Noterete che, nei due tentativi errati precedenti, l’errore è diverso. Nel secondo
caso, if è una parola riservata e Python, vedendo la parola riservata, pensava che
stessimo cercando di dirgli qualcosa, utilizzando una sintassi errata.
Il modo corretto per dire “arrivederci” a Python è digitare quit() al prompt inte-
rattivo >>>. Credo che ti ci sarebbe voluto un po’ per indovinarlo, quindi avere un
libro sull’argomento a portata di mano probabilmente si rivelerà utile.
001010001110100100101010000001111
11100110000011101010010101101101
...
Il linguaggio macchina a prima vista sembra abbastanza semplice, dato che ci sono
solo zeri e uno, ma la sua sintassi è molto più complessa e molto più intricata
di quella del Python. Pertanto pochissimi programmatori scrivono in linguaggio
macchina. Piuttosto si preferisce utilizzare vari “traduttori” per consentire agli
sviluppatori di scrivere in linguaggi di alto livello come Python o JavaScript e
lasciare agli interpreti la conversione in linguaggio macchina per l’esecuzione da
parte della CPU.
Dato che il linguaggio macchina è legato all’hardware del computer, tale linguaggio
non è trasferibile tra diversi tipi di hardware. Contrariamente i programmi scritti
in linguaggi di alto livello possono essere spostati su architetture diverse utilizzando
l’interprete presente sulla nuova macchina o ricompilando il codice per creare una
versione in linguaggio macchina del programma adatta al nuovo sistema.
Questi traduttori di linguaggi di programmazione si dividono in due categorie
generali: (1) interpreti e (2) compilatori.
Un interprete legge il codice sorgente del programma così come è stato scritto
dal programmatore: analizza il codice sorgente e interpreta le istruzioni al volo.
1.6. TERMINOLOGIA: INTERPRETE E COMPILATORE 9
>>> x = 6
>>> print(x)
6
>>> y = x * 7
>>> print(y)
42
>>>
^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^B^@^C^@^A^@^@^@\xa0\x82
^D^H4^@^@^@\x90^]^@^@^@^@^@^@4^@ ^@^G^@(^@$^@!^@^F^@
^@^@4^@^@^@4\x80^D^H4\x80^D^H\xe0^@^@^@\xe0^@^@^@^E
^@^@^@^D^@^@^@^C^@^@^@^T^A^@^@^T\x81^D^H^T\x81^D^H^S
^@^@^@^S^@^@^@^D^@^@^@^A^@^@^@^A\^D^HQVhT\x83^D^H\xe8
....
Non è semplice leggere o scrivere in linguaggio macchina, quindi è utile che si possa
disporre di interpreti e compilatori che permettano di scrivere in linguaggi di alto
livello come Python o C.
10 CAPITOLO 1. PERCHÉ DOVRESTI IMPARARE A PROGRAMMARE?
C:\Python35\python.exe
Questo è più di quanto tu abbia davvero bisogno di sapere per essere un program-
matore Python, ma a volte è utile rispondere subito a quelle piccole fastidiose
domande.
Il “csev$” è il prompt del sistema operativo e “cat hello.py” mostra che il file
“hello.py” ha una riga di codice Python per stampare una stringa.
Avvia l’interprete di Python dicendogli di leggere il codice sorgente dal file “hel-
lo.py” invece di chiederti in modo interattivo linee di codice.
Noterai che non è stato necessario inserire quit() alla fine del programma Python.
Quando Python legge il tuo codice sorgente da un file, sa di doversi fermare quando
raggiunge la fine del file.
1.8. COS’È UN PROGRAMMA? 11
the clown ran after the car and the car ran into the tent
and the tent fell down on the clown and the car
bigcount = None
bigword = None
for word, count in list(counts.items()):
if bigcount is None or count > bigcount:
bigword = word
bigcount = count
print(bigword, bigcount)
# Code: http://www.py4e.com/code3/words.py
12 CAPITOLO 1. PERCHÉ DOVRESTI IMPARARE A PROGRAMMARE?
Non hai nemmeno bisogno di conoscere Python per utilizzare questo script. Hai
bisogno di completare il Capitolo 10 di questo libro per comprendere appieno le
fantastiche tecniche utilizzate per realizzare il programma. Tu sei l’utente finale,
usa semplicemente il programma e stupisciti della sua utilità e di come ti abbia
risparmiato così tanto lavoro manuale. Basta digitare il codice in un file chiamato
words.py oppure scaricare il codice sorgente da http://www.py4e.com/code3/ ed
eseguirlo.
Questo è un buon esempio di come Python e il linguaggio Python agiscano come
intermediario tra te (l’utente finale) e me (lo sviluppatore). Python è un modo per
scambiare sequenze di istruzioni utili (es. programmi) in un linguaggio comune che
possa essere utilizzato da chiunque installi Python sul proprio computer. Nessuno
di noi sta quindi parlando a Python, piuttosto comunichiamo con altri attraverso
Python.
input Ottieni dati dal “mondo esterno”: Potrebbe trattarsi della lettura di dati
contenuti in un file, oppure direttamente da un sensore come un microfo-
no o un GPS. Nei nostri programmi iniziali, il nostro input verrà inserito
direttamente dall’utente tramite tastiera.
output Visualizza i risultati su schermo o li memorizza in un file o magari li invia
ad un dispositivo come un altoparlante per riprodurre musica o leggere del
testo.
esecuzione sequenziale Esegui le istruzioni una dopo l’altra nell’ordine in cui
sono state scritte nel codice.
esecuzione condizionale Verifica le condizioni previste ed esegui, o salta, una
sequenza di istruzioni.
esecuzione ripetuta Esegui ripetutamente alcune istruzioni, spesso con alcune
variazioni.
riutilizzo Scrivi una volta una serie di istruzioni e dà loro un nome per poi, even-
tualmente, riutilizzare quelle istruzioni durante l’esecuzione del programma.
Sembra quasi troppo semplice per essere vero? Ovviamente non è mai così semplice.
Sarebbe come dire che camminare è semplicemente “mettere un piede davanti
all’altro”. L’“arte” di scrivere un programma è il comporre ed unire molte volte
questi elementi di base per produrre un qualcosa che sia utile ai propri utenti.
Ad esempio, il precedente script per il conteggio delle parole utilizza quasi tutti
questi modelli.
1.10. COSA POTREBBE ANDARE STORTO? 13
Si ottiene poco discutendo con Python: è solo uno strumento, non ha emozioni ed è
pronto a servirti ogni volta che tu ne abbia bisogno. I suoi messaggi di errore sono
inclementi, ma rappresentano solo la richiesta di aiuto di Python: ha esaminato
ciò che hai digitato e semplicemente non riesce a capire cosa tu voglia da lui.
Python è molto più simile a un cane, che amandovi incondizionatamente e cono-
scendo poche parole chiave, ti guarda dolcemente (>>>), aspettando che tu dica
qualcosa che sia in grado di capire. Quando Python visualizza “SyntaxError: in-
valid syntax”, sta dicendo, scodinzolando, “Mi sembra che tu abbia detto qualcosa
ma non ne capisco il significato; per favore continua a parlarmi (>>>).”
Man mano che i tuoi programmi diventeranno più sofisticati, potresti incappare in
queste tre tipologie di errore:
Errori di sintassi Questi sono i primi errori che farai e sono i più facili da correg-
gere. Un errore di sintassi indica che hai violato le regole della “grammatica”
di Python. Python farà del suo meglio per puntare direttamente alla riga e
14 CAPITOLO 1. PERCHÉ DOVRESTI IMPARARE A PROGRAMMARE?
Ribadisco che in tutte e tre le tipologie di errori, Python sta semplicemente facendo
del suo meglio per fare esattamente quello che gli hai chiesto.
Mentre prosegui nella lettura di questo libro, non aver paura se i concetti non
sembrano amalgamarsi bene l’uno all’altro sin da subito. Quando stavi imparando
a parlare, non è stato un problema se hai passato i primi anni producendo solo
dei dolci gorgoglii. Non c’erano problemi se ci sono voluti sei mesi per passare
da un vocabolario limitato a semplici frasi, se ci sono voluti 5 o 6 anni in più per
passare da frasi a paragrafi e ancora qualche anno per essere in grado di scrivere
un avvincente racconto breve.
Poiché vorremmo che tu possa imparare Python rapidamente, ti insegneremo tutto
nei prossimi capitoli. Sarà come imparare un nuovo linguaggio che richiede tem-
po per essere assorbito e compreso prima che diventi naturale. Ciò causerà una
certa confusione mentre vedremo e rivedremo argomenti cercando di farti vedere il
quadro generale definendo i piccoli frammenti che lo compongono. Nonostante il
libro sia scritto in modo lineare, ed anche se stai seguendo un corso che proseguirà
linearmente, non esitare ad essere molto non-lineare nel modo in cui ti approcci
ai contenuti. Sbircia avanti e indietro e leggi ciò che ti pare. Sfogliando il mate-
riale più avanzato senza comprenderne appieno i dettagli, potresti capire meglio
il “perché?” della programmazione. Rivedendo il materiale precedente o rifacen-
1.12. GLOSSARIO 15
do gli esercizi precedenti, realizzerai che in realtà hai imparato molto anche se il
materiale che stai fissando ora sembra un po’ imperscrutabile.
Capita spesso che quando stai imparando il tuo primo linguaggio di programma-
zione, ci siano alcuni meravigliosi momenti di stupore in cui, mentre sei intento a
scalfire la tua pietra con martello e scalpello, puoi alzare lo sguardo e cominciare
a vedere che stai davvero realizzando una bellissima scultura. Di solito è inutile
restare svegli tutta la notte a fissare qualcosa che sembra particolarmente difficile.
Fai una pausa, fai un pisolino, mangia uno spuntino, spiega a qualcuno in cosa stai
avendo problemi (magari solo al tuo cane), così da ritornare al problema con occhi
nuovi. Ti assicuriamo che una volta acquisiti i rudimenti della programmazione
descritti nel libro, ti guarderai indietro e vedrai che tutto è stato molto semplice
ed elegante e ti ha solo richiesto un po’ di tempo per assimilarlo.
1.12 Glossario
Bug Un errore in un programma.
Central processing unit Chiamato anche “CPU” o “processore”. È il cuore di
qualsiasi computer ed è ciò che gestisce il codice che scriviamo.
Compilare Tradurre completamente in una sola volta un programma scritto in
un linguaggio di alto livello in un linguaggio di basso livello, in preparazione
per l’esecuzione successiva.
Linguaggio di alto livello Un linguaggio di programmazione come Python pro-
gettato per essere facilmente letto e scritto dagli umani.
Modalità interattiva Un modo di utilizzare l’interprete Python digitando sin-
goli comandi e espressioni al prompt.
Interprete Eseguire un programma scritto in un linguaggio di alto livello tradu-
cendo una riga alla volta.
Linguaggio di basso livello Un linguaggio di programmazione progettato per
essere eseguito da un computer; è chiamato anche “codice macchina” o
“linguaggio assembly”.
Codice macchina Il linguaggio di livello più basso, è il linguaggio che viene
eseguito direttamente dall’unità di elaborazione centrale (CPU).
Memoria principale Conserva programmi e dati. La memoria principale perde
le sue informazioni memorizzate quando il computer viene spento.
Analizzare Esaminare un programma e analizzare la struttura sintattica.
Portabilità Proprietà di un programma che gli permette di essere eseguito su più
di un tipo di computer.
Funzione di stampa Istruzione che fa in modo che l’interprete Python visualizzi
un valore su schermo.
Risoluzione dei problemi Il processo composto da formulare un problema, ri-
cercare una soluzione e fornire la soluzione.
Programma Un set di istruzioni che specifica un calcolo.
Prompt Si verifica quando un programma visualizza un messaggio a schermo e si
interrompe affinché l’utente possa digitare un input destinato al programma.
1.13 Esercizi
Esercizio 1: Quale è la funzione della memoria secondaria in un computer?
a) Esegue tutto il calcolo e la logica del programma
b) Recupera pagine Web da Internet
c) Conserva le informazioni a lungo termine, anche oltre un ciclo di alimentazione
d) Accetta input dall’utente
Esercizio 2: Che cos’è un programma?
Esercizio 3: Quale è la differenza tra un compilatore e un interprete?
Esercizio 4: Quale delle seguenti alternative contiene “codice macchina”?
a) L’interprete di Python
b) La tastiera
c) Un file sorgente di Python
d) Un documento di elaborazione di testo
Esercizio 5: Cosa c’è di errato nel codice seguente?:
Esercizio 6: Dove viene memorizzata una variabile “x” simile a quella scritta
dopo la seguente linea Python?
x = 123
x = 43
x = x + 1
print(x)
1.13. ESERCIZI 17
a) 43
b) 44
c) x + 1
d) Errore perché x = x + 1 non è matematicamente possibile
Esercizio 8: Illustra ognuno dei seguenti esempi facendo dei paragoni con una
capacità degli uomini: (1) Unità centrale di elaborazione, (2) Memoria principale,
(3) Memoria secondaria, (4) Dispositivo di input e (5) Dispositivo di Output. Ad
esempio, “Qual è l’equivalente nell’uomo di una”Unità di elaborazione centrale“?
Esercizio 9: Come si corregge un “Syntax Error”?
18 CAPITOLO 1. PERCHÉ DOVRESTI IMPARARE A PROGRAMMARE?
Capitolo 2
Variabili, espressioni ed
istruzioni
python
>>> print(4)
4
Se non sei sicuro a quale tipo appartenga uno specifico valore, puoi consultare
l’interprete.
>>> type(3.2)
<class 'float'>
19
20 CAPITOLO 2. VARIABILI, ESPRESSIONI ED ISTRUZIONI
E per quanto riguarda valori come “17” e “3.2”? Pur sembrando numeri sono
racchiusi tra virgolette come le stringhe.
>>> type('17')
<class 'str'>
>>> type('3.2')
<class 'str'>
In realtà sono stringhe vere e proprie. Quando inserisci un numero intero di gran-
di dimensioni, come “1,000,000”, potresti essere tentato di utilizzare le virgole a
gruppi di tre cifre. Questo non è un numero intero consentito in Python, anche se
sembra scritto correttamente:
>>> print(1,000,000)
1 0 0
Beh, non è per niente quello che ci aspettavamo! Python interpreta 1,000,000
come se fosse una sequenza di numeri interi separati da virgole, che visualizza
inserendo uno spazio in corrispondenza della virgola. Questo è il primo esempio di
errore semantico che vediamo: il codice viene eseguito senza visualizzare messaggi
di errore ma non fa l’operazione per la quale pensavamo di averlo progettato.
2.2 Variabili
Una delle più potenti funzionalità di un linguaggio di programmazione è la capacità
di manipolare variabili. Una variabile è un nome assegnato ad un valore. Tramite
un’istruzione di assegnazione hai la capacità di creare nuove variabili ed assegnare
loro un valore:
In questo esempio vengono fatte tre assegnazioni: la prima assegna una stringa ad
una nuova variabile chiamata message, la seconda assegna il numero intero 17 alla
variabile n, la terza assegna il valore (approssimativo) di π a pi. È possibile usare
l’istruzione print per visualizzare il valore di una variabile:
>>> print(n)
17
>>> print(pi)
3.141592653589793
>>> type(message)
<class 'str'>
>>> type(n)
<class 'int'>
>>> type(pi)
<class 'float'>
2.3. NOMI DELLE VARIABILI E PAROLE CHIAVI 21
Il nome 76trombones non è valido perché inizia con un numero, more@, invece,
perché contiene il carattere non ammesso ‘@’. Ma cosa c’è di errato in class?
Risulta che class è una delle parole riservate di Python. L’interprete utilizza
queste parole per riconoscere la struttura del programma, quindi non possono
essere assegnate alle variabili.
Python si riserva 33 parole chiave:
2.4 Istruzioni
L’istruzione è un’unità minima di codice che può essere eseguita dall’interprete di
Python. Abbiamo visto due tipi di istruzioni: Print utilizzato sia come istruzione
di un’espressione sia come un’assegnazione. Quando digiti un’istruzione in moda-
lità interattiva, l’interprete la esegue e, nel caso esista, ne visualizza il risultato.
Generalmente uno script contiene una sequenza di istruzioni. Se c’è più di un’istru-
zione i risultati vengono visualizzati uno alla volta mano a mano che le istruzioni
vengono eseguite. Ad esempio:
22 CAPITOLO 2. VARIABILI, ESPRESSIONI ED ISTRUZIONI
print(1)
x = 2
print(x)
produce l’output
1
2
Gli operatori sono simboli speciali che rappresentano calcoli come l’addizione e
la moltiplicazione. I valori a cui è applicato l’operatore sono chiamati operandi.
Gli operatori +, -, *, /, e ** eseguono rispettivamente le operazioni di addizione,
sottrazione, moltiplicazione, divisione, ed elevamento a potenza, come illustrato
negli esempi seguenti:
Nel passaggio tra Python 2.x e Python 3.x è stato modificato l’operatore della
divisione: ora il risultato viene rappresentato in virgola mobile:
>>> minute = 59
>>> minute/60
0.9833333333333333
In Python 2.x l’operatore avrebbe diviso i due interi e troncato il risultato resti-
tuendo un valore intero:
>>> minute = 59
>>> minute/60
0
Per ottenere lo stesso risultato in Python 3.x, bisogna utilizzare la divisione intera
(// intero).
>>> minute = 59
>>> minute//60
0
2.6 Espressioni
L’espressione è una combinazione di valori, variabili e operatori. Sia un unico
valore o una variabile sono considerati un’espressione. Pertanto le seguenti sono
tutte espressioni corrette (posto che sia stato assegnato un valore alla variabile x)
17
x
x + 17
>>> 1 + 1
2
Una cosa che crea spesso confusione tra i principianti è un’espressione presente in
uno script, da sola non restituisce alcun risultato.
Esercizio 1: Digita le seguenti istruzioni nell’interprete Python ed osserva il loro
comportamento:
5
x = 5
x + 1
• Parentesi: hanno la precedenza più alta e possono essere utilizzate per co-
stringere il computer a svolgere un’espressione nell’ordine desiderato. Poiché
le espressioni tra parentesi vengono valutate per prime: 2 * (3-1) è uguale
a 4 e (1 + 1) ** (5-2) a 8. Puoi anche utilizzare le parentesi per rende-
re l’espressione più leggibile, come in (minuto * 100) / 60, senza che ciò
cambi il risultato.
• L’Elevamento a potenza ha il livello di precedenza immediatamente succes-
sivo, pertanto 2 ** 1 + 1 è pari a 3 e non a 4, e 3 * 1 ** 3 è uguale a 3 e
non 27.
• Le M oltiplicazioni e le Divisioni hanno la stessa precedenza, superiore alle
Addizioni e alle Sottrazioni, che posseggono lo stesso livello di precedenza.
Quindi 2 * 3-1 darà come risultato 5, non 4, e 6 + 4 / 2 darà 8.0 e non 5.
• Gli operatori con la stessa precedenza vengono calcolati da sinistra a destra:
L’espressione 5-3-1 ha come risultato 1 e non 3, perché 5-3 viene svolta per
prima e solo dopo viene sottratto 1 da 2. In caso di dubbio, utilizza sempre
le parentesi nelle tue espressioni, per essere sicuro che i calcoli siano eseguiti
nell’ordine desiderato.
24 CAPITOLO 2. VARIABILI, ESPRESSIONI ED ISTRUZIONI
>>> quotient = 7 // 3
>>> print(quotient)
2
>>> remainder = 7 % 3
>>> print(remainder)
1
Quindi 7 diviso per 3 fa 2 con il resto di 1. L’operatore modulo si può rivelare sor-
prendentemente utile. Per esempio, è possibile verificare se un numero è divisibile
per un altro: se x%y fa zero, allora x è divisibile per y. È inoltre possibile ricavare
da un numero quale sia il valore della cifra o delle cifre più a destra. Per esempio,
x % 10 restituisce la cifra più a destra di x (in base 10). In modo simile, x% 100
restituisce le ultime due cifre.
>>> first = 10
>>> second = 15
>>> print(first+second)
25
>>> first = '100'
>>> second = '150'
>>> print(first + second)
100150
2.11 Commenti
Man mano che i programmi diventano più grandi e complessi, questi diventano
più difficili da leggere. I linguaggi formali sono condensati ed è spesso difficile
esaminare un pezzo di codice e capire cosa o perché faccia qualcosa. Per questo
motivo, è una buona idea aggiungere alcune note ai vostri programmi per spiegare
in linguaggio naturale cosa stia facendo il programma. Queste note, che in Python
iniziano con il simbolo #, sono chiamate commenti:
In questo caso, il commento appare da solo su una riga. Potete anche inserire
commenti alla fine di una riga:
Tutto ciò che è compreso tra “#” e la fine della riga viene ignorato e non ha alcun
effetto sul programma. I commenti sono ancor più utili quando documentano
funzionalità del codice non così evidenti. Anche se è ragionevole presumere che chi
legge il codice possa capire cosa questo faccia, è molto più utile spiegarne il perché.
Questo commento al codice è superfluo ed inutile:
v = 5 # assign 5 to v
Questo commento, invece, contiene informazioni utili che non sono comprese nel
codice:
v = 5 # velocity in meters/second.
a = 35.0
b = 12.50
c = a * b
print(c)
2.12. SCEGLIERE NOMI MNEMONICI DELLE VARIABILI 27
hours = 35.0
rate = 12.50
pay = hours * rate
print(pay)
x1q3z9ahd = 35.0
x1q3z9afd = 12.50
x1q3p9afd = x1q3z9ahd * x1q3z9afd
print(x1q3p9afd)
Python interpreta tutti e tre esattamente allo stesso modo mentre noi umani leg-
giamo e comprendiamo questi programmi in modo abbastanza diverso. Si capisce
velocemente lo scopo del secondo esempio poiché lo sviluppatore ha scelto il no-
me delle variabili pensando a quali dati saranno memorizzati in ciascuna di esse.
Chiamiamo questi nomi di variabili così saggiamente scelti “nomi mnemonici delle
variabili”. La parola mnemonico2 significa letteralmente “aiuto per la memoria”.
Scegliamo nomi mnemonici per le variabili principalmente per aiutarci a ricordare
il perché le abbiamo create. Mentre tutto ciò sembra grandioso, i nomi delle va-
riabili mnemonici possono essere d’intralcio alla capacità di uno sviluppatore alle
prime armi di analizzare e comprendere un codice. Questo perché gli sviluppatori
principianti non hanno ancora memorizzato le parole riservate (ce ne sono solo 33)
e talvolta variabili con nomi troppo descrittivi iniziano a sembrare parte del lin-
guaggio e non solo nomi di variabili ben scelti. Dai una rapida occhiata al seguente
codice esempio di Python che viene eseguito ripetutamente (in loop) su alcuni dati.
Affronteremo presto i loop, ma per ora cerca di capire che cosa significhi:
Cosa sta succedendo? Quali parole (for, word, in, ecc.) sono parole riservate e
quali sono solo variabili? Python riesce a capire a livello fondamentale la nozione
di parole? I programmatori principianti hanno difficoltà a distinguere quali parti
del codice debbano restare invariate, come in questo esempio, e quali parti del codice
siano semplicemente scelte fatte dallo sviluppatore. Il seguente codice equivale al
precedente:
Per il programmatore alle prime armi è più facile leggere questo script per sapere
quali parti siano parole riservate di Python e quali parti siano semplicemente va-
riabili. È abbastanza chiaro che Python non conosce la nozione di pizza e trancio
(slice) e che una pizza sia composta da un insieme di uno o più tranci. Ma se il
nostro programma riguarda veramente la lettura di dati e la ricerca di parole nei
dati, pizza e slice non sono per niente nomi mnemonici di variabili. Sceglierli
come nomi di variabili ci distrae dallo scopo del programma. Dopo un breve perio-
do di tempo, conoscerai le più comuni parole riservate e inizieranno a saltarti agli
occhi:
2 Vedi http://www.treccani.it/vocabolario/mnemonico/ per un’estesa descrizione della parola
“mnemonico”.
28 CAPITOLO 2. VARIABILI, ESPRESSIONI ED ISTRUZIONI
Le parti del codice definite da Python (for, in, print, e :) sono in grassetto,
contrariamente alle variabili scelte dallo sviluppatore (word e words). Molti editor
di testo riconoscono la sintassi di Python e colorano le parole riservate in modo
diverso mettendole in evidenza rispetto alle variabili. Dopo un po’ di pratica nel
leggere Python sarai in grado di capire rapidamente quale sia una variabile e quale
una parola riservata.
2.13 Debug
A questo punto, l’errore di sintassi più probabile che tu possa fare è scegliere un
nome di variabile non consentito, come class e yield, che sono parole chiave, o
odd~job eUS$, che contengono caratteri non consentiti. Se inserisci uno spazio nel
nome di variabile, Python riterrà che si tratti di due operandi senza un operatore:
>>> month = 09
File "<stdin>", line 1
month = 09
^
SyntaxError: invalid token
Per quanto riguarda gli errori di sintassi, bisogna dire che i messaggi di errore non
sono di grande aiuto. I messaggi più comuni, SyntaxError: invalid syntax e
SyntaxError: invalid token, sono entrambi poco esplicativi. L’errore di runti-
me più comune è “use before def;”: cioè l’utilizzo di una variabile prima di averle
assegnato un valore. Questo può verificarsi se viene scritta una variabile in modo
errato:
I nomi delle variabili sono case-sensitive: LaTeX non è lo stesso di latex. A questo
punto la causa più probabile di un errore semantico è l’ordine delle operazioni. Ad
esempio, per calcolare 1/2π, potresti essere tentato di scrivere:
Dato che la divisione viene effettuata per prima, otterrai π/2 che non è la stessa
cosa! Poiché Python non ha modo di sapere cosa volessi realmente scrivere, non
riceverai un messaggio di errore ma solo un risultato errato.
2.14. GLOSSARIO 29
2.14 Glossario
Istruzione Istruzione che assegna un valore a una variabile.
Concatenare Unire due operandi tra di loro.
Commento Informazione in un programma destinato ad altri programmatori (o
a chiunque stia leggendo il codice sorgente) e non ha alcun effetto sull’esecu-
zione del programma.
Calcolare Semplificare un’espressione eseguendo le operazioni in modo da ottene-
re un unico valore.
Espressione Combinazione di variabili, operatori e valori che fornisce un singolo
valore come risultato.
Virgola mobile Tipo di dato composto da numeri con le parti frazionarie.
Interi Tipo di dato composto da numeri interi.
Parola chiave o riservata Una parola riservata che viene utilizzata dal compila-
tore per analizzare un programma; non potete utilizzare parole chiave come
if, def e while come nomi di variabili.
Mnemonico Un aiuto mnemonico: spesso diamo alle variabili nomi mnemonici
per aiutarci a ricordare cosa è memorizzato nella variabile.
Operatore modulo Un operatore, rappresentato con il segno di percentuale (%),
che funziona con numeri interi e restituisce il resto quando un numero è diviso
per un altro.
Operando Uno dei valori su cui viene applicato un operatore.
Operatore Simbolo speciale che rappresenta un semplice calcolo come addizioni,
moltiplicazioni o concatenazioni di stringhe.
Regole di precedenza Insieme di regole che disciplinano l’ordine in cui vengono
calcolate le espressioni che coinvolgono molteplici operatori e operandi.
Istruzione Una sezione di codice che rappresenta un comando o un’azione. Finora
le istruzioni che abbiamo visto sono assegnazioni e istruzioni di stampa.
Stringa Un tipo di dato composto da una sequenza di caratteri.
Tipo Una categoria di valori. I tipi visti fino ad ora sono gli interi (tipo int), i
numeri a virgola mobile (tipo float) e le stringhe (tipo str).
Valore Una delle unità di base dei dati, come numeri o stringhe, che possono
essere manipolate da un programma.
Variabile Un nome assegnato ad un valore.
2.15 Esercizi
Esercizio 2: Scrivi un programma che usi input per chiedere all’utente il proprio
nome e poi dia loro il benvenuto.
Enter Hours: 35
Enter Rate: 2.75
Pay: 96.25
30 CAPITOLO 2. VARIABILI, ESPRESSIONI ED ISTRUZIONI
Per ora non ci preoccuperemo di assicurarci che la paga abbia esattamente due
cifre dopo la virgola. Se vuoi, puoi giocare con la funzione round di Python per
arrotondare correttamente la retribuzione risultante a due cifre decimali.
Esercizio 4: Supponiamo di eseguire le seguenti istruzioni di assegnazione:
width = 17
height = 12.0
>>> print(1,000,000)
1 0 0
Esecuzione condizionale
>>> 5 == 5
True
>>> 5 == 6
False
{}
True e False non sono considerati stringhe ma sono valori speciali appartenenti
al tipo bool:
>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>
x != y # x is not equal to y
x > y # x is greater than y
x < y # x is less than y
x >= y # x is greater than or equal to y
x <= y # x is less than or equal to y
x is y # x is the same as y
x is not y # x is not the same as y
31
32 CAPITOLO 3. ESECUZIONE CONDIZIONALE
Seppure questa flessibilità possa tornare utile, ci sono alcuni dettagli che potreb-
bero creare confusione. A meno che non si sappia cosa si stia facendo, è il caso di
evitare un evento del genere.
Per scrivere programmi utili, abbiamo quasi sempre bisogno di verificare le condi-
zioni e modificare di conseguenza il comportamento del programma. Le istruzioni
condizionali ci danno questa capacità. La forma più semplice è l’istruzione if:
if x > 0 :
print('x is positive')
Yes
x>0
print(‘x is positive’)
if x < 0 :
pass # need to handle negative values!
>>> x = 3
>>> if x < 10:
... print('Small')
...
Small
>>>
Quando utilizzi l’interprete Python, è necessario lasciare una riga vuota alla fine
di un blocco, in caso contrario Python restituirà un errore:
>>> x = 3
>>> if x < 10:
... print('Small')
... print('Done')
File "<stdin>", line 3
print('Done')
^
SyntaxError: invalid syntax
Durante la scrittura e l’esecuzione di uno script non è necessaria una riga vuota
alla fine di un blocco di istruzioni, anche se questa può migliorare la leggibilità del
codice.
if x%2 == 0 :
print('x is even')
else :
print('x is odd')
No Yes
x%2 == 0
Poiché questa condizione deve necessariamente essere vera o falsa, verrà eseguita
una delle alternative. Le alternative sono chiamate branches perché sono come
rami nel flusso di esecuzione.
if x < y:
print('x is less than y')
elif x > y:
print('x is greater than y')
else:
print('x and y are equal')
elif è l’abbreviazione di “else if”. Anche stavolta verrà eseguito un solo ramo.
Non c’è limite al numero di istruzioni elif. Possono essere utilizzate svariate
condizioni else, purché siano inserite alla fine.
if choice == 'a':
print('Bad guess')
elif choice == 'b':
print('Good guess')
elif choice == 'c':
print('Close, but not correct')
3.6. CONDIZIONI NIDIFICATE 35
Yes
x<y print(‘less’)
Yes
x>y print (‘greater’)
print(‘equal’)
Una condizione può anche essere annidata all’interno di un’altra. Avremmo potuto
scrivere l’esempio a tre branch anche in questo modo:
if x == y:
print('x and y are equal')
else:
if x < y:
print('x is less than y')
else:
print('x is greater than y')
La condizione esterna contiene due branch. Il primo contiene una semplice istru-
zione. Il secondo branch contiene un’istruzione if, composta da due branch propri.
Questi due branch sono entrambi semplici istruzioni, sebbene possano essere anche
istruzioni condizionali.
Nonostante l’indentazione delle istruzioni renda la struttura visivamente più intui-
tiva, le condizioni nidificate diventano molto rapidamente difficili da comprendere.
In generale, è meglio evitarle quando possibile. Gli operatori logici forniscono
spesso un modo per semplificare le istruzioni nidificate. Ad esempio, possiamo
riscrivere lo script seguente usando una singola condizione:
if 0 < x:
if x < 10:
print('x is a positive single-digit number.')
36 CAPITOLO 3. ESECUZIONE CONDIZIONALE
Yes No
x == y
Yes No
x<y
print(‘equal’)
print(‘less’) print’‘greater’)
Dato che l’istruzione print viene eseguita solo se vengono soddisfatte entrambe le
condizioni, possiamo ottenere lo stesso effetto con l’operatore and:
# Code: http://www.py4e.com/code3/fahren.py
Se eseguiamo questo codice e gli forniamo valori non validi, fallirà semplicemente
mostrando un messaggio di errore poco amichevole:
python fahren.py
Enter Fahrenheit Temperature:72
22.22222222222222
python fahren.py
Enter Fahrenheit Temperature:fred
Traceback (most recent call last):
File "fahren.py", line 2, in <module>
fahr = float(inp)
ValueError: could not convert string to float: 'fred'
Per gestire questi tipi di errori previsti e inaspettati esiste una funzione condizio-
nale incorporata in Python chiamata “try/except”. La finalità di try e except
è collegata al sapere in anticipo che alcune sequenze di istruzioni potrebbero in-
contrare problemi. Pertanto si possono aggiungere alcune istruzioni alternative da
eseguire in caso di errore. Queste istruzioni aggiuntive (il blocco except) verranno
ignorate se non si verificano errori. Potresti pensare alla funzione try e except
in Python come ad una “polizza assicurativa” su una sequenza di istruzioni. Ad
esempio, possiamo riscrivere il codice del nostro convertitore di temperatura come
segue:
# Code: http://www.py4e.com/code3/fahren2.py
python fahren2.py
Enter Fahrenheit Temperature:72
22.22222222222222
python fahren2.py
Enter Fahrenheit Temperature:fred
Please enter a number
38 CAPITOLO 3. ESECUZIONE CONDIZIONALE
>>> x = 6
>>> y = 2
>>> x >= 2 and (x/y) > 2
True
>>> x = 1
>>> y = 0
>>> x >= 2 and (x/y) > 2
False
>>> x = 6
>>> y = 0
>>> x >= 2 and (x/y) > 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
Il terzo esempio ha dato errore perché Python, nel calcolare (x/y) dove y è zero, è
incappato in un errore di runtime. Il secondo esempio non è fallito perché, essendo
la prima parte dell’espressione x >= 2 calcolata come False, (x/y) non è mai
stato eseguito a causa della regola del cortocircuito. Possiamo utilizzare questa
logica per posizionare strategicamente uno schema del guardiano appena prima
del calcolo che potrebbe causare un errore, come nell’esempio seguente:
>>> x = 1
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x = 6
3.9. DEBUG 39
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x >= 2 and (x/y) > 2 and y != 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
Nella prima espressione logica, x >= 2 è False, quindi il calcolo si ferma su and.
Nella seconda espressione logica, x >= 2 è True ma y != 0 è False quindi non
raggiungiamo mai (x/y). Nella terza espressione logica, il y != 0 viene dopo il
calcolo (x/y) pertanto l’espressione fallisce con un errore. Nella seconda espressio-
ne, diciamo che y != 0 ha la funzione da guardia per assicurare che (x/y) venga
eseguito solo se y è diverso da zero.
3.9 Debug
• il tipo di errore;
• dove si è verificato.
Gli errori di sintassi sono solitamente facili da trovare, ma ci sono alcuni tranelli.
Gli errori di spaziatura possono essere difficoltosi da correggere perché gli spazi e
le tabulazioni sono normalmente invisibili e siamo abituati ad ignorarli.
>>> x = 5
>>> y = 6
File "<stdin>", line 1
y = 6
^
IndentationError: unexpected indent
3.10 Glossario
Blocco La sequenza di istruzioni presenti all’interno di un’istruzione composta.
Espressione booleana Espressione che può assumere il valore Vero o Falso.
Branch Una delle sequenze di istruzioni alternative di un’istruzione condizionale.
3.11 Esercizi
Esercizio 1: Riscrivi lo script del calcolo della retribuzione per attribuire ad un
dipendente una maggiorazione oraria di 1,5 volte, per le ore di lavoro straordinario
fatte oltre le 40.
Enter Hours: 45
Enter Rate: 10
Pay: 475.0
Enter Hours: 20
Enter Rate: nine
Error, please enter numeric input
Esercizio 3: Scrivi un programma per richiedere un valore compreso tra 0.0 e 1.0.
Se non è compreso nell’intervallo specificato, visualizza un messaggio di errore. Se
è compreso tra 0,0 e 1,0, visualizza un giudizio utilizzando la seguente tabella:
Score Grade
>= 0.9 A
>= 0.8 B
>= 0.7 C
>= 0.6 D
< 0.6 F
Esegui varie volte il programma per testarlo con diversi valori di input.
42 CAPITOLO 3. ESECUZIONE CONDIZIONALE
Capitolo 4
Funzioni
>>> type(32)
<class 'int'>
in questo esempio il nome della funzione è type mentre il numero 32 tra parentesi
è l’argomento della funzione. L’argomento può essere un valore o una variabile
che stiamo passando alla funzione come input. In questo caso specifico il risultato
della funzione type è il tipo di dato dell’argomento: <class ‘int’>.
Nel gergo informatico si dice che una funzione “accetta” un argomento e “restitui-
sce” un valore di ritorno.
43
44 CAPITOLO 4. FUNZIONI
La funzione max ci indica che il “carattere più grande” nella stringa é la lettera
“w” mentre min ci mostra che il carattere più piccolo é lo spazio. Un’altra funzione
molto utilizzata è la funzione len che ci dice quanti oggetti ci sono nell’argomento
analizzato. Nell’esempio seguente l’argomento di len è una stringa e perciò viene
restituito il numero di caratteri nella stringa.
Come vedremo nei prossimi capitoli queste funzioni non sono progettate per opera-
re solo con le stringhe ma possono essere applicate su un qualsiasi insieme di valori.
Voglio ricordarti che anche i nome di funzioni vanno trattati come parole riservate
(ad esempio, evita di utilizzare “max” come nome di variabile).
>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int() with base 10: 'Hello'
int può convertire valori in virgola mobile in numeri interi eliminando semplice-
mente la parte frazionaria senza arrotondare il dato:
>>> int(3.99999)
3
>>> int(-2.3)
-2
>>> float(32)
32.0
>>> float('3.14159')
3.14159
>>> str(32)
'32'
>>> str(3.14159)
'3.14159'
4.4. FUNZIONI MATEMATICHE 45
Il modulo math di Python ti permette di eseguire la maggior parte delle più co-
muni funzioni matematiche. Essendo un modulo, va importato prima di poterlo
utilizzare:
>>> print(math)
<module 'math' (built-in)>
Per accedere a una delle funzioni gestite da questo modulo è necessario specificare
il nome del modulo e della funzione desiderata separati da un punto. Questo
particolare formato viene chiamato notazione punto.
Nel primo esempio viene calcolato il logaritmo in base 10 del rapporto segna-
le/rumore. Il modulo math è dotato anche di una funzione chiamata log che
calcola i logaritmi in base e.
Nel secondo esempio é stato calcolato il seno in radianti. Il nome della variabile
ti dovrebbe suggerire che sin e le altre funzioni trigonometriche (cos, tan, ecc.)
accettano un argomento in radianti. Per convertire i gradi in radianti, dovrai
dividere il dato in ingresso per 360 e moltiplicarlo per 2π:
>>> degrees = 45
>>> radians = degrees / 360.0 * 2 * math.pi
>>> math.sin(radians)
0.7071067811865476
import random
for i in range(10):
x = random.random()
print(x)
0.11132867921152356
0.5950949227890241
0.04820265884996877
0.841003109276478
0.997914947094958
0.04842330803368111
0.7416295948208405
0.510535245390327
0.27447040171978143
0.028511805472785867
Esercizio 1:: Esegui lo script precedente sul tuo sistema per vedere quali numeri
otterrai. Ripeti l’operazione più volte e confronta i risultati.
La funzione random è solo una delle molte funzioni che gestiscono i numeri casuali.
randint accetta i parametri low e high e restituisce un intero compreso tra i due
(inclusi) estremi.
>>> t = [1, 2, 3]
>>> random.choice(t)
2
>>> random.choice(t)
3
Il modulo random fornisce anche funzioni per generare valori casuali da distribu-
zioni continue come la gaussiana, l’esponenziale, la gamma, ecc.
def print_lyrics():
print("I'm a lumberjack, and I'm okay.")
print('I sleep all night and I work all day.')
def è la parola chiave che indica che il nome di questa funzione è print_lyrics.
Fai attenzione che per i nomi delle funzioni valgono le stesse regole in vigore per i
nomi delle variabili: sono consentiti lettere, numeri e alcuni segni di punteggiatura
ma il primo carattere non può mai essere un numero. Inoltre, non puoi utilizzare
una parola chiave come nome di una funzione, e evita di dare lo stesso nome ad
una variabile e ad una funzione.
Le parentesi vuote dopo il nome indicano che questa funzione non accetta argomen-
ti. Nei capitoli seguenti ti insegnerò a costruire delle funzioni che ricevono degli
argomenti come input.
Una funzione è composta da un header, la prima riga della funzione, ed un corpo
composto dal resto delle istruzioni. L’header deve sempre terminare con due punti
e il corpo, che deve essere indentato per convenzione di quattro spazi, può contenere
un numero qualsiasi di istruzioni.
Se decidi di scrivere una definizione di funzione in modalità interattiva, l’interprete
continuerà a visualizzare tre punti di sospensione (. . . ) fino a quando non inserirai
una riga vuota per indicare che hai completato il corpo:
Se invece la definizione di una funzione fa parte di uno script non è necessario che
tu inserisca la riga vuota finale.
La definizione di una funzione crea una variabile con lo stesso nome.
>>> print(print_lyrics)
<function print_lyrics at 0xb7e99e9c>
>>> print(type(print_lyrics))
<class 'function'>
>>> print_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
Una volta che avrai definito una funzione, potrai utilizzarla all’interno di un’altra
funzione. Ad esempio, per ripetere il ritornello precedente, potremmo scrivere una
funzione chiamata repeat_lyrics:
def repeat_lyrics():
print_lyrics()
print_lyrics()
>>> repeat_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
Ammetto che la canzone non faccia proprio così ma non è importante per ora.
def print_lyrics():
print("I'm a lumberjack, and I'm okay.")
print('I sleep all night and I work all day.')
def repeat_lyrics():
4.8. FLUSSO DI ESECUZIONE 49
print_lyrics()
print_lyrics()
repeat_lyrics()
# Code: http://www.py4e.com/code3/lyrics.py
def print_twice(bruce):
print(bruce)
print(bruce)
>>> print_twice('Spam')
Spam
Spam
>>> print_twice(17)
17
17
>>> import math
>>> print_twice(math.pi)
3.141592653589793
3.141592653589793
Dato che le regole di composizione che si applicano alle funzioni integrate si applica-
no anche a quelle definite dall’utente, possiamo usare qualsiasi tipo di espressione
come argomento per print_twice:
L’argomento viene valutato prima che venga chiamata la funzione, quindi ne-
gli esempi precedenti le espressioni Spam '* 4 e math.cos (math.pi)vengono
valutate una sola volta. Potete utilizzare inoltre una variabile come argomento:
Il nome della variabile che passiamo come argomento (michael) non ha nulla a che
fare con il nome del parametro (bruce). Non importa quale sia il nome valore (nel
chiamante); qui in print_twice, chiamiamo tutti bruce.
x = math.cos(radians)
golden = (math.sqrt(5) + 1) / 2
>>> math.sqrt(5)
2.23606797749979
math.sqrt(5)
Credo che questo script non sia molto utile: è vero che calcola la radice quadrata
di 5 ma non ne visualizza il risultato nè lo memorizza in una variabile.
Le funzioni vuote possono visualizzare qualcosa sullo schermo o avere qualche altro
effetto, ma non hanno alcun valore di ritorno. Se tenti di assegnare il risultato a
una variabile otterrai un valore speciale chiamato “None”.
Il valore None non è uguale alla stringa “None”: è un valore speciale che ha un suo
proprio tipo.
>>> print(type(None))
<class 'NoneType'>
52 CAPITOLO 4. FUNZIONI
x = addtwo(3, 5)
print(x)
# Code: http://www.py4e.com/code3/addtwo.py
Quando questo script viene eseguito, l’istruzione print visualizza “8” perché la
funzione addtwo è stata chiamata passandole gli argomenti 3 e 5. All’interno
della funzione i parametri a eb hanno assunto rispettivamente il valore 3 e 5. La
funzione calcola la somma dei due numeri e la inserisce nella variabile di funzione
locale added. Infine traminte l’istruzione return il valore calcolato viene inviato al
codice chiamante come risultato della funzione, assegnato alla variabile x e quindi
visualizzato.
Potrebbe non esserti chiaro il motivo per cui vale la pena di suddividere un
programma in funzioni. I motivi sono diversi:
Nel resto del libro, spesso userò una definizione di funzione per spiegare un concetto.
Parte della capacità di creare e utilizzare le funzioni è di avere una funzione che
interpreti correttamente una singola idea come “trova il valore più piccolo in un
elenco di valori”. Successivamente ti mostrerò il codice che trova il valore più
piccolo in un elenco di valori e te lo presenterò come una funzione chiamata min
che prende come argomento un elenco di valori e restituisce il valore più piccolo
dell’elenco.
4.12. DEBUG 53
4.12 Debug
Nonostante il fatto che la maggior parte degli editor di testo sono in grado di
riconoscere nativamente il codice Python, se usi un editor di testo per scrivere i
tuoi script, potresti avere delle noie con spazi e tabulazioni. Il modo migliore per
evitare questi problemi è utilizzare esclusivamente gli spazi evitando le tabulazioni.
Spesso il debug é reso più complicato dal fatto che le tabulazioni e spazi sono
generalmente invisibili. Per mitigare questo problema prova ad utilizzare un editor
che gestisca automaticamente l’indentazione.
Non dimenticare inoltre di salvare sempre il tuo script prima di eseguirlo dato che
non tutti gli ambienti di sviluppo lo fanno automaticamente. In questo modo il
codice che stai guardando nell’editor di testo sarà lo stesso che stai facendo girare.
Il debug può richiedere molto più tempo se continui a ripetere più e più volte lo
stesso script bacato!
Assicurati sempre di eseguire l’ultima versione del codice che stai esaminando. Nel
caso ti venissero dei dubbi, inserisci una riga tipo print (" ciao ") all’inizio del
programma prima di eseguirlo nuovamente. Se non vedi apparire ciao . . . non
stai facendo girare la versione giusta!
4.13 Glossario
Argomento Il valore fornito a una funzione quando questa viene chiamata. Que-
sto valore è assegnato al parametro corrispondente nella funzione.
Corpo La sequenza di istruzioni contenute in una definizione di funzione.
Composizione Utilizzo di un’espressione come parte di un’espressione più grande
o di un’istruzione come parte di un’istruzione più ampia.
Deterministico Pertinente a un programma che, dati gli stessi input, produce lo
stesso risultato ogni volta che viene eseguito.
Notazione del punto La sintassi per chiamare una funzione gestita da un mo-
dulo specificando il nome del modulo seguito da un punto e dal nome della
funzione stessa.
Flusso di esecuzione L’ordine in cui le istruzioni vengono eseguite durante l’e-
secuzione di un programma.
Funzione produttiva Una funzione che restituisce un valore.
Funzione Sequenza di istruzioni provvista di nome che svolge alcune operazioni
utili. Le funzioni possono accettare o non accettare argomenti e possono o
meno produrre un risultato.
Chiamata di funzione L’istruzione che esegue una funzione. È composta dal
nome della funzione seguito da un elenco di argomenti.
Definizione di funzione L’istruzione che da luogo ad una nuova funzione, spe-
cificandone il nome, i parametri e le istruzioni che esegue.
Oggetto funzione Il valore creato dalla definizione di funzione. Il nome della
funzione è una variabile che fa riferimento a un oggetto funzione.
Header La prima riga di una definizione di funzione.
54 CAPITOLO 4. FUNZIONI
4.14 Esercizi
Esercizio 4: A cosa serve la parola chiave “def” in Python?
def fred():
print("Zap")
def jane():
print("ABC")
jane()
fred()
jane()
Esercizio 6: Riscrivi il calcolo della tua retribuzione con gli straordinari pagati il
50% in più creando una funzione chiamata computepay che richieda i due parametri
hours e rate.
Enter Hours: 45
Enter Rate: 10
Pay: 475.0
Esercizio 7: Riscrivi lo script del capitolo precedente creando una funzione chia-
mata computegrade che accetta un punteggio come parametro e restituisce un
voto sotto forma di stringa.
Score Grade
>= 0.9 A
>= 0.8 B
>= 0.7 C
>= 0.6 D
< 0.6 F
Iterazione
x = x + 1
>>> x = x + 1
NameError: name 'x' is not defined
Prima di poter aggiornare una variabile, occorre inizializzarla, solitamente con una
semplice assegnazione:
>>> x = 0
>>> x = x + 1
57
58 CAPITOLO 5. ITERAZIONE
n = 5
while n > 0:
print(n)
n = n - 1
print('Decollo!')
Puoi quasi leggere l’istruzione while come se fosse scritta in inglese: “Mentre n è
maggiore di 0, mostra il valore di n e ad esso sottrai 1. Quando arrivi a 0, esci
dall’istruzione while e visualizza la parola Decollo!”
Più formalmente, ecco il flusso di esecuzione per un’istruzione while: 1. Valuta
la condizione, ottenendo come risultato True o False. 2. Se la condizione è falsa,
esci dall’istruzione while e prosegui con l’istruzione successiva. 3. Se la condizione
è vera, esegui il blocco e torna al passaggio 1. Questo tipo di flusso è chiamato
loop o ciclo perché il terzo step ricomincia dall’inizio del ciclo. Ogni esecuzione del
blocco incluso nel ciclo viene chiamata iterazione. Per il ciclo precedente, diremo
che “aveva cinque iterazioni”, in altre parole il blocco del loop è stato eseguito
cinque volte.
Il blocco del ciclo dovrebbe cambiare il valore di una o più variabili in modo da
far diventare infine falsa la condizione e far sì che il ciclo termini. Chiamiamo
variabile di iterazione la variabile che cambia ogni volta che il ciclo viene eseguito
e controlla quando il ciclo terminerà. Se non esiste una variabile di iterazione, il
ciclo si ripeterà per sempre dando luogo ad un ciclo infinito.
n = 10
while True:
print(n, end=' ')
n = n - 1
print('Done!')
5.5. FERMARE LE ITERAZIONI CON CONTINUE 59
while True:
line = input('> ')
if line == 'done':
break
print(line)
print('Done!')
# Code: http://www.py4e.com/code3/copytildone1.py
La condizione del ciclo è True, che è sempre vera, quindi il ciclo viene ripetuto finché
non incontra l’istruzione break. Ogni volta viene mostrato il prompt all’utente con
una parentesi angolare. Se l’utente digita done, l’istruzione break interrompe il
ciclo, Altrimenti il programma ripete a pappagallo qualsiasi cosa scriva l’utente e
torna all’inizio del ciclo. Ecco un esempio di esecuzione:
while True:
line = input('> ')
if line[0] == '#':
continue
if line == 'done':
break
print(line)
print('Done!')
# Code: http://www.py4e.com/code3/copytildone2.py
Vengono visualizzati tutti gli input, tranne quelli che iniziano con il segno cancellet-
to perché, quando viene eseguito continue, l’iterazione corrente termina e ritorna
all’istruzione while per iniziare la successiva saltando l’istruzione print.
Dal punto di vista di Python, la variabile friends è una lista ˆ [Le liste verranno
esaminate in modo più dettagliato in un capitolo successivo.] di tre stringhe e il
ciclo for scorre attraverso l’elenco ed esegue il blocco una volta per ognuna di esse,
generando questo output:
count = 0
for itervar in [3, 41, 12, 9, 74, 15]:
count = count + 1
print('Count: ', count)
La variabile count viene impostata su zero prima dell’inizio del ciclo, ed il ciclo
for scorre l’elenco dei numeri. La nostra variabile di iterazione si chiama itervar
e sebbene non usiamo itervar nel ciclo, controlla il ciclo e fa sì che il corpo
del ciclo sia eseguito una volta per ciascuno dei valori nella lista. Nel blocco del
ciclo, aggiungiamo 1 al valore corrente di “count” per ciascuno dei valori dell’elenco.
Mentre il ciclo è in esecuzione, il valore di “count” è il numero di valori che abbiamo
visto “finora”. Una volta ok, il valore finale di count è il numero totale di elementi.
Costruiamo il ciclo in modo da ottenere ciò che vogliamo al termine del ciclo. Un
altro ciclo simile che calcola la somma di un insieme di numeri è il seguente:
62 CAPITOLO 5. ITERAZIONE
total = 0
for itervar in [3, 41, 12, 9, 74, 15]:
total = total + itervar
print('Total: ', total)
Come puoi vedere, in questo ciclo abbiamo utilizzato la variabile di iterazione. In-
vece di aggiungere semplicemente uno a count, come nel ciclo precedente, aggiun-
giamo il valore effettivo (3, 41, 12, ecc.) al totale parziale durante ogni iterazione
del ciclo. La variabile total, contiene pertanto il “totale parziale dei valori finora
esaminati”. Quindi, prima che inizi il primo ciclo, total è zero perché non abbiamo
ancora esaminato alcun valore, durante il ciclo, total è il totale parziale, e alla
fine del ciclo total è il totale complessivo di tutti i valori dell’elenco. Mentre il
ciclo viene eseguito, total accumula la somma degli elementi. Una variabile usata
in questo modo viene talvolta chiamata accumulatore.
Né il ciclo di conteggio né il ciclo di somma sono particolarmente utili nella pratica
perché già esistono due funzioni len () e sum (), che calcolano rispettivamente il
numero di elementi e il totale degli elementi dell’elenco.
largest = None
print('Before:', largest)
for itervar in [3, 41, 12, 9, 74, 15]:
if largest is None or itervar > largest :
largest = itervar
print('Loop:', itervar, largest)
print('Largest:', largest)
Before: None
Loop: 3 3
Loop: 41 41
Loop: 12 41
Loop: 9 41
Loop: 74 74
Loop: 15 74
Largest: 74
La variabile largest si può definire meglio come il “valore più grande che abbiamo
visto finora”. Prima del ciclo, diamo alla variabile largest il valore della costante
None. None è una costante speciale che possiamo memorizzare in una variabile per
contrassegnarla come “vuota”. Prima dell’avvio del primo ciclo, il valore più grande
che abbiamo visto finora è None poiché non abbiamo ancora inserito alcun valore.
Mentre il ciclo è in esecuzione, se largest è None, il primo valore esaminato sarà il
più grande fino a quel punto. Nella prima iterazione, quando il valore di itervar è
5.8. DEBUG 63
smallest = None
print('Before:', smallest)
for itervar in [3, 41, 12, 9, 74, 15]:
if smallest is None or itervar < smallest:
smallest = itervar
print('Loop:', itervar, smallest)
print('Smallest:', smallest)
Di nuovo, il più piccolo è il “più piccolo finora”, prima, durante e dopo l’e-
secuzione del ciclo. Al termine del ciclo, più piccolo contiene il valore minimo
dell’elenco. Come per i due esempi precedenti, conteggio e somma, le funzioni
integrate max () e min () rendono superflua la creazione di questi script. Quella
che segue è una semplice versione della funzione min () integrata in Python:
def min(values):
smallest = None
for value in values:
if smallest is None or value < smallest:
smallest = value
return smallest
5.8 Debug
Quando iniziate a scrivere programmi più complessi, potresti investire più tempo
per il debug. Più codice significa più possibilità di commettere errori e più spazio
concesso agli errori per nascondersi.
Un modo per ridurre il tempo di debug è effettuare un “debug per bisezione”. Ad
esempio, se ci sono 100 righe nel tuo programma e le controlli una alla volta, ci
vorranno 100 step. Prova invece ad analizzare il problema dal centro. Esamina la
parte centrale del programma, o la sezione più vicina a questa, per trovare un valore
intermedio da controllare. Aggiungi un’istruzione print (o qualcos’altro che abbia
un output verificabile) ed esegui il programma. Se il valore di riferimento centrale
non risulta corretto, il problema deve essere nella prima metà del programma.
Se è corretto, il problema è probabilmente nella seconda metà. Ogni volta che
esegui un controllo come questo, dimezzi il numero di righe da controllare. Dopo
64 CAPITOLO 5. ITERAZIONE
sei passaggi (che sono molto meno di 100), dovresti scendere a una o due righe
di codice, almeno in teoria. Nella pratica non è sempre chiaro capire quale sia il
“centro del programma” e non è sempre possibile controllarlo. Non ha senso contare
le righe e trovare il punto centrale esatto. Pensa invece ai punti del programma in
cui potrebbero esserci errori e dove sia più facile effettuare un controllo. Quindi
scegli un punto in cui effettuare il controllo in modo che ci siano le stesse probabilità
che l’errore si trovi nella precedente o successiva porzione di codice.
5.9 Glossario
Accumulatore Una variabile utilizzata in un ciclo per sommare o accumulare un
risultato.
Contatore Una variabile utilizzata in un ciclo per contare il numero di volte in
cui si è verificato qualcosa. Un contatore viene impostato su zero e quindi
incrementato ogni volta che vogliamo “contare” qualcosa.
Decremento Un aggiornamento che riduce il valore di una variabile.
Inizializzare L’assegnazione che fornisce un valore iniziale a una variabile che
verrà aggiornata.
Incremento Un aggiornamento che aumenta il valore di una variabile (spesso di
uno).
Ciclo infinito Un ciclo in cui la condizione di fine non è mai soddisfatta o per il
quale non esiste una tale condizione.
Iterazione Esecuzione ripetuta di un insieme di istruzioni utilizzando una funzio-
ne che evoca se stessa o un ciclo.
5.10 Esercizi
Esercizio 1: Scrivi un programma che legga ripetutamente i numeri fino a quando
l’utente non digiti “finito”. Una volta che viene digitato “finito”, dovrà essere
visualizzato il totale, il conteggio e la media dei numeri. Se l’utente dovesse digitare
qualcosa di diverso da un numero, occorrerà rilevare l’errore usando try e except,
visualizzare un messaggio di errore e passare al numero successivo.
Enter a number: 4
Enter a number: 5
Enter a number: bad data
Invalid input
Enter a number: 7
Enter a number: done
16 3 5.333333333333333
Stringhe
>>> print(letter)
a
65
66 CAPITOLO 6. STRINGHE
b a n a n a
[0] [1] [2] [3] [4] [5]
Per ottenere l’ultima lettera di una stringa, potresti essere tentato di provare
qualcosa del genere:
Il motivo del messaggio d’errore è che non vi è alcuna lettera in “banana” con
l’indice 6. Considerato che abbiamo iniziato a contare da zero, le sei lettere sono
numerate da 0 a 5. Per ottenere l’ultimo carattere, dovrai sottrarre 1 a length:
In alternativa, puoi usare indici negativi, che partono a contare dalla fine della
stringa. L’espressione fruit [-1] ci darà l’ultima lettera, fruit [-2] ci darà la
penultima, e così via.
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1
6.4. SEGMENTI DI STRINGHE 67
Questo ciclo scorre la stringa e mostra una singola lettera su una riga separata.
La condizione del ciclo è index < len(fruit), quindi quando index è uguale
alla lunghezza della stringa, la condizione è falsa e il corpo del ciclo non viene
più eseguito. L’ultimo carattere a cui si accede è quello con l’indice uguale a
len(fruit)-1, che è l’ultimo carattere nella stringa.
Esercizio 1: Scrivi un ciclo while che inizi dall’ultimo carattere della stringa e
proceda fino al primo carattere della stringa, visualizzando ogni lettera su una riga
separata, tranne che all’indietro.
Un altro modo per scrivere uno scorrimento è con un ciclo for:
Ogni volta che viene eseguito il ciclo, viene assegnato alla variabile char il carattere
successivo nella stringa. Il ciclo si ripete fino a quando non rimane alcun carattere.
L’operatore restituisce la parte della stringa dal carattere indicato con il primo
indice (incluso) al carattere indicato con il secondo indice (escluso). Se si omette il
primo parametro (prima dei due punti), il segmento parte dall’inizio della stringa.
Se invece si omette il secondo, il segmento arriva alla fine della stringa:
Una stringa vuota è uguale a qualsiasi altra stringa nonostante non contenga
caratteri ed abbia lunghezza pari a 0.
Esercizio 2: Dato che fruit è una stringa, cosa significa fruit[:]?
68 CAPITOLO 6. STRINGHE
Il motivo dell’errore è che le stringhe sono immutabili, cioè non è possibile mo-
dificare una stringa esistente. La cosa migliore che puoi fare è creare una nuova
stringa che sia una variante dell’originale:
word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print(count)
6.7 L’operatore in
La parola in è un operatore booleano che prende due stringhe e restituisce True
se la prima è contenuta nella seconda:
if word == 'banana':
print('All right, bananas.')
Altre operazioni di comparazione sono utili per mettere delle parole in ordine
alfabetico:
Python non gestisce le lettere maiuscole e minuscole nello stesso modo in cui fanno
le persone: tutte le lettere maiuscole vengono prima delle lettere minuscole, quindi:
capitalize(...)
S.capitalize() -> str
Questa forma di notazione con il punto specifica il nome del metodo (upper) e il
nome della stringa (word) a cui applicare il metodo. Le parentesi vuote indicano
che questo metodo non accetta argomenti.
La chiamata di un metodo è chiamata invocazione: nel caso precedente diremo che
stiamo invocando upper sulla stringa word.
Ad esempio, esiste un metodo per le stringhe chiamato “find” che cerca l’eventuale
posizione di una stringa o segmento all’interno di un’altra:
>>> print(index)
1
In questo esempio, hai invocato find su word e passato come parametro la lettera
che stiamo cercando. Il metodo find può cercare sia segmenti di stringhe sia
caratteri:
>>> word.find('na')
2
Questa funzione può ricevere, come secondo argomento, l’indice da cui dovrebbe
iniziare la ricerca:
>>> word.find('na', 3)
4
Noterai che startswith richiede che ci sia un’esatta corrispondenza tra lettere
maiuscole e minuscole. Può quindi tornare utile prendere una stringa e convertirla
tutta in minuscolo tramite il metodo lower.
Usiamo una applicazione del metodo find che ci permette di specificare la posizione
nella stringa da cui vogliamo che find inizi a guardare. Dividiamo, estraiamo i
caratteri dalla posizione successiva al simbolo chiocciola fino a quello che precede
lo spazio vuoto escluso.
La documentazione per find è disponibile all’indirizzo https://docs.python.org/3.
5/library/stdtypes.html#string-methods.
>>> camels = 42
>>> '%d' % camels
'42'
6.12. DEBUG 73
Il risultato è la stringa “42” che non deve essere confusa con il numero intero 42.
Una sequenza di formato può comparire in qualsiasi punto della stringa, pertanto
ti è possibile inserire un valore in una frase:
>>> camels = 42
>>> 'I have spotted %d camels.' % camels
'I have spotted 42 camels.'
Se in una stringa c’è più di una sequenza, il secondo operando deve essere una
tupla ˆ [Una tupla è una sequenza di valori separati da virgole all’interno di una
coppia di parentesi. Tratteremo le tuple nel Capitolo 10]. Ogni sequenza di formato
corrisponde a un elemento della tupla, nell’ordine indicato. Nell’esempio seguente
viene utilizzato “%d” per formattare un numero intero, “%g” per formattare un
decimale a virgola mobile (non chiedetemi perché) e “%s” per formattare una
stringa:
Nel primo esempio non sono presenti abbastanza elementi. Nel secondo l’elemento
è del tipo sbagliato. L’operatore di formattazione è potente, ma può essere difficile
da usare. Puoi leggere informazioni più dettagliate su https://docs.python.org/3.
5/library/stdtypes.html#printf-style-string-formatting.
6.12 Debug
Un talento che dovresti coltivare mentre programmi è il chiederti sempre: “Cosa
potrebbe andare storto qui?” o in alternativa “Quale imprevedibile cosa potrebbe
essere fatta dal nostro utente per mandare in crash il mio programma (apparen-
temente) perfetto?”. Ad esempio, guarda il programma che ti ho mostrato per
dimostrare il ciclo while nel capitolo sull’iterazione:
while True:
line = input('> ')
if line[0] == '#':
continue
if line == 'done':
break
print(line)
print('Done!')
# Code: http://www.py4e.com/code3/copytildone2.py
74 CAPITOLO 6. STRINGHE
Il codice funziona bene finché non viene inserita una riga vuota. Quindi non essendo
presente un carattere nella posizione zero, viene visualizzato un traceback. Ci sono
due soluzioni per rendere la riga tre “sicura” anche se la riga è vuota. La prima
è semplicemente usare il metodo startswith che restituirà False se la stringa è
vuota.
if line.startswith('#'):
6.13 Glossario
Contatore Una variabile utilizzata per contare qualcosa; è solitamente inizializ-
zata a zero e poi incrementata.
Stringa vuota Una stringa senza caratteri e con lunghezza pari a 0, rappresen-
tata da due virgolette.
Operatore di formato Un operatore % che riceve una stringa di formato e una
tupla e genera una stringa che include gli elementi della tupla formattata
come specificato dalla stringa di formato.
Sequenza di formato Una sequenza di caratteri in una stringa di formato (es.
%d) che specifica come deve essere formattato un dato.
Stringa di formato Una stringa utilizzata con l’operatore di formattazione che
contiene sequenze di formato.
Flag Una variabile booleana utilizzata per indicare se una condizione è vera o
falsa.
Invocazione Un’istruzione che serve per chiamare un metodo.
Immutabile La proprietà della sequenza i cui elementi non possono essere alterati.
6.14 Esercizi
Esercizio 5: prendi il seguente codice Python contenente una stringa: str =
'X-DSPAM-Confidence:0.8475
Usa find e la segmentazione delle stringhe per estrarre la porzione di stringa dopo
il carattere “:” e utilizza la funzione float per convertire la stringa estratta in un
numero a virgola mobile.
Esercizio 6: leggi la documentazione dei metodi di stringa su https://docs.pyt
hon.org/3.5/library/stdtypes.html#string-methods Potrebbe essere il caso di fare
degli esperimenti con alcuni di essi per assicurarti di aver capito come funzionano.
strip e replace sono particolarmente utili.
La documentazione utilizza una sintassi che potrebbe essere fonte di confusione.
Ad esempio, in find(sub[, start[, end]]), le parentesi indicano argomenti op-
zionali. In altre parole sub è necessario, start è facoltativo ma nel caso tu lo
inserisca, allora end diventa opzionale.
76 CAPITOLO 6. STRINGHE
Capitolo 7
File
7.1 Persistenza
Finora, abbiamo imparato come scrivere programmi e comunicare le nostre inten-
zioni all’unità di elaborazione centrale usando l’esecuzione condizionale, le funzioni
e le iterazioni. Abbiamo anche imparato come creare e utilizzare strutture di dati
nella memoria principale. La CPU e la memoria RAM sono i luoghi dove il nostro
software lavora e funziona. È dove si forma tutto il “pensiero”. Ma se ricordate le
nostre discussioni sull’architettura hardware, una volta che l’alimentazione viene
tolta, qualsiasi cosa memorizzata nella CPU o nella memoria principale viene can-
cellata. Praticamente, i nostri programmi sono stati solo fugaci esercizi divertenti
per imparare Python.
What
Software Next?
Periferiche Central
di input e Processing Rete
di output Unit
Memoria Memoria
principale secondaria
In questo capitolo, iniziamo a lavorare con i file nella Memoria Secondaria che,
come scritto, non viene cancellata quando si spegne il pc e, nel caso di utilizzo di
un’unità flash USB, consente di rimuovere dal sistema i dati prodotti dai nostri
script e trasportarli su un altro sistema. Ci concentreremo principalmente sulla
77
78 CAPITOLO 7. FILE
open H
A
close From stephen.m..
N Return-Path: <p..
read D Date: Sat, 5 Jan ..
L To: source@coll..
write E From: stephen...
Subject: [sakai]...
Details: http:/...
Il tuo programma …
python
Se il file non esiste, open fallirà visualizzando un traceback e verrà fornito alcun
handle che permetta di accedere ai contenuti del file:
Successivamente useremo try e except per gestire con più eleganza la situazione
in cui si prova ad aprire un file che non esiste.
7.3. FILE DI TESTO E RIGHE 79
Un file di testo può essere pensato come una sequenza di righe, proprio come una
stringa Python può essere vista come una sequenza di caratteri. Ad esempio, questo
è un file di testo dimostrativo che registra l’attività di posta di vari appartenenti
di un team di sviluppo di progetti open source:
From [email protected] Sab 5 gen 09:14:16 2008
Return-Path: <[email protected]>
Date: Sat, 5 Jan 2008 09:12:18 -0500
To: [email protected]
From: [email protected]
Subject: [sakai] svn commit: r39772 - content / branches /
Details: http: //source.sakaiproject.org/viewsvn/? view = rev & rev
= 39772
...
L’intero file di interazioni di posta è disponibile su
www.py4e.com/code3/mbox.txt
e una versione ridotta del file è disponibile su
www.py4e.com/code3/mbox-short.txt
Questi file sono strutturati in un formato standard per file contenenti più messaggi
di posta. Le righe che iniziano con “From” separano i messaggi e le righe che
iniziano con “From:” fanno parte dei messaggi. Per ulteriori informazioni sul
formato mbox, consulta:
en.wikipedia.org/wiki/Mbox.
Per suddividere il file in righe, c’è un carattere speciale che rappresenta la “fine
della riga” chiamato carattere newline.
In Python, il carattere newline viene rappresentato con backslash-n (\n) tra le
costanti di stringa. Anche se sembra essere composto da due caratteri, è in realtà
un singolo carattere. Prendi in considerazione il seguente listato. Il contenuto
che stiamo inserendo nella variabile “stuff” nell’interprete, ci mostra che la stringa
contiene i caratteri \\n. Quando però la stampiamo tramite il comando print,
puoi notare che la stringa viene mostrata su due righe, spezzata nel punto dove è
presente newline.
Nota che nel secondo caso la lunghezza della stringa X\nY è di tre caratteri perché
il carattere newline conta come un singolo carattere. Quindi, quando guardiamo
le linee in un file, dobbiamo immaginare che ci sia uno speciale carattere invisibile
chiamato newline alla fine di ogni riga che indica la fine della riga. Quindi il
carattere newline divide i caratteri del file in righe.
fhand = open('mbox-short.txt')
count = 0
for line in fhand:
count = count + 1
print('Line Count:', count)
# Code: http://www.py4e.com/code3/open.py
Possiamo usare il file handle come sequenza per il ciclo for che, in questo caso, ne
trova e visualizza semplicemente il numero di righe . La traduzione approssimativa
del ciclo for in italiano è, “per ogni riga nel file indicato dal file handle, aggiungi
uno alla variabile count.” La ragione per cui la funzione open non legge subito
l’intero file è perchè potrebbe essere di notevoli dimensioni e contenere molti giga-
byte di dati. L’istruzione open richiede lo stesso tempo indipendentemente dalla
dimensione del file. Il ciclo for effettivamente fa in modo che vengano letti i dati
contenuti nel file.
Quando il file viene letto utilizzando un ciclo for, Python si occupa di suddividere
i dati in righe separate usando il carattere newline. Python legge ogni riga fino al
carattere newline e lo include come ultimo carattere nella variabile line in ogni
iterazione del ciclo for.
Poiché il ciclo for prende in considerazione i dati una riga alla volta, può legge-
re e contare efficientemente le righe in file di grandi dimensioni senza esaurire la
memoria principale. Lo script precedente può contare le righe in file di qualsiasi
dimensione utilizzando pochissima memoria dato che ogni riga, dopo essere stata
letta e “contata” , viene subito dopo scartata. Se il file è relativamente picco-
lo rispetto alla dimensione della memoria principale, puoi leggere l’intero file in
un’unica stringa applicando il metodo read sul file handle:
In questo esempio, l’intero contenuto del file mbox-short.txt (tutti i 94.626 ca-
ratteri) viene letto direttamente nella variabile inp. Usiamo il taglio delle stringhe
per stampare i primi 20 caratteri dei dati di stringa memorizzati in inp.
7.5. RICERCHE IN UN FILE 81
Quando il file viene letto in questo modo, tutti i caratteri, inclusi tutte le righe
e i caratteri newline, vengono considerati come una grande stringa nella variabile
inp. Ricorda che questa applicazione della funzione open dovrebbe essere usata
solo se i dati del file “entrano” comodamente nella memoria principale del vostro
computer.
Se il file è troppo grande per adattarsi alla memoria principale, dovresti scrivere il
tuo programma per leggere il file in blocchi utilizzando un ciclo for o while.
fhand = open('mbox-short.txt')
count = 0
for line in fhand:
if line.startswith('From:'):
print(line)
# Code: http://www.py4e.com/code3/search1.py
From: [email protected]
From: [email protected]
From: [email protected]
From: [email protected]
...
L’output sembra ottimo dato che le uniche righe che stiamo visualizzando sono
quelle che iniziano con “From:”. Perché allora stiamo vedendo delle righe vuote in
più? Ciò è dovuto al carattere invisibile newline.
Ognuna delle righe termina con il carattere newline. L’istruzione print visualizza
la stringa nella variabile line che include una riga e quindi print visualizza un’altra
nuova riga, ottenendo l’effetto di doppia spaziatura che vediamo. Potremmo usare
il taglio delle stringhe per visualizzare tutto tranne l’ultimo carattere, ma c’è un
approccio più semplice: usare il metodo rstrip per rimuovere gli spazi bianchi dal
lato destro di una stringa, come nell’esempio seguente:
82 CAPITOLO 7. FILE
fhand = open('mbox-short.txt')
for line in fhand:
line = line.rstrip()
if line.startswith('From:'):
print(line)
# Code: http://www.py4e.com/code3/search2.py
From: [email protected]
From: [email protected]
From: [email protected]
From: [email protected]
From: [email protected]
From: [email protected]
From: [email protected]
...
Man mano che i tuoi script di gestione dei file diventano più complessi, potresti
voler strutturare i cicli di ricerca utilizzando continue. L’idea di base del ciclo
di ricerca è che stai cercando le righe “interessanti” e ignorando quelle “non inte-
ressanti”; inoltre, quando trovi una riga interessante, che tu voglia farci qualcosa.
Potresti strutturare questo tipo di ciclo come nell’esempio seguente:
fhand = open('mbox-short.txt')
for line in fhand:
line = line.rstrip()
# Skip 'uninteresting lines'
if not line.startswith('From:'):
continue
# Process our 'interesting' line
print(line)
# Code: http://www.py4e.com/code3/search3.py
fhand = open('mbox-short.txt')
for line in fhand:
7.6. FAR SCEGLIERE ALL’UTENTE IL NOME DEL FILE 83
line = line.rstrip()
if line.find('@uct.ac.za') == -1: continue
print(line)
# Code: http://www.py4e.com/code3/search4.py
Non è il caso di modificare il nostro codice Python ogni volta che vogliamo elabo-
rare un file diverso: è più utile chiedere all’utente di inserire il nome del file ogni
volta che il programma viene eseguito. In questo modo è possibile usare il nostro
programma su file diversi senza modificare continuamente il codice.
È una modifica abbastanza semplice da implementare: l’utente potrà inserire il
nome del file utilizzando la funzione input:
# Code: http://www.py4e.com/code3/search6.py
Il nome del file inserito dall’utente viene salvato nella variabile fname e successiva-
mente aperto. Ora è possibile eseguire il programma su file diversi senza alterarne
continuamente il codice.
python search6.py
Enter the file name: mbox.txt
There were 1797 subject lines in mbox.txt
python search6.py
Enter the file name: mbox-short.txt
There were 27 subject lines in mbox-short.txt
84 CAPITOLO 7. FILE
python search6.py
Enter the file name: missing.txt
Traceback (most recent call last):
File "search6.py", line 2, in <module>
fhand = open (fname)
FileNotFoundError: [Errno 2] No such file or directory: 'missing.txt'
python search6.py
Enter the file name: na na boo boo
Traceback (most recent call last):
file "search6.py", riga 2, in <module>
fhand = open (fname)
FileNotFoundError: [Errno 2] No such file or directory : 'na na boo boo'
Non ridere. Gli utenti faranno tutto quello che possono, di proposito o con in-
tenzioni malevole, per violare i programmi. In effetti, un componente importante
di qualsiasi team di sviluppo software è la persona o il gruppo chiamato Quality
Assurance (o QA in breve) il cui compito è quello di fare le cose più folli possibili
nel tentativo di violare il software che hanno creato.
Il team addetto al controllo qualità è responsabile della ricerca dei difetti nei pro-
grammi prima di consegnarli agli utenti finali che potrebbero acquistare il software
o pagare il nostro stipendio per sviluppare software. Quindi, anche se non sembra,
la squadra di controllo qualità è la migliore amica dello sviluppatore.
Tornando al nostro script, una volta individuato il difetto nel programma, possiamo
sistemarlo elegantemente usando la struttura try/except: dobbiamo supporre
che la chiamata open potrebbe fallire e aggiungere del codice come nell’esempio
seguente:
count = count + 1
print('There were', count, 'subject lines in', fname)
# Code: http://www.py4e.com/code3/search7.py
python search7.py
Inserisci il nome del file: mbox.txt
There were 1797 subject lines in mbox.txt
python search7. py
Immettere il nome del file: na na boo boo
File cannot be opened: na na boo boo
Per scrivere all’interno di un file, devi aprirlo inserendo “w” come secondo parame-
tro:
Aprendo in modalità di scrittura un file che già esiste ne cancellerai i dati già
presenti e lo avvierai nuovamente, quindi fai attenzione! Se invece il file non esiste,
ne viene creato uno nuovo.
Il write applicato al file handle dell’oggetto permette di salvare i dati nel file,
restituendo il numero di caratteri scritti.
La modalità di scrittura predefinita per scrivere (e leggere) stringhe, è testo.
86 CAPITOLO 7. FILE
Di nuovo, l’oggetto file tiene traccia di dove si trova, quindi se chiami nuovamente
write, i nuovi dati verranno aggiunti alla fine del file.
Dobbiamo assicurarci di gestire le estremità delle righe mentre scriviamo nel file
inserendo esplicitamente il carattere newline quando vogliamo terminare una riga.
L’istruzione print aggiunge automaticamente una nuova riga, ma il metodo write
non inserisce automaticamente un carattere newline.
Quando hai finito di scrivere, devi chiudere il file per assicurarti che l’ultimo bit
di dati venga effettivamente scritto sul disco in modo che non vada perso se si
interrompe l’alimentazione.
>>> fout.close ()
Potremmo chiudere i file che apriamo anche per leggerli, ma possiamo essere un
po’ approssimativi se apriamo solo alcuni file dato che Python si assicura che tutti
i file aperti vengano chiusi quando il programma termina . Quando scriviamo dei
file, vogliamo chiudere esplicitamente i file in modo da non lasciare nulla al caso.
7.9 Debug
Quando leggi e scrivi il contenuto di file, potresti incontrare dei problemi con gli
spazi bianchi. Questi errori possono essere difficili da risolvere perché gli spazi, le
schede e gli a capo sono normalmente invisibili:
In questi casi la funzione integrata repr può tornarti utile. Prende qualsiasi oggetto
come argomento e restituisce lo stesso oggetto sotto forma di stringa. Per quanto
riguarda le stringhe, i caratteri di spaziatura vengono rappresentati con sequenze
di backslash:
>>> print(repr(s))
'1 2\t 3\n 4'
7.10. GLOSSARIO 87
7.10 Glossario
7.11 Esercizi
python shout.py
Enter a file name: mbox-short.txt
FROM [email protected] SAT JAN 5 09:14:16 2008
RETURN-PATH: <[email protected]>
RECEIVED: FROM MURDER (MAIL.UMICH.EDU [141.211.14.90])
BY FRANKENSTEIN.MAIL.UMICH.EDU (CYRUS V2.3.8) WITH LMTPA;
SAT, 05 JAN 2008 09:14:16 -0500
`X-DSPAM-Confidence:` **`0.8475` **
88 CAPITOLO 7. FILE
Quando trovi una riga che inizia con “X-DSPAM-Confidence:” seziona la riga per
estrarre il numero a virgola mobile contenuto nella stessa. Conta queste righe e
quindi calcolane il totale dei valori di spam confidence.
Quando raggiungi la fine del file, visualizza la media dei valori di spam confidence.
python egg.py
Immettere il nome del file: mbox.txt
There were 1797 subject lines in mbox.txt
python egg.py
Immettere il nome del file: missing.tyxt
File cannot be opened: missing.tyxt
python egg.py
Inserisci il nome del file: na na boo boo
NA NA BOO BOO TO YOU - You have been punk'd!
Non ti sto incoraggiando a inserire degli Easter Egg nei tuoi programmi, diciamo
che questo è solo un esercizio.
Capitolo 8
Elenchi
89
90 CAPITOLO 8. ELENCHI
>>> print(cheeses[0])
Cheddar
A differenza delle stringhe, gli elenchi sono modificabili in quanto è possibile mo-
dificare l’ordine degli elementi in un elenco o riassegnare un elemento in un elenco.
Quando la parentesi è visualizzata sul lato sinistro di un’assegnazione, identifica
l’elemento dell’elenco che verrà modificato.
Funziona bene solo se devi leggere gli elementi di un elenco. Se vuoi scrivere o
aggiornare degli elementi, devi lavorare con gli indici. Un modo comune per farlo
è combinare le funzioni range e len:
for i in range(len(numbers)):
numbers[i] = numbers[i] * 2
Questo ciclo scorre l’elenco e aggiorna ciascun elemento. len restituisce il numero
di elementi dell’elenco. range restituisce un elenco di indici da 0 a n−1, dove n è la
lunghezza dell’elenco. Ad ogni ripetizione del ciclo, i assume l’indice dell’elemento
successivo. L’istruzione di assegnazione nel corpo usa i per leggere il vecchio valore
dell’elemento e assegnare quello nuovo.
Un ciclo for su una lista vuota non esegue mai il blocco:
for x in empty:
print('This never happens.')
Come ti ho scritto, un elenco può contenere un altro elenco, che comunque conta
come se fosse un singolo elemento. Per farti un esempio, la lunghezza di questo
elenco è pari a quattro:
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = a + b
>>> print(c)
[1, 2, 3, 4, 5, 6]
>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
Nel primo esempio 0 viene ripetuto quattro volte, nel secondo l’elenco viene ripe-
tuto tre volte.
92 CAPITOLO 8. ELENCHI
>>> t[:]
['a', 'b', 'c', 'd', 'e', 'f']
Poiché gli elenchi sono editabili, spesso è utile farne prima una copia e poi eseguire
operazioni che le aggreghino, le invertano o le tronchino.
Un operatore di slicing posto sul lato sinistro di un’assegnazione ti permette di
aggiornare più elementi:
extend accetta un elenco come argomento e ne accoda tutti gli elementi all’elenco
specificato:
La maggior parte dei metodi applicabili agli elenchi non accettano argomenti: mo-
dificano l’elenco e restituiscono None. Se scrivessi accidentalmente t = t.sort(),
rimarresti molto deluso dal risultato.
Come al solito, lo slice seleziona tutti gli elementi fino al secondo indice escluso.
94 CAPITOLO 8. ELENCHI
Puoi utilizzare la funzione sum() che funziona solo se tutti gli elementi dell’elenco
sono numeri. Le altre funzioni (max(), len(), ecc.) funzionano con stringhe e altri
elementi che possano essere comparati.
Potremmo riscrivere un programma visto in precedenza che calcolava la media di
un insieme di numeri immessi dall’utente utilizzando, stavolta, un elenco.
Innanzitutto ecco lo script che abbiamo scritto per calcolare la media dei numeri
inseriti:
total = 0
count = 0
while (True):
inp = input('Enter a number: ')
if inp == 'done': break
value = float(inp)
total = total + value
count = count + 1
# Code: http://www.py4e.com/code3/avenum.py
numlist = list()
while (True):
inp = input('Enter a number: ')
8.9. ELENCHI E STRINGHE 95
# Code: http://www.py4e.com/code3/avelist.py
nell’esempio precedente abbiamo creato un elenco vuoto prima che inizi il ciclo, e
ogni volta che veniva inserito un numero lo abbiamo aggiunto all’elenco. Alla fine
del programma, abbiamo calcolato semplicemente la somma dei numeri dell’elenco
e la dividiamo per il conteggio degli elementi inseriti per ottenere la media.
>>> s = 'spam'
>>> t = list(s)
>>> print(t)
['s', 'p', 'a', 'm']
dato che list è un nome riservato, evita di usarlo come nome per una variabile.
Ti consiglio anche di evitare la lettera l perché assomiglia troppo al numero 1.
Personalmente preferisco la t.
La funzione list suddivide una stringa in singole lettere. Se invece vuoi dividere
una stringa in singole parole, ti torna utile il metodo split:
Dopo aver usato split per spezzare la stringa in un elenco di parole, puoi usare
l’operatore indice (parentesi quadre) per cercare una particolare parola nell’elenco.
Puoi chiamare split con un argomento opzionale chiamato delimitatore che speci-
fica quali caratteri usare come separatore delle parole. Nell’esempio seguente viene
utilizzato un trattino come delimitatore:
>>> s = 'spam-spam-spam'
>>> delimiter = '-'
>>> s.split(delimiter)
['spam', 'spam', 'spam']
96 CAPITOLO 8. ELENCHI
In questo caso il delimitatore è uno spazio, perciò join ne aggiunge uno tra le
varie parole. Se dovessi aver bisogno di concatenare delle stringhe senza spazi,
puoi usare come delimitatore una stringa vuota “”.
fhand = open('mbox-short.txt')
for line in fhand:
line = line.rstrip()
if not line.startswith('From '): continue
words = line.split()
print(words[2])
# Code: http://www.py4e.com/code3/search5.py
Sat
Fri
Fri
Fri
...
In seguito impareremo tecniche sempre più sofisticate per scegliere le righe su cui
lavorare e come smontarle per trovare l’informazione precisa che stiamo cercando.
8.11. OGGETTI E VALORI 97
a = 'banana'
b = 'banana'
a ‘banana’ a
‘banana’
b ‘banana’ b
Nel primo caso a e b si riferiscono a due oggetti diversi che hanno lo stesso valore
mentre nel secondo caso si riferiscono allo stesso oggetto.
Puoi usare l’operatore is per verificare se due variabili si riferiscono allo stesso
oggetto.
>>> a = 'banana'
>>> b = 'banana'
>>> a is b
True
In questo esempio, Python ha creato un unico oggetto stringa a cui fanno riferi-
mento sia a che b.
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False
In questo caso, potremmo dire che i due elenchi sono equivalenti, dato che conten-
gono gli stessi elementi, ma non identici, perché non sono lo stesso oggetto. Se
due oggetti sono identici sono anche equivalenti, ma se sono equivalenti non sono
necessariamente identici.
8.12 Alias
Se a si riferisce ad un oggetto e assegni b = a, allora entrambe le variabili si
riferiscono allo stesso oggetto:
>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
>>> b[0] = 17
>>> print(a)
[17, 2, 3]
a = 'banana'
b = 'banana'
def delete_head(t):
del t[0]
>>> t1 = [1, 2]
>>> t2 = t1.append(3)
>>> print(t1)
[1, 2, 3]
>>> print(t2)
None
>>> t3 = t1 + [3]
>>> print(t3)
[1, 2, 3]
>>> t2 is t3
False
def bad_delete_head(t):
t = t[1:] # ERRATO!
L’operatore slice crea un nuovo elenco e l’assegnazione fa in modo che “t” si riferisca
ad esso, senza che tutto questo abbia effetto sull’elenco che è stato passato come
argomento.
Un’alternativa è scrivere una funzione che crei e restituisca un nuovo elenco. Ad
esempio tail restituisce tutti gli elementi di un elenco tranne il primo:
def tail(t):
return t[1:]
Ricorda che questa funzione lascia intatto l’elenco originale. Ecco come viene
utilizzata:
Esercizio 1: Scrivi una funzione chiamata chop che prenda un elenco, lo modifichi
rimuovendo il primo e l’ultimo elemento e restituisca None.
Quindi scrivi una funzione chiamata middle che prenda un elenco e restituisca un
nuovo elenco contenente tutti gli elementi tranne il primo e l’ultimo.
100 CAPITOLO 8. ELENCHI
8.14 Debug
L’uso incauto degli elenchi (e di altri oggetti mutabili) può portarti a passare
lunghe ore nelle operazioni di debug.
Ecco alcuni problemi comuni e dei suggerimenti su come evitarli:
1. Non dimenticare che la maggior parte dei metodi applicabili agli elenchi
modificano l’argomento e restituiscono None. Questo è l’opposto del compor-
tamento dei metodi di stringa che restituiscono una nuova stringa e lasciano
immutato l’originale.
Stai attento se sei abituato a scrivere codice per le stringhe come questo:
word = word.strip ()
t = t.sort() # ERRATO!
t.append (x)
t = t + [x]
t.append([x]) # ERRATO!
t = t.append(x) # ERRATO!
t + [x] # ERRATO!
t = t + x # ERRATO!
orig = t [:]
t.sort ()
fhand = open('mbox-short.txt')
for line in fhand:
words = line.split()
if words[0] != 'From' : continue
print(words[2])
python search8.py
Sat
Traceback (most recent call last):
File "search8.py", line 5, in <module>
if words[0] != 'From' : continue
IndexError: list index out of range
comando print della variabile words prima della riga cinque. Aggiungiamo
persino un prefisso “Debug:” alla riga in modo da poter mantenere separato
l’output normale da quello di debug.
X-DSPAM-Result: Innocent
X-DSPAM-Processed: Sat Jan 5 09:14:16 2008
X-DSPAM-Confidence: 0.8475
X-DSPAM-Probability: 0.0000
Details: http://source.sakaiproject.org/viewsvn/?view=rev&rev=39772
fhand = open('mbox-short.txt')
count = 0
for line in fhand:
words = line.split()
# print 'Debug:', words
if len(words) == 0 : continue
8.15. GLOSSARIO 103
Esercizio 2: scopri quale riga del programma precedente non è ancora adeguata-
mente protetta. Cerca di costruire un file di testo che faccia fallire il programma
e quindi modifica il programma in modo che la riga sia adeguatamente protetta
e testala nuovamente per essere sicuro che gestisca correttamente il nuovo file di
testo.
8.15 Glossario
Alias Circostanza in cui due o più variabili si riferiscono allo stesso oggetto.
Delimitatore Un carattere o una stringa utilizzata per indicare il punto in cui
deve essere divisa una stringa.
Elemento Uno dei valori in un elenco (o altra sequenza); viene chiamato anche
oggetto.
Equivalente Avere lo stesso valore.
Indice Un valore intero che indica la posizione di un elemento in un elenco.
Identico Essere lo stesso oggetto (che implica l’equivalenza).
Elenco Una sequenza di valori.
Elaborazione trasversale Accesso sequenziale a ciascun elemento in un elenco.
8.16 Esercizi
Esercizio 4: scarica una copia del file www.py4e.com/code3/romeo.txt
Scrivi un programma che lo apra e lo legga riga per riga. Dividi la riga in un elenco
di parole usando la funzione split.
Controlla se ogni parola è già presente in un elenco. Se la parola non è nell’elenco,
aggiungila. Al termine del programma, ordina e visualizza in ordine alfabetico le
parole risultanti.
Esercizio 5: Scrivi un programma per leggere i dati della casella di posta e quando
trova la riga che inizia con “From”, divida la riga in parole usando la funzione split.
Siamo interessati a sapere chi ha inviato il messaggio indicato nella parola delle
righe che iniziano con From.
From [email protected] Sat 5 Jan 09:14:16 2008
Analizza la riga From per visualizzarne la seconda parola, quindi conta anche il
numero di righe From (non From:), visualizzandone il risultato alla fine.
Questo è un buon esempio di output con poche righe rimosse:
python fromcount.py
Enter a file name: mbox-short.txt
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
There were 27 lines in the file with From as the first word
Enter a number: 6
Enter a number: 2
Enter a number: 9
8.16. ESERCIZI 105
Enter a number: 3
Enter a number: 5
Enter a number: done
Maximum: 9.0
Minimum: 2.0
106 CAPITOLO 8. ELENCHI
Capitolo 9
Dizionari
Questa linea crea un oggetto che associa la chiave “one” al valore “uno”. Se visua-
lizzi nuovamente il dizionario, vedrai una coppia chiave-valore con due punti tra la
chiave e il valore:
>>> print(eng2sp)
{'one': 'uno'}
107
108 CAPITOLO 9. DIZIONARI
L’ordine delle coppie chiave-valore non è lo stesso. Se digiti lo stesso esempio sul
tuo computer, potresti ottenere un risultato ancora diverso. In generale, l’ordine
degli elementi in un dizionario è imprevedibile.
Ma questo non è un problema perché gli elementi di un dizionario non sono mai
indicizzati con indici interi. Invece, vanno usate le chiavi per cercare i valori
corrispondenti:
>>> print(eng2sp['two'])
'dos'
La chiave 'two' si associa sempre al valore “dos” in modo che l’ordine degli og-
getti non abbia importanza. Se provi a visualizzare una chiave non presente nel
dizionario, otterrai un’eccezione:
>>> print(eng2sp['four'])
KeyError: 'four'
La funzione len può essere utilizzata sui dizionari: restituisce il numero di coppie
chiave-valore:
>>> len(eng2sp)
3
L’operatore in lavora sui dizionari: ti dice se quello che cerchi risulta essere una
chiave del dizionario (non è sufficiente che risulti come un valore).
Per vedere se qualcosa risulta come valore in un dizionario, puoi usare il metodo
values, che restituisce i valori come un elenco e successivamente l’operatore in:
L’operatore in utilizza algoritmi diversi per elenchi e dizionari. Per gli elenchi,
utilizza un algoritmo di ricerca lineare. Man mano che l’elenco si allunga, il tempo
di ricerca si allunga in proporzione diretta alla dimensione dello stesso. Per i dizio-
nari Python utilizza un algoritmo chiamato tabella degli hash che ha una proprietà
notevole: l’operatore in richiede circa la stessa quantità di tempo indipendente-
mente dal numero di elementi presenti in un dizionario. Non ti spiegherò perché
le funzioni di hash siano così magiche, ma puoi ottenere ulteriori informazioni da
wikipedia.org/wiki/Hash_table.
Esercizio 1:
Scarica una copia del file: www.py4e.com/code3/words.txt
Scrivi un programma che legga le parole in words.txt e le memorizzi come chiavi
in un dizionario. Non importa quali siano i valori. Quindi puoi usare l’operatore
in per verificare rapidamente se una stringa è contenuta nel dizionario.
9.1. DIZIONARIO COME INSIEME DI CONTATORI 109
1. Potresti creare 26 variabili, una per ogni lettera dell’alfabeto. Quindi po-
tresti scorrere la stringa e, per ogni carattere, incrementare il contatore
corrispondente probabilmente usando una condizione concatenata.
2. Potresti creare una lista con 26 elementi per poi convertire ciascuno in un
numero (utilizzando la funzione ord), infine utilizzare il numero come indice
nell’elenco e incrementare il contatore appropriato.
3. Potresti creare un dizionario con caratteri come chiavi e contatori come valori
corrispondenti. La prima volta che trovi un nuovo carattere, dovrai aggiun-
gere un elemento al dizionario. Successivamente, dovresti incrementare il
valore di un elemento esistente.
word = 'brontosaurus'
d = dict()
for c in word:
if c not in d:
d[c] = 1
else:
d[c] = d[c] + 1
print(d)
L’istogramma indica che le lettere “a” e “b” compaiono una sola volta; “o” appare
due volte e così via.
110 CAPITOLO 9. DIZIONARI
I dizionari hanno un metodo chiamato get che riceve una chiave e un valore predefi-
nito. Se la chiave è presente nel dizionario, get restituisce il valore corrispondente,
altrimenti restituisce il valore predefinito.
Per esempio:
Possiamo usare get per scrivere il nostro istogramma in modo più rapido. Dato che
il metodo get gestisce automaticamente il caso in cui una chiave non è presente in
un dizionario, possiamo ridurre quattro righe ad una sola ed eliminare l’istruzione
if.
word = 'brontosaurus'
d = dict()
for c in word:
d[c] = d.get(c,0) + 1
print(d)
L’uso del metodo get per semplificare questo ciclo di conteggio sarà un “idioma”
usato molto comunemente in Python e lo vedrai molte altre volte nel resto del libro.
Quindi dovresti fermarti un momento e confrontare il ciclo che utilizza l’istruzione
if e l’operatore in con il ciclo che utilizza il metodo get. Fanno esattamente la
stessa cosa, ma il secondo è più conciso.
quanto uno dei cicli è il ciclo esterno e l’altro il ciclo interno. Poiché il ciclo
interno esegue tutte le sue iterazioni ogni volta che il ciclo esterno effettua una
singola iterazione, pensiamo come se il ciclo interno iterasse “più rapidamente” e
il ciclo esterno iterasse più lentamente.
La combinazione dei due cicli annidati garantisce che conteremo ogni parola di
ogni riga del file in input.
counts = dict()
for line in fhand:
words = line.split()
for word in words:
if word not in counts:
counts[word] = 1
else:
counts[word] += 1
print(counts)
# Code: http://www.py4e.com/code3/count1.py
Quando esegui lo script, vedrai un output grezzo di tutti i conteggi in una serie di
hash disordinati (il file romeo.txt è disponibile su www.py4e.com/code3/romeo.txt).
python count1.py
Enter the file name: romeo.txt
{'and': 3, 'envious': 1, 'already': 1, 'fair': 1,
'is': 3, 'through': 1, 'pale': 1, 'yonder': 1,
'what': 1, 'sun': 2, 'Who': 1, 'But': 1, 'moon': 1,
'window': 1, 'sick': 1, 'east': 1, 'breaks': 1,
'grief': 1, 'with': 1, 'light': 1, 'It': 1, 'Arise': 1,
'kill': 1, 'the': 3, 'soft': 1, 'Juliet': 1}
È un po’ scomodo cercare nel dizionario le parole più comuni e i loro conteggi,
quindi abbiamo bisogno di aggiungere altro codice Python per ottenere un output
che ci sarà più leggibile.
jan 100
chuck 1
annie 42
Il ciclo for scorre le chiavi del dizionario, quindi dobbiamo utilizzare l’operatore di
indice per recuperare il valore corrispondente per ogni chiave. Ecco come appare
l’output:
jan 100
annie 42
Per prima cosa vedrai la lista delle chiavi in modo non ordinato che otteniamo dal
metodo keys. Quindi vedrai le coppie chiave-valore ordinate dal ciclo for.
9.4. ANALISI AVANZATA DEL TESTO 113
Dato che la funzione split cerca spazi e tratta le parole come elementi separati
da spazi, tratteremo le parole “soft!” e “soft” come parole differenti e creeremo
una voce di dizionario separata per ognuna di queste.
Inoltre, poiché il file è in maiuscolo, considereremo “who” e “Who” come parole
diverse con diversi conteggi.
Possiamo risolvere entrambi i problemi usando i metodi per le stringhe lower,
punctuation e translate. Il metodo translate è il più subdolo. Ecco la
documentazione del metodo translate:
line.translate(str.maketrans(fromstr, tostr, deletestr))
Sostituisce i caratteri in fromstr con il carattere nella stessa posizione in tostr ed
elimina tutti i caratteri che sono in deletestr. Fromstr e tostr possono essere
stringhe vuote e il parametro deletestr può essere omesso.
Non specificheremo la tostr, ma useremo il parametro deletestr per elimina-
re tutta la punteggiatura. Lasceremo persino che Python ci fornisca la lista dei
caratteri che considera “punteggiatura”:
import string
counts = dict()
for line in fhand:
line = line.rstrip()
114 CAPITOLO 9. DIZIONARI
print(counts)
# Code: http://www.py4e.com/code3/count2.py
Osserva questo output: è ancora poco gestibile ma possiamo usare Python per darci
esattamente quello che stiamo cercando. Per farlo però ho bisogno di spiegarti come
funzionano le tuple di Python. Riprenderemo questo esempio una volta che avrai
familiarità con queste cose.
9.5 Debug
Nel lavorare con insiemi di dati più grandi può risultare poco pratico eseguire il
debug visualizzando e controllando i dati manualmente. Ecco alcuni suggerimenti
per il debug di insiemi di dati di grandi dimensioni:
Ridimensionare l’input: se possibile, ridurre la dimensione del dataset. Ad esempio,
se il programma deve analizzare un file di testo, inizia con solo le prime 10 righe
o con il campione più piccolo che riesci a trovare. Puoi quindi modificare il file
stesso o (meglio) lo script in modo che legga solo le prime n righe.
Se c’è un errore, riduci n al numero minore di righe in cui si manifesta l’errore, poi
aumentalo gradualmente man mano che trovi e correggi gli errori che si presentano.
Controllare i riepiloghi e i tipi: anziché visualizzare e controllare l’intero set di
dati, prendi in considerazione la visualizzazione di riepiloghi dei dati: ad esempio,
il numero di elementi in un dizionario o il totale di un elenco di numeri.
9.6. GLOSSARIO 115
Una causa comune negli errori di runtime è un valore del tipo errato. Per eseguire
il debug di questo tipo di errore è spesso sufficiente visualizzarne il tipo e agire di
conseguenza.
Inserire autocontrolli: a volte puoi scrivere del codice per verificare automatica-
mente i possibili errori. Ad esempio, se si calcola la media di un elenco di numeri,
è possibile verificare che il risultato non sia più grande o più piccolo di tutti gli
elementi presenti. Questo viene definito “controllo di integrità” perché individua
risultati “completamente illogici”.
Un altro tipo di controllo confronta i risultati di due diversi calcoli per verificare
se sono coerenti. Questo è chiamato “controllo di coerenza”.
Visualizzare bene l’output: la formattazione dell’output di debug può rendere più
facile l’individuazione di un errore.
Ancora una volta, il tempo speso per la costruzione di impalcature (scaffolding)
può ridurre il tempo che dovrai spendere nel debugging.
9.6 Glossario
Dizionario Una correlazione tra una serie di chiavi e i corrispondenti valori.
Tabella di hash L’algoritmo utilizzato per implementare i dizionari Python.
Funzione di hash Una funzione utilizzata da una tabella di hash per calcolare
la posizione di una chiave.
Istogramma Una serie di contatori.
Implementazione Il modo utilizzato per eseguire un calcolo.
Elemento Un altro nome utilizzato per una coppia chiave-valore.
Chiave L’oggetto che appare in un dizionario come prima parte di una coppia
chiave-valore.
Coppia chiave-valore La rappresentazione della correlazione da una chiave a un
valore.
Ricerca L’operazione nel dizionario che permette di trovare una chiave e il valore
corrispondente.
Cicli annidati Uno o più cicli contenuti “all’interno” di un altro ciclo. Il ciclo
interno viene completato ogni volta che viene eseguito il ciclo esterno.
Valore l’oggetto che appare in un dizionario come seconda parte di una coppia
chiave-valore. Questo è un modo più specifico di utilizzare la parola “valore”.
9.7 Esercizi
Esercizio 2: Scrivi un programma che classifichi ogni messaggio di posta in base
al giorno della settimana in cui è stato inviato. Per fare ciò, cerca le righe che
iniziano con “From”, quindi cerca la terza parola e aggiorna il conteggio di ciascuno
dei giorni della settimana. Alla fine del programma visualizza i contenuti del tuo
dizionario (l’ordine non ha importanza).
Riga di esempio:
Esempio di esecuzione:
python dow.py
Enter a file name: mbox-short.txt
{'Fri': 20, 'Thu': 6, 'Sat': 1}
Esercizio 4: Aggiungi del codice allo script dell’esercizio precedente che indichi
chi ha il maggior numero di messaggi nel file.
Dopo che sono stati analizzati tutti i dati ed i risultati sono salvati nel dizionario
sono stati letti e il dizionario è stato creato, tramite un ciclo “massimo” (vedi
nel capitolo 5 la sezione 5.7.2 per limitare i cicli) trova chi ha più messaggi e
visualizzane il numero.
Esercizio 5: Scrivi uno script che registri il nome di dominio (anziché l’indirizzo)
da cui è stato inviato il messaggio anziché il mittente (ovvero, l’intero indirizzo
email). Alla fine fai in modo che il programma visualizzi i contenuti del dizionario.
python schoolcount.py
Enter a file name: mbox-short.txt
{'media.berkeley.edu': 4, 'uct.ac.za': 6, 'umich.edu': 7,
'gmail.com': 1, 'caret.cam.ac.uk': 1, 'iupui.edu': 8}
Capitolo 10
Tuple
Anche se non necessario è convenzione racchiudere le tuple tra parentesi tonde per
identificarle rapidamente quando esaminiamo uno script di Python:
Per creare una tupla contenente un singolo elemento, scrivere l’elemento tra virgo-
lette seguito da una virgola:
>>> t1 = ('a',)
>>> type(t1)
<type 'tuple'>
>>> t2 = ('a')
>>> type(t2)
<type 'str'>
1 Curiosità: la parola “tupla” deriva dai nomi dati a sequenze di numeri di lunghezza variabile:
117
118 CAPITOLO 10. TUPLE
Un altro modo per costruire una tupla è usare la funzione tuple. In assenza di
argomenti, viene creata una tupla vuota:
>>> t = tuple()
>>> print(t)
()
>>> t = tuple('lupins')
>>> print(t)
('l', 'u', 'p', 'i', 'n', 's')
Dato che tuple è il nome di una funzione di Python, devi evitare di usarlo come
nome di variabile.
La maggior parte degli operatori degli elenchi funziona anche sulle tuple. L’opera-
tore parentesi quadra permette di indicare la posizione di un elemento:
>>> print(t[1:3])
('b', 'c')
Se cerchi però di modificare uno degli elementi della tupla, tutto quello che otterrai
è un messaggio di errore:
Pur non essendo possibile modificare gli elementi di una tupla, puoi sostituirla con
un’altra:
La funzione sort funziona allo stesso modo: di base ordina iniziando dal primo
elemento, ma nel caso di pari lunghezza, inizia dal secondo elemento, e così via.
Questa caratteristica torna utile nel modello chiamato DSU per
decorare (decorate) una sequenza costruendo un elenco di tuple con una o più
chiavi di ordinamento che precedono gli elementi della sequenza,
ordinare (sort) l’elenco di tuple usando il sort incorporato in Python, e
eliminare (undecorate) la decorazione estraendo gli elementi della sequenza,
una volta ordinati.
Ad esempio, supponi di avere un elenco di parole da ordinare dalla più lunga alla
più corta:
t.sort(reverse=True)
res = list()
for length, word in t:
res.append(word)
print(res)
# Code: http://www.py4e.com/code3/soft.py
Il primo ciclo crea un elenco di tuple, ognuna delle quali è una parola preceduta
da un numero che indica la sua lunghezza. sort confronta il primo elemento,
la lunghezza, mentre il secondo elemento viene preso in considerazione solo se
necessario per superare i casi in cui la lunghezza sia la stessa. L’argomento reverse
= True imposta l’esecuzione di sort in ordine decrescente.
Il secondo ciclo scorre l’elenco di tuple e crea un elenco delle parole in ordine
decrescente di lunghezza. Le parole di quattro caratteri sono ordinate in ordine
alfabetico inverso: “what” apparirà prima di “soft” nell’elenco che segue.
120 CAPITOLO 10. TUPLE
Ovviamente i versi perdono molto del loro impatto poetico quando sono trasformati
in una lista di parole ordinate in ordine di lunghezza decrescente.
>>> y
'fun'
>>>
>>> a, b = b, a
Entrambi i lati di questa istruzione sono tuple: sul lato sinistro c’è una tupla di
variabili, nel lato destro c’è una tupla di espressioni. Ogni valore sul lato destro
viene assegnato alla rispettiva variabile sul lato sinistro. Tutte le espressioni sul
lato destro sono valutate prima di qualsiasi assegnazione.
Il numero di variabili a sinistra e il numero di valori a destra devono essere uguali:
>>> a, b = 1, 2, 3
ValueError: too many values to unpack
Più in generale, il lato destro può contenere un qualsiasi tipo di sequenza (stringa,
elenco o tupla). Ad esempio, per suddividere un indirizzo email in nome utente e
dominio, è possibile scrivere:
>>> print(uname)
monty
>>> print(domain)
python.org
Il nuovo elenco viene ordinato secondo un ordine alfabetico crescente del valore
della chiave.
10 a
22 c
1 b
Di nuovo, l’ordine è basato sul valore dell’hash (cioè, nessun ordine particolare).
Se combiniamo queste due tecniche, possiamo visualizzare il contenuto di un dizio-
nario ordinato per il valore memorizzato in ciascuna coppia chiave-valore.
Per fare questo, prima dobbiamo creare un elenco di tuple in cui ogni tupla è
(valore, chiave). Il metodo items ci fornisce un elenco di tuple (chiave,
valore), che questa volta vogliamo ordinare per valore e non per chiave. Una
volta creato l’elenco con le tuple chiave-valore è semplice ordinare l’elenco in ordine
inverso e visualizzare il nuovo elenco.
>>> l.sort(reverse=True)
>>> l
[(22, 'c'), (10, 'a'), (1, 'b')]
>>>
import string
fhand = open('romeo-full.txt')
counts = dict()
for line in fhand:
line = line.translate(str.maketrans('', '', string.punctuation))
line = line.lower()
words = line.split()
for word in words:
if word not in counts:
counts[word] = 1
else:
counts[word] += 1
lst.sort(reverse=True)
# Code: http://www.py4e.com/code3/count3.py
La prima parte del programma, quella che analizza il file e produce il dizionario
che associa ogni parola al numero di volte che viene ripetuta nel testo, è rimasta
invariata. Ora, piuttosto che visualizzare semplicemente i “conteggi” e terminare
il programma, costruiamo un elenco di tuple “(val, key)” che poi ordineremo in
ordine inverso.
Dato che il valore è a sinistra, verrà utilizzato per i confronti. Se è presente più
di una tupla con lo stesso valore, verrà esaminato il secondo elemento (la chiave),
in altre parole le tuple con lo stesso valore verranno ordinate in ordine alfabetico
della chiave.
124 CAPITOLO 10. TUPLE
Alla fine scriviamo un bel ciclo for che esegue un’iterazione di assegnazione
multipla e visualizza le dieci parole più comuni ripetendo una parte dell’elenco
(lst[:10]).
Ora l’output sembra finalmente quello che vorremmo per la nostra analisi della
frequenza delle parole.
61 i
42 and
40 romeo
34 to
34 the
32 thou
32 juliet
30 that
29 my
24 thee
Il fatto che questa complessa analisi dei dati possa essere eseguita con un program-
ma Python di 19 righe di facile comprensione è una delle ragioni per cui Python è
un buon candidato quale linguaggio per l’esplorazione delle informazioni.
directory[last,first] = number
Questo ciclo scorre le chiavi in directory, che in realtà sono tuple. Assegna poi
gli elementi di ciascuna tupla alle variabili last e first, infine visualizza il nome
e il numero di telefono corrispondente.
Poiché le tuple sono immutabili, non sono disponibili metodi come sort e reverse,
che possono modificare elenchi esistenti. Comunque Python ti mette a disposizione
le funzioni integrate sorted e reversed, che accettano qualsiasi sequenza come pa-
rametro e restituiscono una nuova sequenza composta dagli stessi elementi ordinati
diversamente.
10.9 Debug
Elenchi, dizionari e tuple sono conosciuti genericamente come strutture di dati. In
questo capitolo abbiamo iniziato ad esaminare strutture di dati composte, come
elenchi di tuple o dizionari che contengono tuple come chiavi ed elenchi come valori.
Le strutture di dati composti sono utili ma sono soggette a ciò che chiamiamo
errori di formato: errori, cioè, causati dal fatto che una struttura di dati è di
tipo, dimensione o struttura sbagliati. Capita che mentre scrivi codice ti possa
dimenticare del formato dei dati e possa introdurre un errore.
Ad esempio, un programma che si aspetta un elenco contenente un numero intero
e tu gli passi un intero puro e semplice (non incluso in un elenco), ti darà errore.
Quando esegui il debug di un programma, specialmente se stai lavorando su un
bug particolarmente ostico, ci sono quattro cose da provare:
lettura esamina il tuo codice, rileggilo a te stesso e controlla che faccia quello che
volevi facesse.
esecuzione sperimenta apportando modifiche e eseguendo versioni diverse. Spes-
so se indichi la cosa giusta nel posto giusto del programma, il problema diven-
ta ovvio. A volte dovrai passare un po’ di tempo per costruire un’impalcatura
(scaffolding).
126 CAPITOLO 10. TUPLE
riflessione: prenditi un po’ di tempo per pensare di che tipo di errore parliamo:
sintassi, runtime, semantica? Quali informazioni puoi ottenere dai messaggi di
errore o dall’output del programma? Che tipo di errore potrebbe causare il pro-
blema che stai osservando? Cosa hai cambiato per ultimo, prima che apparisse il
problema?
10.10 Glossario
Comparabile Un tipo di dato di cui è possibile controllare il valore per vedere
se è maggiore, minore o uguale a un altro dello stesso tipo. I tipi che sono
comparabili possono essere messi in un elenco e ordinati.
10.11. ESERCIZI 127
10.11 Esercizi
Esercizio 1: Rivedi uno degli script precedenti nel modo seguente: leggi e analizza
le righe contenenti “From” ed estrai gli indirizzi dalla riga. Conta il numero di
messaggi provenienti da ogni persona usando un dizionario. Dopo aver letto tutti i
dati, visualizza la persona con il maggior numero di occorrenze creando un elenco
di tuple (count, email) dal dizionario. Quindi ordina l’elenco in ordine inverso e
visualizza la persona che ha il maggior numero di occorrenze.
Esempio di riga:
From [email protected] Sat 5 Jan 09:14:16 2008
Inserire un nome per il file: mbox-short.txt
[email protected] 5
Esercizio 2: Questo programma conta la distribuzione delle ore del giorno in cui è
stato spedito ciascuno dei messaggi. Puoi estrarre l’ora dalla riga “From” trovando
la stringa dell’orario e quindi suddividendo quella stringa usando il carattere dei
due punti. Dopo aver registrato i conteggi per ogni timestamp, visualizzali, uno
per riga, ordinandoli in base all’ora come mostrato di seguito.
Esempio di esecuzione:
python timeofday.py
Enter a file name: mbox-short.txt
04 3
06 1
07 1
09 2
128 CAPITOLO 10. TUPLE
10 3
11 6
14 1
15 2
16 4
17 2
18 1
19 1
Espressioni regolari
Finora abbiamo letto file, cercato modelli ed estratto porzioni di righe ritenute inte-
ressanti. Abbiamo usato metodi di stringa come split e find, o il frazionamento
di elenchi e stringhe per estrarre parti delle righe.
la funzione di ricerca ed estrazione è talmente popolare che è stata sviluppata per
Python la libreria espressoni regolari che gestisce con grande eleganza molte di
queste attività. La ragione per cui non abbiamo presentato prima le espressioni
regolari nel libro è perché, sebbene siano molto potenti, sono un po’ complicate e
il padroneggiare la loro sintassi richiede del tempo.
Le espressioni regolari sono quasi un piccolo linguaggio di programmazione dedicato
alla ricerca e l’analisi delle stringhe. In effetti sono stati scritti interi libri sul
tema delle espressioni regolari. In questo capitolo, parleremo solo delle basi delle
espressioni regolari. Per maggiori dettagli sulle espressioni regolari, consulta:
http://en.wikipedia.org/wiki/Regular_expression
https://docs.python.org/3.5/library/re.html
La libreria delle espressioni regolari re deve essere importata nel tuo programma
prima che tu possa usarla. L’uso più semplice della libreria di espressioni regolari
è la funzione search(). Questo programma presenta un uso banale della funzione
di ricerca:
# Code: http://www.py4e.com/code3/re01.py
Il file viene aperto e viene letta ogni riga tramite un ciclo in cui viene utilizzata
l’espressione regolare search() per visualizzare solo che contengono la stringa
“From:”. Questo programma non sfrutta il vero potere delle espressioni regolari:
129
130 CAPITOLO 11. ESPRESSIONI REGOLARI
# Code: http://www.py4e.com/code3/re02.py
Ora lavora solo le righe che iniziano con la stringa “From:”. Questo è un esem-
pio molto elementare che avremmo potuto riprodurre utilizzando con il metodo
startswith() incluso nella libreria delle stringhe. Ma serve per introdurre il con-
cetto secondo cui le espressioni regolari possono contenere caratteri speciali che ci
danno più controllo su ciò che confronterà l’espressione regolare.
# Code: http://www.py4e.com/code3/re03.py
11.2. ESTRAZIONE DEI DATI UTILIZZANDO LE ESPRESSIONI REGOLARI131
# Search for lines that start with From and have an at sign
import re
hand = open('mbox-short.txt')
for line in hand:
line = line.rstrip()
if re.search('^From:.+@', line):
print(line)
# Code: http://www.py4e.com/code3/re04.py
La stringa di ricerca “ˆFrom:.+@” restringe l’analisi alle righe che iniziano con
correttamente alle righe che iniziano con “From:”, seguite da uno o più caratteri
(“.+”), dal carattere chiocciola, come nell’esempio seguente:
From:stephen.marquard@ uct.ac.za
Puoi pensare al carattere wildcard “.+” come all’estensione del controllo di tutti i
caratteri inclusi tra i due punti e la chiocciola.
From :.+ @
È bene pensare ai caratteri più e asterisco come “invadenti”. Ad esempio, la stringa
seguente corrisponderebbe all’ultimo carattere chiocciola mentre “.+” andrebbe
oltre, come mostrato di seguito:
From: [email protected], [email protected], e cwen @ iupui.edu
È possibile far sì che l’asterisco o il segno più non siano così “avidi” aggiungendo un
altro carattere. Fai riferimento alla documentazione dettagliata per informazioni
su come disattivare questo avido comportamento.
Return-Path: <[email protected]>
for <[email protected]>;
Received: (from apache@localhost)
Author: [email protected]
Chiaramente non vogliamo scrivere del codice per ciascun caso suddividendo e seg-
mentando in modo diverso ogni riga. Questo programma seguente usa findall()
per trovare le righe contenenti al loro interno indirizzi e-mail e ne estraggano
quest’ultima.
import re
s = 'A message from [email protected] to [email protected] about meeting @2PM'
lst = re.findall('\S+@\S+', s)
print(lst)
# Code: http://www.py4e.com/code3/re05.py
['[email protected]', '[email protected]']
# Code: http://www.py4e.com/code3/re06.py
Viene letta ogni riga e quindi estratte tutte le sottostringhe che corrispondono alla
nostra espressione regolare. Poiché findall() restituisce un elenco, controlliamo
se il numero di elementi nel nostro elenco è maggiore di zero per visualizzare solo
11.2. ESTRAZIONE DEI DATI UTILIZZANDO LE ESPRESSIONI REGOLARI133
le righe in cui abbiamo trovato almeno una sottostringa che somigli ad un indirizzo
email.
Se utilizziamo il programma con mbox.txt otterremo il seguente risultato:
['[email protected]']
['[email protected]']
['<[email protected]>']
['<[email protected]>']
['<[email protected]>;']
['<[email protected]>;']
['<[email protected]>;']
['apache@localhost)']
['[email protected];']
Alcuni degli indirizzi email presentano all’inizio o alla fine caratteri non corretti
come “<” o “;”. Ora dobbiamo indicare che siamo interessati solo alle stringhe che
iniziano e finiscono con una lettera o un numero.
Per fare ciò sfruttiamo un’altra caratteristica delle espressioni regolari: le parentesi
quadre. Queste vengono utilizzate per indicare un insieme di più caratteri accetta-
bili che siamo disposti a considerare corrispondenti. In un certo senso, “‘’S" chiede
di confrontare l’insieme di “caratteri diversi dallo spazio”. Ora saremo un po’ più
espliciti in termini di caratteri che confronteremo.
Ecco la nostra nuova espressione regolare:
[a-zA-Z0-9]\S*@\S*[a-zA-Z]
Sta diventando tutto un po’ complicato e puoi iniziare a capire perché le espressioni
regolari vanno considerate loro stesse un piccolo linguaggio. Per tradurre questa
espressione regolare: cerchiamo sottostringhe che inizino con una singola lettera
minuscola o maiuscola o un numero “[a-zA-Z0-9]”, seguito da zero o più caratteri
non vuoti (“’S’*”), da una chiocciola, da zero o più caratteri non vuoti (“’S*“),
da una lettera maiuscola o minuscola. Nota che siamo passati da”+" a “*” per
indicare zero o più caratteri non vuoti dato che “[a-zA-Z0-9]” è già considerato
un carattere non vuoto. Ricorda che “*” o “+” si applicano al singolo carattere
immediatamente a sinistra del segno stesso.
Se usiamo questa espressione nel nostro programma, i nostri dati appariranno
molto più puliti:
# Code: http://www.py4e.com/code3/re07.py
134 CAPITOLO 11. ESPRESSIONI REGOLARI
...
['[email protected]']
['[email protected]']
['[email protected]']
['[email protected]']
['[email protected]']
['[email protected]']
['[email protected]']
['apache@localhost']
X-DSPAM-Confidence: 0.8475
X-DSPAM-Probability: 0.0000
insomma non vogliamo i numeri in virgola mobile provenienti da una qualsiasi riga;
vogliamo soltanto i numeri contenuti nelle righe che hanno la sintassi sopra indicata.
Possiamo costruire la seguente espressione regolare per selezionare le righe che ci
interessano:
^X-.*: [0-9.]+
In altre parole, stiamo dicendo al pc che stiamo cercando le righe che iniziano con
“X-”, seguite da zero o più caratteri (“.*”), da due punti (“:”) e da uno spazio.
Dopo lo spazio, cerchiamo uno o più caratteri numerici (0-9) o un punto “[0-9.]+”.
Nota che all’interno delle parentesi quadre il punto corrisponde a un punto (in
altre parole non è un carattere wildcard tra parentesi quadre).
Questa è un’espressione molto stringente che corrisponderà quasi solo alle righe
che ci interessano:
# Search for lines that start with 'X' followed by any non
# whitespace characters and ':'
# followed by a space and any number.
# The number can include a decimal.
import re
hand = open('mbox-short.txt')
for line in hand:
11.3. COMBINARE RICERCA ED ESTRAZIONE 135
line = line.rstrip()
if re.search('^X\S*: [0-9.]+', line):
print(line)
# Code: http://www.py4e.com/code3/re10.py
Quando eseguirai il programma, vedrai che verranno visualizzate solo le righe che
stiamo cercando.
X-DSPAM-Confidence: 0.8475
X-DSPAM-Probability: 0.0000
X-DSPAM-Confidence: 0.6178
X-DSPAM-Probability: 0.0000
# Code: http://www.py4e.com/code3/re11.py
['0.8475']
['0.0000']
['0.6178']
['0.0000']
['0.6961']
['0.0000']
..
136 CAPITOLO 11. ESPRESSIONI REGOLARI
Anche se i numeri sono ancora parte di un elenco e devono essere convertiti da strin-
ghe a numeri in virgola mobile, grazie al potere delle espressioni regolari abbiamo
potuto cercare ed estrarre le informazioni che ci interessano.
Voglio farti un altro esempio di questa tecnica: se dai un’occhiata al file, vedrai un
certo numero di righe simili alla seguente:
Details: http://source.sakaiproject.org/viewsvn/?view=rev&rev=39772
Se volessimo estrarre tutti i numeri delle versioni (i numeri interi alla fine della
riga) utilizzando la tecnica precedente, potremmo scrivere lo script seguente:
# Code: http://www.py4e.com/code3/re12.py
['39772']
['39771']
['39770']
['39769']
...
Ricorda che “[0-9] +” è “avido” e tenta di includere quante più cifre nella stringa
prima di estrarla. Questo comportamento “avido” è il motivo per cui otteniamo
tutte e cinque le cifre per ogni numero. La libreria di espressioni regolari si espande
in entrambe le direzioni fino a quando non incontra un carattere non numerico o
l’inizio o la fine di una riga.
Ora puoi utilizzare le espressioni regolari per rifare un esercizio visto in precedenza
in cui eravamo interessati all’ora del giorno di ciascuna mail. Abbiamo cercato
questo tipo di righe:
estratto la quinta parola e diviso nuovamente la riga al carattere due punti per
tirare fuori i due caratteri che ci interessavano. Nonostante abbia funzionato, in
realtà il nostro codice è piuttosto fragile: dato che presuppone che le righe siano
ben formattate. Se dovessimo aggiungere un numero congruo di controlli degli
errori (o un grande blocco try/except) per assicurarci che il programma non si
blocchi quando incontra righe formattate in modo errato, il codice si espanderebbe
di almeno 10-15 righe diventando piuttosto difficile da leggere.
Possiamo farlo in un modo molto più semplice tramite la seguente espressione
regolare:
^From .* [0-9][0-9]:
Con questa espressione regolare stiamo cercando le righe che inizino con “From”
(nota lo spazio), seguito da un numero qualsiasi di caratteri (“.*”), poi da uno
spazio, da due cifre “[0-9][0-9]”, ed infine un carattere di due punti. Questa però è
solo la definizione del tipo di righe che stiamo cercando.
Per estrarre l’ora abbiamo bisogno di utilizzare findall(), e di aggiungere le
parentesi attorno alle due cifre come indicato qui sotto:
^From .* ([0-9][0-9]):
# Code: http://www.py4e.com/code3/re13.py
['09']
['18']
['16']
['15']
...
modo per indicare che questi sono caratteri “normali” e vogliamo confrontarli con
caratteri reali come il segno di dollaro o un accento circonflesso.
Possiamo indicare questa intenzione anteponendo backslash al carattere che ci inte-
ressa. Ad esempio, possiamo individuare gli importi in denaro tramite la seguente
espressione regolare.
import re
x = 'We just received $10.00 for cookies.'
y = re.findall('\$[0-9.]+',x)
Dal momento che facciamo precedere backslash al simbolo del dollaro, questo
verrà interpretato come simbolo del dollaro anziché “fine della riga”. Il resto
dell’espressione regolare corrisponderà a una o più cifre o al carattere del punto.
Nota: All’interno di parentesi quadre, i caratteri non sono considerati “speciali”.
Quindi quando diciamo “[0-9.]”, indichiamo effettivamente numeri o il punto. Al
di fuori delle parentesi quadre, il punto è visto come un carattere “wild card” che
corrisponde a qualsiasi carattere. Te lo ripeto: all’interno delle parentesi quadre il
punto è solo un punto.
11.5 Sommario
Anche se abbiamo solo scalfito la superficie del mondo delle espressioni regolari,
ora hai un po’ imparato la struttura del loro linguaggio. Sono stringhe di ricer-
ca contenenti caratteri speciali che comunicano i tuoi desideri al sistema delle
espressioni regolari definendo cosa è “coincidente” e cosa va estratto dalle stringhe
corrispondenti.
Ecco alcuni di quei caratteri speciali e sequenze di caratteri:
ˆ Corrisponde all’inizio riga.
$ Corrisponde alla fine riga.
. Corrisponde a qualsiasi carattere (un carattere wild-card).
\s corrisponde a uno spazio.
\S corrisponde a un carattere diverso dallo spazio (opposto a \s).
* Si applica al carattere immediatamente precedente e indica zero o più dei ripeti-
zioni dello stesso.
*? Si applica al carattere immediatamente precedente e indica zero o più dei
ripetizioni dello stesso in “modalità non avida”.
+ Si applica al carattere immediatamente precedente e indica uno o più dei caratteri
precedenti.
+? Si applica al carattere immediatamente precedente e indica uno o più dei
caratteri precedenti in “modalità non avida”.
[aeiou] Corrisponde a un singolo carattere fintanto che quel carattere si trova
nell’insieme specificato. In questo esempio, ricercherebbe “a”, “e”, “i”, “o” o “u” e
nessun altro carattere.
11.6. SEZIONE BONUS PER UTENTI UNIX/LINUX 139
Grep mostra le righe che iniziano con la stringa “From:” presenti nel file
mbox-short.txt. Quando farai un po’ di pratica con il comando grep e studierai
la sua documentazione, noterai alcune sottili differenze tra il funzionamento
delle espressioni regolari in Python e in grep. Ad esempio, grep non supporta
il carattere non vuoto “§”, quindi per ottenere lo stesso risultato dovrai usare
una notazione per gli insiemi leggermente più complessa: “[‘ˆ’]” che indica di
individuare un carattere diverso da uno spazio.
11.7 Debug
Python è dotato di una documentazione integrata semplice e rudimentale che può
essere molto utile se hai bisogno di un rapido aggiornamento per rinfrescare la
memoria sul nome di un particolare metodo. Questa documentazione può essere
140 CAPITOLO 11. ESPRESSIONI REGOLARI
>>> help()
help> modules
Se sai già quale modulo vuoi utilizzare, puoi usare il comando dir() per ottenere
un elenco dei metodi disponibili nel modulo come nell’esempio sottostante:
>>> import re
>>> dir(re)
[.. 'compile', 'copy_reg', 'error', 'escape', 'findall',
'finditer' , 'match', 'purge', 'search', 'split', 'sre_compile',
'sre_parse' , 'sub', 'subn', 'sys', 'template']
11.8 Glossario
Codice fragile Codice che funziona quando i dati di input sono in un formato
particolare ma è soggetto a malfunzionamenti se c’è qualche variazione ri-
spetto al formato corretto. Lo chiamiamo “codice fragile” perché si “rompe”
facilmente.
Corrispondenza avida La nozione per indicare che in un’espressione regolare i
caratteri “+” e “*” si espandono verso l’esterno per corrispondere alla stringa
più grande possibile.
Grep Comando disponibile nella maggior parte dei sistemi Unix che permette la
ricerca nei file di testo di righe che soddisfino le espressioni regolari imposta-
te dall’utente. Il nome del comando è l’acronimo di “Generalized Regular
Expression Parser”.
Espressione regolare Linguaggio per impostare ricerche complesse di stringhe.
Un’espressione regolare può contenere caratteri speciali che indicano parame-
tri come il focalizzare una ricerca solo all’inizio o alla fine di una riga e/o
molte altre funzionalità simili.
Wild-card Un carattere speciale che indica qualsiasi carattere. Nelle espressioni
regolari il carattere wild-card è il punto (.).
11.9. ESERCIZI 141
11.9 Esercizi
Esercizio 1: Scrivi un semplice programma che simuli il comportamento del co-
mando grep di Unix. Fai che richieda all’utente l’inserimento di un’espressione
regolare e poi ritorni il numero di righe che corrispondono alle specifiche della
ricerca.
$ python grep.py
Enter a regular expression: ^Author
mbox.txt had 1798 lines that matched ^Author
$ python grep.py
Enter a regular expression: ^X-
mbox.txt had 14368 lines that matched ^X-
$ python grep.py
Enter a regular expression: java$
mbox.txt had 4218 lines that matched java$
Enter file:mbox.txt
38444.0323119
Enter file:mbox-short.txt
39756.9259259
142 CAPITOLO 11. ESPRESSIONI REGOLARI
Capitolo 12
Molti degli esempi in questo libro si sono concentrati sulla lettura di file e sulla
ricerca di dati al loro interno, ma in rete abbiamo molte altre fonti di informazione
da cui attingere. In questo capitolo faremo finta di essere un browser Web e
recupereremo le pagine Web utilizzando l’HyperText Transfer Protocol (HTTP).
Quindi leggeremo ed analizzeremo i dati presenti in esse contenuti.
143
144 CAPITOLO 12. PROGRAMMI PER INTERNET
Avrai modo di vedere che è un documento lungo e complesso di 176 pagine conte-
nente molti dettagli. Se lo trovi interessante, sentiti libero di leggerlo tutto. Per
ora, dai un’occhiata a pagina 36 di RFC2616 dove troverai la sintassi per la richie-
sta GET. Proviamo a richiedere un documento da un server web: colleghiamoci al
server www.pr4e.org sulla porta 80 e inviamo la seguente richiesta
GET http://data.pr4e.org/romeo.txt HTTP / 1.0
il cui secondo parametro indica la pagina web che stiamo richiedendo, inviamo
poi anche una riga vuota. Il server web risponderà con alcune informazioni di
intestazione riguardanti il documento, una riga vuota e il contenuto del documento
richiesto.
import socket
while True:
data = mysock.recv(512)
if (len(data) < 1):
break
print(data.decode(),end='')
mysock.close()
# Code: http://www.py4e.com/code3/socket1.py
Per prima cosa il programma effettua una connessione alla porta 80 del server
www.py4e.com. Dato che il nostro programma sta impersonando un “browser
web”, il protocollo HTTP prevede che il comando GET sia seguito da una riga
vuota.
Una volta inviata la riga vuota, scriviamo un ciclo che riceva dal socket dati in
blocchi da 512 caratteri e li visualizzi fino a quando non ci sono più dati da leggere
(cioè quando recv() restituisce una stringa vuota).
Il programma produrrà l’output seguente:
HTTP/1.1 200 OK
Date: Sun, 14 Mar 2010 23:52:41 GMT
Server: Apache
12.3. RECUPERO DI UN’IMMAGINE TRAMITE HTTP 145
Il tuo
programma
www.py4e.com
socket ,
- Pagine web
connect C
Porta 80 .
/
send .
0
recv 1
.
L’output inizia con l’invio dell’intestazione dal server Web che descrive il docu-
mento. Ad esempio, l’intestazione Content-Type indica che il documento è un
documento di testo (text/plain).
Dopo che il server ci ha fornito l’intestazione, viene mandata una riga vuota che
indica la fine delle intestazioni e viene iniziato l’invio del file romeo.txt.
Questo esempio è servito per mostrarti come realizzare una connessione di rete di
basso livello con un socket. I socket possono essere utilizzati per comunicare con
un server Web, con un server di posta o con molti altri tipi di server. Tutto ciò
che serve è trovare il documento che descrive il protocollo da utilizzare e scrivere
il codice per inviare e ricevere i dati rispettando quanto indicato.
Dal momento che il protocollo più comunemente utilizzato è l’HTTP, Python
è dotato di una libreria appositamente progettata per supportare il recupero di
documenti e dati dal web.
import socket
import time
HOST = 'data.pr4e.org'
PORT = 80
mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect((HOST, PORT))
mysock.sendall(b'GET http://data.pr4e.org/cover3.jpg HTTP/1.0\r\n\r\n')
count = 0
picture = b""
while True:
data = mysock.recv(5120)
if (len(data) < 1): break
time.sleep(0.25)
count = count + len(data)
print(len(data), count)
picture = picture + data
mysock.close()
# Code: http://www.py4e.com/code3/urljpeg.py
$ python urljpeg.py
2920 2920
1460 4380
1460 5840
1460 7300
...
1460 62780
1460 64240
2920 67160
1460 68620
1681 70301
Header length 240
12.3. RECUPERO DI UN’IMMAGINE TRAMITE HTTP 147
HTTP/1.1 200 OK
Date: Sat, 02 Nov 2013 02:15:07 GMT
Server: Apache
Last-Modified: Sat, 02 Nov 2013 02:01:26 GMT
ETag: "19c141-111a9-4ea280f8354b8"
Accept-Ranges: bytes
Content-Length: 70057
Connection: close
Content-Type: image/jpeg
Noterai che l’intestazione di questo url, Content-Type, indica che il corpo del
documento è un’immagine (image/jpeg). Al termine del programma, è possi-
bile visualizzare l’immagine aprendo il file stuff.jpg con un visualizzatore di
immagini.
Mentre il programma è in esecuzione potrai notare che non riceviamo solo 5120
caratteri ad ogni chiamata del metodo recv(): in realtà riceviamo il numero di
caratteri trasferiti attraverso la rete dal server Web dalla chiamata di recv(). In
questo esempio, abbiamo ottenuto 1460 o 2920 caratteri ogni volta che abbiamo
richiesto fino a 5120 caratteri.
I risultati potrebbero variare a seconda della velocità della tua connessione. Ti
prego di notare inoltre che nell’ultima chiamata a recv() otteniamo 1681 byte, la
fine del flusso di dati, e nella successiva chiamata a recv() riceviamo una stringa
di lunghezza zero che ci avvisa che il server ha chiamato close() al socket e non
vi sono più dati in arrivo.
Possiamo rallentare le nostre successive chiamate recv() rimuovendo il segno di
commento alla chiamata time.sleep(). In questo modo, possiamo ritardare di un
quarto di secondo ogni chiamata per permettere al server di “anticiparci” e inviarci
più dati prima di richiamare nuovamente recv(). Il programma ora si comporterà
nel modo seguente:
$ python urljpeg.py
1460 1460
5120 6580
5120 11700
...
5120 62900
5120 68020
2281 70301
Header length 240
HTTP/1.1 200 OK
Date: Sat, 02 Nov 2013 02:22:04 GMT
Server: Apache
Last-Modified: Sat, 02 Nov 2013 02:01:26 GMT
ETag: "19c141-111a9-4ea280f8354b8"
Accept-Ranges: bytes
Content-Length: 70057
Connection: close
Content-Type: image/jpeg
Ora, tranne che nella prima e l’ultima chiamata a recv(), abbiamo ricevuto 5120
caratteri ad ogni richiesta di nuovi dati.
148 CAPITOLO 12. PROGRAMMI PER INTERNET
Tra le richieste send() inviate dal server e le richieste recv() provenienti dalla
nostra applicazione c’è un buffer. Se il programma è con il delay attivo, il server
potrebbe riempire il buffer del socket ed essere costretto a sospendere l’invio di
ulteriori dati fino a quando il nostro script inizia a svuotare il buffer. La sospensione
di invio o ricezione di dati viene chiamato “controllo di flusso”.
import urllib.request
fhand = urllib.request.urlopen('http://data.pr4e.org/romeo.txt')
for line in fhand:
print(line.decode().strip())
# Code: http://www.py4e.com/code3/urllib1.py
Una volta che la pagina web è stata aperta con urllib.urlopen, possiamo trat-
tarla come un file e leggerne il contenuto usando un ciclo for.
Quando il programma è in esecuzione, vediamo solo il contenuto del file. Le in-
testazioni che sono state inviate vengono rimosse dal urllib e vengono restituiti
solo i dati.
fhand = urllib.request.urlopen('http://data.pr4e.org/romeo.txt')
counts = dict()
for line in fhand:
words = line.decode().split()
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts)
# Code: http://www.py4e.com/code3/urlwords.py
12.5. ANALISI DELL’HTML E RACCOLTA DATI DAL WEB 149
Ancora una volta tengo a ripeterti che dopo aver avuto accesso alla pagina Web,
possiamo leggerla come se si trattasse di un file locale.
href="http://.+?"
# Search for lines that start with From and have an at sign
import urllib.request, urllib.parse, urllib.error
import re
# Code: http://www.py4e.com/code3/urlregex.py
python urlregex.py
Enter - http://www.dr-chuck.com/page1.htm
http://www.dr-chuck.com/page2.htm
python urlregex.py
Enter - http://www.py4e.com/book.htm
http://www.greenteapress.com/thinkpython/thinkpython.html
http://allendowney.com/
http://www.py4e.com/code
http://www.lib.umich.edu/espresso-book-machine
http://www.py4e.com/py4inf-slides.zip
Le espressioni regolari funzionano molto bene quando il codice HTML è ben for-
mattato e prevedibile. Ma dal momento che ci sono molte pagine HTML “danneg-
giate”, una soluzione che utilizzi solo espressioni regolari potrebbe non riportare
alcuni collegamenti validi o fornire dati non validi.
Questo problema può essere risolto utilizzando una robusta libreria per analisi
HTML.
# Code: http://www.py4e.com/code3/urllinks.py
python urllinks.py
Enter - http://www.dr-chuck.com/page1.htm
http://www.dr-chuck.com/page2.htm
1 Puoi utilizzare il comando “pip install beautifulsoup”.
2 Il formato XML verrà affrontato nel prossimo capitolo.
152 CAPITOLO 12. PROGRAMMI PER INTERNET
python urllinks.py
Enter - http://www.py4e.com/book.htm
http://www.greenteapress.com/thinkpython/thinkpython.html
http://allendowney.com/
http://www.si502.com/
http://www.lib.umich.edu/espresso-book-machine
http://www.py4e.com/code
http://www.py4e.com/
Puoi utilizzare BeautifulSoup per estrarre varie parti di ciascun tag come indicato
nelle pagine seguenti:
# Code: http://www.py4e.com/code3/urllink2.py
python urllink2.py
Enter - http://www.dr-chuck.com/page1.htm
TAG: <a href="http://www.dr-chuck.com/page2.htm">
12.8. LEGGERE FILE BINARI TRAMITE URLLIB 153
Second Page</a>
URL: http://www.dr-chuck.com/page2.htm
Content: ['\nSecond Page']
Attrs: [('href', 'http://www.dr-chuck.com/page2.htm')]
img = urllib.request.urlopen('http://data.pr4e.org/cover3.jpg').read()
fhand = open('cover3.jpg', 'wb')
fhand.write(img)
fhand.close()
# Code: http://www.py4e.com/code3/curl1.py
Questo programma legge in una sola volta tutti i dati in remoto, li memorizza
temporaneamente nella ram del computer nella variabile img, quindi apre il file
cover.jpg e salva i dati su disco. Questa operazione funzionerà se la dimensione
del file è inferiore a quella della memoria del tuo computer.
Quindi, se l’obiettivo è un file audio o video di grandi dimensioni, questo script
potrebbe crashare o rallentare sensibilmente qualora il computer esaurisca la me-
moria. Per evitare di saturare la memoria, scarichiamo i dati in singoli blocchi (o
buffer), scriviamo su disco prima di ottenere il successivo. In questo modo il pro-
gramma può leggere file di qualsiasi dimensione senza utilizzare tutta la memoria
ram di cui disponi nel computer.
img = urllib.request.urlopen('http://data.pr4e.org/cover3.jpg')
fhand = open('cover3.jpg', 'wb')
size = 0
while True:
info = img.read(100000)
if len(info) < 1: break
size = size + len(info)
fhand.write(info)
154 CAPITOLO 12. PROGRAMMI PER INTERNET
# Code: http://www.py4e.com/code3/curl2.py
In questo esempio stiamo leggendo solo 100.000 caratteri alla volta che poi scri-
viamo nel file cover.jpg prima di recuperare i successivi 100.000 caratteri dal
web.
python curl2.py
568248 characters copied.
curl -O http://www.py4e.com/cover.jpg
12.9 Glossario
12.10 Esercizi
Esercizio 1: Modifica il programma socket socket1.py in modo da richiedere
all’utente l’URL rendendolo quindi in grado di leggere qualsiasi pagina web. Puoi
usare split('/') per suddividere l’URL nelle sue componenti in modo da poter
estrarre il nome host per la chiamata connect del socket. Aggiungi il controllo
degli errori usando try ed except per gestire la condizione in cui l’utente inserisca
un URL non formattato correttamente o sia inesistente.
Esercizio 2: Modifica il tuo programma socket in modo che conti il numero
di caratteri che ha ricevuto e interrompa la visualizzazione di qualsiasi testo dopo
che ne ha mostrato 3000. Il programma dovrà inoltre accettare l’intero documento,
contare il numero totale di caratteri e visualizzarne il numero.
Esercizio 3: Utilizza urllib per replicare l’esercizio precedente per (1) recuperare
il documento da un URL, (2) visualizzare i primi 3000 caratteri e (3) contarne
il numero complessivo. Non preoccuparti delle intestazioni per questo esercizio,
per ora è sufficiente mostrare semplicemente i primi 3000 caratteri contenuti nel
documento.
Esercizio 4: Modifica il programma urllinks.py per estrarre e contare i tag
di paragrafo (p) dal documento HTML scaricato e visualizza il conteggio dei pa-
ragrafi come output del programma. Non visualizzare il testo del paragrafo: è
sufficiente contarli. Metti alla prova il programma con diverse pagine Web e di
varie dimensioni.
Esercizio 5: (Avanzato) Modifica il programma socket in modo che mostri i dati
solo dopo che siano state ricevute le intestazioni e una riga vuota. Ricorda che
recv sta ricevendo caratteri (inclusi caratteri newline e tutti gli altri) e non righe.
156 CAPITOLO 12. PROGRAMMI PER INTERNET
Capitolo 13
Una volta che fu più semplice scambiare i documenti via HTTP e analizzarli utiliz-
zando appositi script, non passò molto tempo prima che venissero sviluppati e pro-
dotti documenti realizzati specificamente per essere utilizzati da altri programmi
(ovvero pagine HTML destinate ad essere visualizzate in un browser).
Esistono due formati principali che utilizziamo per scambiare dati sul Web:
<person>
<name>Chuck</name>
<phone type="intl">
+1 734 303 4456
</phone>
<email hide="yes"/>
</person>
157
158 CAPITOLO 13. UTILIZZO DI SERVIZI WEB
person
+1 734
Chuck
303 4456
import xml.etree.ElementTree as ET
data = '''
<person>
<name>Chuck</name>
<phone type="intl">
+1 734 303 4456
</phone>
<email hide="yes"/>
</person>'''
tree = ET.fromstring(data)
print('Name:', tree.find('name').text)
print('Attr:', tree.find('email').get('hide'))
# Code: http://www.py4e.com/code3/xml1.py
Name: Chuck
Attr: yes
È possibile utilizzare un parser XML come ElementTree anche con file XML molto
più complessi di quello utilizzato in questo esempio, avendo a disposizione molte
regole per l’interpretazione del codice XML e l’estrazione di dati senza preoccuparci
troppo delle regole della sintassi.
13.3. CICLI CON I NODI DEGLI XML 159
import xml.etree.ElementTree as ET
input = '''
<stuff>
<users>
<user x="2">
<id>001</id>
<name>Chuck</name>
</user>
<user x="7">
<id>009</id>
<name>Brent</name>
</user>
</users>
</stuff>'''
stuff = ET.fromstring(input)
lst = stuff.findall('users/user')
print('User count:', len(lst))
# Code: http://www.py4e.com/code3/xml2.py
User count: 2
Name Chuck
Id 001
Attribute 2
Name Brent
Id 009
Attribute 7
{
"name" : "Chuck",
"phone" : {
"type" : "intl",
"number" : "+1 734 303 4456"
},
"email" : {
"hide" : "yes"
}
}
Avrai notato alcune differenze: per prima cosa, nel formato XML possiamo ag-
giungere attributi come “intl” al tag “phone”, mentre con il formato JSON ciò
non è possibile: abbiamo solo coppie chiave-valore. Anche il tag XML “person” è
scomparso, sostituito da una serie di parentesi graffe.
In generale le strutture JSON sono più semplici perché JSON dispone di meno
funzionalità rispetto al linguaggio XML. Il formato JSON ha però il vantaggio
di mappare direttamente alcune combinazioni di dizionari ed elenchi di Python.
Poiché quasi tutti i linguaggi di programmazione hanno qualcosa di equivalente ai
dizionari e agli elenchi di Python, JSON è un formato ideale con cui far scambiare
dati a due programmi.
Esso sta rapidamente diventando il formato scelto per quasi tutti gli scambi di dati
tra applicazioni grazie alla sua relativa semplicità rispetto al formato XML.
import json
data = '''
13.5. ANALIZZARE CODICE JSON 161
[
{ "id" : "001",
"x" : "2",
"name" : "Chuck"
} ,
{ "id" : "009",
"x" : "7",
"name" : "Chuck"
}
]'''
info = json.loads(data)
print('User count:', len(info))
# Code: http://www.py4e.com/code3/json2.py
Se confronti il codice per estrarre i dati dal JSON e dall’XML, vedrai che ciò che
otteniamo da json.loads() è un elenco Python, che possiamo scorrere con un ciclo
for, in cui ogni elemento è un dizionario Python. Una volta analizzato il JSON,
possiamo usare l’operatore indice per estrarre i vari dati di ciascun utente. Non
abbiamo bisogno di utilizzare la libreria JSON per effettuare una ricerca all’interno
del JSON analizzato, dal momento che i dati restituiti sono strutture native di
Python.
User count: 2
Name Chuck
Id 001
Attribute 2
Name Brent
Id 009
Attribute 7
In generale per i servizi web c’è una tendenza ad abbandonare il formato XML
in favore del formato JSON; dato che il formato JSON è più semplice e più di-
rettamente associabile alle strutture di dati native che troviamo già presenti nei
linguaggi di programmazione, l’analisi e l’estrazione dei dati sono in genere più
semplici e dirette. D’altro canto l’XML è più auto-descrittivo e per questo ci sono
alcune applicazioni in cui XML risulta essere la scelta migliore. Ad esempio, la
maggior parte dei word processor memorizza internamente i documenti utilizzando
il formato XML piuttosto che il JSON.
162 CAPITOLO 13. UTILIZZO DI SERVIZI WEB
Puoi vedere molti esempi di SOA quando navighi in rete: da un unico sito web
puoi prenotare viaggi aerei, hotel e automobili. Naturalmente i dati per gli hotel
non sono memorizzati nei computer delle compagnie aeree: piuttosto i server delle
compagnie aeree contattano i servizi sui computer dell’hotel e recuperano i dati che
poi presentano all’utente. Quando l’utente accetta di effettuare una prenotazione
alberghiera utilizzando il sito della compagnia aerea, il sito della compagnia aerea
utilizza un altro servizio Web dei sistemi dell’hotel per effettuare concretamente la
prenotazione. Quando arriva il momento di addebitare il costo sulla tua carta di
credito per l’intera transazione vengono coinvolti anche altri computer.
API
API API
Applicazione
di viaggi
Un’architettura orientata ai servizi (SOA) presenta molti vantaggi tra cui: (1)
mantiene sempre una sola copia di dati (questo è particolarmente importante in
casi come prenotazioni di hotel in cui non vogliamo sovrapporre azioni) e (2) i
proprietari dei dati possono gestire le regole sull’uso dei propri dati. Con questi
vantaggi, un sistema SOA deve essere progettato oculatamente per avere buone
prestazioni e soddisfare le esigenze dell’utente.
I servizi messi a disposizione in rete dalle applicazioni tramite le API vengono
chiamati servizi web.
while True:
address = input('Enter location: ')
if len(address) < 1: break
{'address': address})
print('Retrieving', url)
uh = urllib.request.urlopen(url)
data = uh.read().decode()
print('Retrieved', len(data), 'characters')
try:
js = json.loads(data)
except:
js = None
print(json.dumps(js, indent=4))
lat = js["results"][0]["geometry"]["location"]["lat"]
lng = js["results"][0]["geometry"]["location"]["lng"]
print('lat', lat, 'lng', lng)
location = js['results'][0]['formatted_address']
print(location)
# Code: http://www.py4e.com/code3/geojson.py
$ python3 geojson.py
Enter location: Ann Arbor, MI
Retrieving http://maps.googleapis.com/maps/api/
geocode/json?address=Ann+Arbor%2C+MI
Retrieved 1669 characters
{
"status": "OK",
"results": [
{
"geometry": {
13.8. SICUREZZA E UTILIZZO DELLE API 165
"location_type": "APPROXIMATE",
"location": {
"lat": 42.2808256,
"lng": -83.7430378
}
},
"address_components": [
{
"long_name": "Ann Arbor",
"types": [
"locality",
"political"
],
"short_name": "Ann Arbor"
}
],
"formatted_address": "Ann Arbor, MI, USA",
"types": [
"locality",
"political"
]
}
]
}
lat 42.2808256 lng -83.7430378
Ann Arbor, MI, USA
Enter location:
# https://apps.twitter.com/
# Create new App and get the four strings
def oauth():
return {"consumer_key": "h7Lu...Ng",
"consumer_secret" : "dNKenAC3New...mmn7Q",
"token_key" : "10185562-eibxCp9n2...P4GEQQOSGI",
"token_secret" : "H0ycCFemmC4wyf1...qoIpBo"}
# Code: http://www.py4e.com/code3/hidden.py
https://api.twitter.com/1.1/statuses/user_timeline.json?count=2
&oauth_version=1.0&oauth_token=101...SGI&screen_name=drchuck
&oauth_nonce=09239679&oauth_timestamp=1380395644
&oauth_signature=rLK...BoD&oauth_consumer_key=h7Lu...GNg
&oauth_signature_method=HMAC-SHA1
Puoi leggere le specifiche OAuth se vuoi ottenere maggiori informazioni dei vari
parametri che vengono aggiunti per soddisfare i requisiti di sicurezza.
Per i programmi destinati a lavorare con Twitter, abbiamo nascosto tutta la parte
più complessa nei file oauth.py e twurl.py. È sufficiente inserire i segreti in hidden.py
e inviare l’URL desiderato alla funzione twurl.augment(), il codice della libreria
aggiungere tutti gli altri parametri all’URL per conto nostro.
Questo programma recupera la cronologia di un particolare utente Twitter e ci resti-
tuisce i dati richiesti in una stringa in formato JSON. Visualizziamo semplicemente
i primi 250 caratteri della stringa:
# https://apps.twitter.com/
# Create App and get the four strings, put them in hidden.py
TWITTER_URL = 'https://api.twitter.com/1.1/statuses/user_timeline.json'
while True:
print('')
acct = input('Enter Twitter Account:')
if (len(acct) < 1): break
url = twurl.augment(TWITTER_URL,
{'screen_name': acct, 'count': '2'})
print('Retrieving', url)
connection = urllib.request.urlopen(url, context=ctx)
data = connection.read().decode()
print(data[:250])
headers = dict(connection.getheaders())
# print headers
print('Remaining', headers['x-rate-limit-remaining'])
# Code: http://www.py4e.com/code3/twitter1.py
Insieme ai dati della cronologia, Twitter restituisce anche i metadati relativi alla
richiesta nelle intestazioni delle risposte HTTP. In particolare l’intestazione x-rate-
limit-remaining ci informa su quante altre richieste possiamo fare prima di venire
168 CAPITOLO 13. UTILIZZO DI SERVIZI WEB
bloccati per un breve periodo di tempo. Potrai notare che le richieste rimanenti
diminuiscono ogni volta di uno.
Nell’esempio seguente vogliamo recuperare gli amici di Twitter di un utente, ana-
lizzare il JSON che riceveremo per estrarre alcune informazioni sugli amici. Inoltre
eseguiremo il dump del JSON dopo averlo analizzato e “stampato” con un rientro
di quattro caratteri per consentirci di analizzare i dati nel caso volessimo consultare
più campi.
# https://apps.twitter.com/
# Create App and get the four strings, put them in hidden.py
TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'
while True:
print('')
acct = input('Enter Twitter Account:')
if (len(acct) < 1): break
url = twurl.augment(TWITTER_URL,
{'screen_name': acct, 'count': '5'})
print('Retrieving', url)
connection = urllib.request.urlopen(url, context=ctx)
data = connection.read().decode()
js = json.loads(data)
print(json.dumps(js, indent=2))
headers = dict(connection.getheaders())
print('Remaining', headers['x-rate-limit-remaining'])
for u in js['users']:
print(u['screen_name'])
if 'status' not in u:
print(' * No status found')
continue
s = u['status']['text']
print(' ', s[:50])
# Code: http://www.py4e.com/code3/twitter2.py
Dato che il JSON viene trasformato in una serie di elenchi e dizionari annidati,
13.8. SICUREZZA E UTILIZZO DELLE API 169
possiamo usare una combinazione di operazioni su indici e cicli for per scorrere le
strutture dati scrivendo pochissimo codice Python.
L’output del programma sarà simile al seguente (alcuni elementi dei dati sono stati
abbreviati per adattarsi alle dimensioni della pagina):
{
"next_cursor": 1444171224491980205,
"users": [
{
"id": 662433,
"followers_count": 28725,
"status": {
"text": "@jazzychad I just bought one .__.",
"created_at": "Fri Sep 20 08:36:34 +0000 2013",
"retweeted": false,
},
"location": "San Francisco, California",
"screen_name": "leahculver",
"name": "Leah Culver",
},
{
"id": 40426722,
"followers_count": 2635,
"status": {
"text": "RT @WSJ: Big employers like Google ...",
"created_at": "Sat Sep 28 19:36:37 +0000 2013",
},
"location": "Victoria Canada",
"screen_name": "_valeriei",
"name": "Valerie Irvine",
],
"next_cursor_str": "1444171224491980205"
}
leahculver
@jazzychad I just bought one .__.
_valeriei
RT @WSJ: Big employers like Google, AT&T are h
ericbollens
RT @lukew: sneak peek: my LONG take on the good &a
halherzog
Learning Objects is 10. We had a cake with the LO,
scweeker
@DeviceLabDC love it! Now where so I get that "etc
Nella parte finale dell’output é contenuto il ciclo for che legge i cinque “amici”
più recenti dell’account Twitter drchuck e ne visualizza l’ultimo stato anche se nel
JSON che ti viene restituito sono contenuti molti più dati.
Se osservi l’output del programma noterai che il “trova gli amici” di un determinato
account ha una diversa velocità in base al numero di query sulla cronologia che ti
è possibile eseguire per ogni periodo di tempo.
Queste chiavi API sicure consentono a Twitter di conoscere con certezza chi sta
usando le sue API e i suoi dati. Le limitazioni della velocità ci consentono di
eseguire semplici query dei dati personali, impedendoci di creare un prodotto che
raccolga massivamente dati interrogando le API milioni di volte al giorno.
13.9 Glossario
API Application Program Interface - Una sorta di contratto che definisce le
modalità di interazione tra le componenti di una o più applicazioni.
ElementTree Una libreria di Python utilizzata per analizzare i dati XML.
JSON JavaScript Object Notation. Un formato che consente il markup di dati
strutturati sulla base della sintassi degli oggetti JavaScript.
SOA Service-Oriented Architecture. Indica un’applicazione composta da compo-
nenti connessi attraverso una rete.
XML eXtensible Markup Language. Un metalinguaggio per la definizione del
markup di dati strutturati.
13.10 Esercizi
Esercizio 1: Modifica uno degli script www.py4e.com/code3/geojson.py o
www.py4e.com/code3/ geoxml.py in modo da visualizzare i die caratteri del
codice paese inseriti dai dati recuperati. Aggiungi il controllo degli errori in modo
che il tuo script non vada in errore se il codice del paese non è presente. Una
volta completato con successo questo compito, fagli cercare “Atlantic Ocean” e
assicurati che possa gestire luoghi che non si trovano in nessun paese.
Capitolo 14
Programmazione ad oggetti
• Codice sequenziale
• Codice condizionale (istruzioni if)
• Codice ripetitivo (cicli)
• Memorizza e riutilizza (funzioni).
Negli capitoli precedenti abbiamo esplorato l’uso delle variabili semplici e delle
strutture per la gestione dei dati come elenchi, tuple e dizionari.
Mano a mano che i programmi raggiungono i milioni di righe diventa sempre più
importante scrivere codice che sia facile da interpretare. Se stai lavorando su un
programma di milioni di righe, non potrai mai tenere in mente allo stesso tempo
l’intero programma. Abbiamo bisogno di trovare modi per spezzare il programma
in più pezzi in modo che risolvere problemi, correggere un bug o aggiungere una
nuova caratteristica sia il più semplice possibile.
171
172 CAPITOLO 14. PROGRAMMAZIONE AD OGGETTI
stuff = list()
stuff.append('python')
stuff.append('chuck')
stuff.sort()
print (stuff[0])
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))
# Code: http://www.py4e.com/code3/party1.py
Piuttosto che concentrarci su ciò che potresti ottenere tramite queste poche righe di
codice, vediamo cosa sta realmente accadendo dal punto di vista della programma-
zione ad oggetti. Non preoccuparti se la prima volta che leggi i prossimi paragrafi
ti sembra che non abbiamo molto senso dato che non ti ho ancora spiegato il
significato di molti dei termini presenti.
La prima riga sta costruendo un oggetto di tipo lista, la seconda e la terza linea
chiamano il metodo append(), la quarta riga chiama il metodo sort() e la quinta
riga sta ottenendo l’elemento in posizione 0.
La sesta riga chiama il metodo __getitem __() nell’elenco stuff con parametro
zero.
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))
# Code: http://www.py4e.com/code3/elev.py
Program
Input Output
All’interno del programma hanno luogo alcune interazioni ben definite con il mondo
“esterno” che generalmente non sono qualcosa su cui ci focalizziamo. Quando
scriviamo del codice ci preoccupiamo solo dei dettagli “all’interno del programma”.
Socket html.parser
Object Object
Un modo per pensare alla programmazione orientata agli oggetti è il voler cercare
di separare il nostro programma in più “zone”. Ogni “zona” é composta da codice
e dati (come se fosse un programma a se stante) e ha interazioni ben definite con
il mondo esterno e con le altre zone all’interno del programma. Se riprendiamo
in considerazione lo script di estrazione dei collegamenti in cui abbiamo usato la
libreria BeautifulSoup possiamo vedere un programma costituito da più oggetti
che interagiscono tra loro per svolgere un compito:
# Code: http://www.py4e.com/code3/urllinks.py
L’URL viene richiesto come stringa, poi passato in urllib per il recupero dei dati
da web. La libreria urllib effettua la connessione di rete appoggiandosi alla libre-
ria socket. La stringa scaricata da urllib viene consegnata a BeautifulSoup per
l’analisi. BeautifulSoup utilizza l’oggetto html.parser1 per restituire un oggetto a
cui viene applicato il metodo tags() per generare un dizionario di oggetti tag, che
vengono passati in rassegna tramite il metodo get() per visualizzarne l’eventuale
attributo ‘href’.
Possiamo rappressentare con la seguente immagine l’interazione degli oggetti di
questo programma appena descritta.
In questo momento la cosa più importante non è comprendere appieno il funzio-
namento di questo programma, quanto piuttosto vedere com’é stato strutturato
questo insieme di oggetti e di come ne viene orchestrato lo scambio di informazioni.
È anche importante notare che quando studiavi il funzionamento di uno script
presente nei primi capitoli di questo libro, eri in grado di capire appieno cosa stava
succedendo senza nemmeno renderti conto che il programma stava “gestendo il
movimento dei dati tra gli oggetti presenti”. Allora per te erano solo righe di
codice che portavano a termine il lavoro.
Socket html.parser
Object Object
Socket html.parser
Object Object
class PartyAnimal:
x = 0
def party(self) :
self.x = self.x + 1
print("So far",self.x)
an = PartyAnimal()
an.party()
an.party()
an.party()
14.6. IL NOSTRO PRIMO OGGETTO PYTHON 177
PartyAnimal.party(an)
# Code: http://www.py4e.com/code3/party2.py
Ogni metodo che ha l’aspetto di una funzione, inizia con la parola chiave def ed è
costituito da un blocco di codice indentato. L’esempio precedente ha un attributo
(x) e un metodo (party). I metodi hanno uno primo parametro speciale chiamato
per convenzione self.
Proprio come la parola chiave def non provoca l’esecuzione del codice della funzio-
ne, la parola chiave class non crea un oggetto. Piuttosto la parola chiave class
definisce un modello che indica quali dati e codice saranno contenuti in ogni oggetto
di tipo PartyAnimal. Potresti pensare che la classe sia uno stampino per biscotti e
che gli oggetti creati utilizzando la classe siano i biscotti2 . Nel preparare i biscotti,
sai che la glassa non va messa sullo stampino quanto piuttosto sui biscotti e hai
sempre la possibilità di mettere una glassa diversa su ciascun biscotto.
an = PartyAnimal()
questo è il punto in cui indichiamo a Python dove costruire (ad es. creare) un
oggetto o “l’istanza della classe denominata PartyAnimal”. Sembra una chiamata
di funzione alla classe stessa e Python costruisce l’oggetto con i dati e i metodi
corretti e restituisce l’oggetto che viene quindi assegnato alla variabile an. In
un certo senso tutto questo è abbastanza simile alla riga che abbiamo usato fin
dall’inizio:
counts = dict()
Qui stiamo dicendo a Python di creare un oggetto usando il template dict (già
presente nel linguaggio), restituire l’istanza del dizionario e assegnarla alla variabile
counts.
2 Cookie immagine copyright CC-BY https://www.flickr.com/photos/dinnerseries/23570475099
178 CAPITOLO 14. PROGRAMMAZIONE AD OGGETTI
an.party()
Quando viene chiamato il metodo party, il primo parametro (chiamato per con-
venzione self) punta alla particolare istanza dell’oggetto PartyAnimal che viene
chiamata all’interno di party. All’interno del metodo party, possiamo vedere la
riga:
self.x = self.x + 1
Questa sintassi utilizzando l’operatore ‘punto’ indica ‘la x dentro self’. Quindi ogni
volta che viene chiamato party() viene incrementato di 1 il valore interno x che
viene poi visualizzato.
Per aiutarti a comprendere la differenza tra una funzione globale e un metodo
all’interno di una classe/oggetto, nella riga seguente puoi vedere un altro modo
per chiamare il metodo party all’interno dell’oggetto an:
PartyAnimal.party(an)
class PartyAnimal:
x = 0
def party(self) :
self.x = self.x + 1
14.8. CICLO DI VITA DELL’OGGETTO 179
print("So far",self.x)
an = PartyAnimal()
print ("Type", type(an))
print ("Dir ", dir(an))
print ("Type", type(an.x))
print ("Type", type(an.party))
# Code: http://www.py4e.com/code3/party3.py
Puoi notare che abbiamo creato un nuovo type utilizzando la parola chiave class
e nell’output di dir che sia l’attributo intero x sia il metodo party sono disponibili
nell’oggetto.
class PartyAnimal:
x = 0
def __init__(self):
print('I am constructed')
def party(self) :
self.x = self.x + 1
print('So far',self.x)
def __del__(self):
print('I am destructed', self.x)
180 CAPITOLO 14. PROGRAMMAZIONE AD OGGETTI
an = PartyAnimal()
an.party()
an.party()
an = 42
print('an contains',an)
# Code: http://www.py4e.com/code3/party4.py
I am contructed
So far 1
So far 2
I am destructed 2
an contains 42
an = 42
class PartyAnimal:
x = 0
name = ''
def __init__(self, nam):
self.name = nam
print(self.name,'constructed')
14.10. EREDITARIETÀ 181
def party(self) :
self.x = self.x + 1
print(self.name,'party count',self.x)
s = PartyAnimal('Sally')
j = PartyAnimal('Jim')
s.party()
j.party()
s.party()
# Code: http://www.py4e.com/code3/party5.py
Il costruttore ha sia un parametro self che punta all’istanza dell’oggetto sia altri
parametri che vengono passati al costruttore mentre l’oggetto viene costruito:
s = PartyAnimal('Sally')
la riga presente all’interno del costruttore:
self.name = nam
Copia il parametro passato in (nam) nell’attributo name all’interno dell’istanza
dell’oggetto.
L’output del programma mostra che ognuno degli oggetti (s e j) contiene le proprie
copie indipendenti di x e nam:
Sally constructed
Sally party count 1
Jim constructed
Jim party count 1
Sally party count 2
14.10 Ereditarietà
Un’altra caratteristica potente della programmazione orientata agli oggetti è la
possibilità di creare una nuova classe estendendone una già esistente. Quando
estendiamo una classe, chiamiamo la classe originale ‘classe genitore’ e la nuova
‘classe figlia’.
In questo esempio sposteremo la nostra classe PartyAnimal nel suo file:
class PartyAnimal:
x = 0
name = ''
def __init__(self, nam):
self.name = nam
print(self.name,'constructed')
def party(self) :
182 CAPITOLO 14. PROGRAMMAZIONE AD OGGETTI
self.x = self.x + 1
print(self.name,'party count',self.x)
# Code: http://www.py4e.com/code3/party.py
class CricketFan(PartyAnimal):
points = 0
def six(self):
self.points = self.points + 6
self.party()
print(self.name,"points",self.points)
s = PartyAnimal("Sally")
s.party()
j = CricketFan("Jim")
j.party()
j.six()
print(dir(j))
# Code: http://www.py4e.com/code3/party6.py
Nel definire l’oggetto CricketFan abbiamo indicato che stiamo estendendo la classe
PartyAnimal: tutte le variabili (x) e i metodi (party) della classe PartyAnimal
sono ereditate dalla classe CricketFan.
Puoi vedere che all’interno del metodo six nella classe CricketFan possiamo chia-
mare il metodo party dalla classe PartyAnimal. Le variabili e i metodi della classe
genitore sono uniti nella classe figlio.
Durante l’esecuzione del programma possiamo vedere che s e j sono istanze in-
dipendenti di PartyAnimal e CricketFan. L’oggetto j ha capacità aggiuntive
rispetto all’oggetto s.
Sally constructed
Sally party count 1
Jim constructed
Jim party count 1
Jim party count 2
Jim points 6
['__class__', '__delattr__', ... '__weakref__',
'name', 'party', 'points', 'six', 'x']
Nell’output dir per l’oggetto j (istanza della classe CricketFan) puoi vedere che
entrambi hanno sia gli attributi e i metodi della classe genitore sia gli attributi e i
metodi che sono stati aggiunti quando la classe è stata estesa per creare la classe
CricketFan.
14.11. SOMMARIO 183
14.11 Sommario
stuff = list()
stuff.append('python')
stuff.append('chuck')
stuff.sort()
print (stuff[0])
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))
# Code: http://www.py4e.com/code3/party1.py
La prima riga costruisce un oggetto list. Durante la sua costruzione viene chia-
mato il metodo constructor (chiamato __init__) per impostare gli attributi dei
dati interni che verranno utilizzati per memorizzare i dati dell’elenco. Grazie
all’incapsulamento non abbiamo bisogno di sapere o di preoccuparci di come siano
organizzati questi attributi dei dati interni.
Non stiamo passando alcun parametro al costruttore e quando il costruttore ritorna,
usiamo la variabile stuff per puntare all’istanza restituita della classe list.
La seconda e la terza riga chiamano il metodo append con un parametro per ag-
giungere un nuovo elemento alla fine dell’elenco tramite l’aggiornamento degli attri-
buti all’interno di stuff. Nella quarta riga viene chiamato il metodo sort, senza
parametri, per ordinare i dati all’interno dell’oggetto stuff.
Quindi visualizziamo il primo elemento nell’elenco usando le parentesi quadre che
sono una scorciatoia per chiamare il metodo __getitem__ all’interno dell’oggetto
stuff. Questo equivale a chiamare il metodo __getitem__ nella classe list pas-
sando l’oggetto stuff come primo parametro e la posizione che stiamo cercando
come secondo parametro.
Al termine del programma, prima che venga scartato l’oggetto stuff, viene chia-
mato il distruttore (denominato __del__) in modo che l’oggetto possa eliminare
qualsiasi questione rimasta in sospeso.
Queste sono le basi e la terminologia della programmazione ad oggetti. Ci sono
molti dettagli aggiuntivi su come utilizzare al meglio questo approccio durante lo
sviluppo di applicazioni e librerie di grandi dimensioni ma ciò va oltre lo scopo di
questo capitolo.3
3 Se sei curioso di sapere dove è definita la classe list, dai un’occhiata (speriamo che l’URL non
14.12 Glossario
Attributo Una variabile che fa parte di una classe.
Classe Un modello che può essere utilizzato per costruire un oggetto. Definisce
gli attributi e i metodi che compongono l’oggetto.
Classe figlia Una nuova classe creata quando viene estesa una classe genitore. La
classe figlia eredita tutti gli attributi e i metodi della classe genitore.
Costruttore Un metodo opzionale con un nome speciale (__init__) chiamato
nel momento in cui una classe viene usata per costruire un oggetto. Di solito
viene utilizzato per impostare i valori iniziali dell’oggetto.
Distruttore Usato raramente, un metodo opzionale con un nome speciale
(__del__) che viene chiamato appena prima che un oggetto venga distrutto.
185
186 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
colonna attributo
Tabella Relazione
Nelle descrizioni tecniche dei database relazionali i concetti di tabella, riga e colon-
na sono più formalmente indicati rispettivamente come relazione, tupla ed attributo.
In questo capitolo noi utilizzeremo i termini meno formali.
colonna, in questo capitolo manterremo una rigorosa distinzione dei nostri tipi di dati in modo
che i concetti si possano applicare anche ad altre tipologie di database come MySQL.
15.4. CREAZIONE DI UNA TABELLA 187
Per conoscere i vari tipi di dati supportati da SQLite potete consultare il seguente
indirizzo: http://www.sqlite.org/datatypes.html
Definire fin dall’inizio la struttura dei vostri dati può sembrare scomodo nei primi
tempi, ma il vantaggio è un accesso veloce ai vostri dati anche quando il database
conterrà una grande quantità di dati. Il codice necessario per creare un data-
base composto da una tabella denominata Tracks impostata su due colonne nel
database è il seguente:
import sqlite3
conn = sqlite3.connect('music.sqlite')
cur = conn.cursor()
conn.close()
# Code: http://www.py4e.com/code3/db1.py
execute
U
fetchone R Users Courses
2
fetchall 3
Members
close 4
!
p'
Una volta ottenuto il cursore, possiamo iniziare a eseguire comandi sui contenuti
del database usando il metodo execute(). I comandi del database sono espressi
in un linguaggio speciale che è stato standardizzato da molti diversi fornitori di
database per permetterci di imparare un solo linguaggio per database.
188 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
Il secondo comando crea una tabella chiamata Tracks con una colonna di testo
chiamata title e una colonna con dati numerici interi chiamata plays.
Ora che abbiamo creato una tabella chiamata Tracks, possiamo inserirvi alcuni
dati usando l’operatore SQL INSERT. Di nuovo, iniziamo effettuando una connes-
sione al database e ottenendo il cursore. Possiamo quindi eseguire comandi SQL
usando il cursore. Il comando SQL INSERT indica quale tabella stiamo usando e
quindi definisce una nuova riga elencando i campi che vogliamo includere (title,
plays) seguito dal VALUES che vogliamo sia inserito nella nuova riga. Indichiamo
i valori con punti interrogativi (?,?) per indicare che i valori effettivi vengono
passati come una tupla ('My Way', 15) come secondo parametro della chiamata
execute().
import sqlite3
conn = sqlite3.connect('music.sqlite')
cur = conn.cursor()
print('Tracks:')
cur.execute('SELECT title, plays FROM Tracks')
for row in cur:
print(row)
cur.close()
# Code: http://www.py4e.com/code3/db2.py
15.5. RIEPILOGO DELLO STRUCTURED QUERY LANGUAGE 189
Per prima cosa inseriamo due righe nella nostra tabella e usiamo commit() per
effettuare la scrittura dei dati nel file di database.
Tracks
title plays
Thunderstruck 20
My Way 15
Quindi usiamo il comando SELECT per recuperare le righe che abbiamo appena
inserito dalla tabella. Nel comando SELECT, indichiamo quali colonne selezionare
(title, plays) e indichiamo da quale tabella vogliamo recuperare i dati. Dopo
aver eseguito l’istruzione SELECT, è possibile far scorrere il cursore con un’istruzione
for. Per migliorare l’efficienza, il cursore non legge tutti i dati dal database quan-
do eseguiamo l’istruzione SELECT. I dati vengono invece letti su richiesta mentre
scorriamo le righe con l’istruzione for. L’output del programma è il seguente:
Tracks:
('Thunderstruck', 20)
('My Way', 15)
Il nostro ciclo for trova due righe, e ogni riga è una tupla Python con il primo
valore come title e il secondo valore come numero di plays.
Nota: potreste vedere stringhe che iniziano con “u” in altri libri o su Internet.
Si trattava di un’indicazione utilizzata in Python 2 che specificava che le stringhe
sono stringhe Unicode in grado di memorizzare set di caratteri non latini. Per
impostazione predefinita, in Python 3 tutte le stringhe sono unicode.
Alla fine del programma, eseguiamo un comando SQL per “CANCELLARE” le
righe che abbiamo appena creato in modo da poter eseguire il programma più e
più volte. Il comando DELETE contiene l’uso di una clausola WHERE che ci consente di
applicare un criterio di selezione in modo da poter chiedere al database di applicare
il comando solo alle righe che rispettano il criterio. In questo esempio il criterio
si applica a tutte le righe, quindi svuotiamo la tabella in modo da essere in grado
di eseguire il programma ripetutamente. Dopo aver eseguito DELETE, chiamiamo
anche commit() per rendere effettiva la rimozione dei dati dal database.
Per inserire una riga in una tabella, usiamo il comando SQL INSERT:
L’istruzione INSERT specifica il nome della tabella, quindi un elenco dei cam-
pi/colonne che si desidera popolare nella nuova riga, quindi la parola chiave VALUES
e un elenco di valori corrispondenti per ciascuno dei campi. Il comando SQL SELECT
è usato per recuperare righe e colonne da un database. L’istruzione SELECT con-
sente di specificare quali colonne si desidera recuperare e una clausola WHERE che
seleziona le righe che si desidera esaminare. Permette anche una clausola opzionale
’ORDER BY‘ per controllare l’ordinamento delle righe restituite.
L’uso di * indica che si desidera che il database restituisca tutte le colonne per
ogni riga che corrisponde alla clausola WHERE. Si noti che, a differenza di Python,
in una clausola WHERE di SQL usiamo un singolo segno di uguale per indicare un
test per l’uguaglianza piuttosto che un doppio segno di uguale. Altre operazioni
logiche consentite in una clausola WHERE includono <, >, <=, >=, !=, così come AND
e OR e parentesi per costruire espressioni logiche. È possibile richiedere che le righe
restituite vengano ordinate in base a uno dei campi in questo modo:
Per rimuovere una riga occorre usare una clausola WHERE in un’istruzione SQL
DELETE. La clausola WHERE determina quali righe devono essere cancellate:
TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'
conn = sqlite3.connect('spider.sqlite')
cur = conn.cursor()
cur.execute('''
CREATE TABLE IF NOT EXISTS Twitter
(name TEXT, retrieved INTEGER, friends INTEGER)''')
ctx.verify_mode = ssl.CERT_NONE
while True:
acct = input('Enter a Twitter account, or quit: ')
if (acct == 'quit'): break
if (len(acct) < 1):
cur.execute('SELECT name FROM Twitter WHERE retrieved = 0 LIMIT 1')
try:
acct = cur.fetchone()[0]
except:
print('No unretrieved Twitter accounts found')
continue
print('Remaining', headers['x-rate-limit-remaining'])
js = json.loads(data)
# Debugging
# print json.dumps(js, indent=4)
countnew = 0
countold = 0
for u in js['users']:
friend = u['screen_name']
print(friend)
cur.execute('SELECT friends FROM Twitter WHERE name = ? LIMIT 1',
(friend, ))
try:
count = cur.fetchone()[0]
cur.execute('UPDATE Twitter SET friends = ? WHERE name = ?',
(count+1, friend))
countold = countold + 1
except:
cur.execute('''INSERT INTO Twitter (name, retrieved, friends)
VALUES (?, 0, 1)''', (friend, ))
countnew = countnew + 1
print('New accounts=', countnew, ' revisited=', countold)
conn.commit()
cur.close()
# Code: http://www.py4e.com/code3/twspider.py
l’account, una per indicare se abbiamo recuperato gli amici di questo account e
quante volte questo account è stato inserito fra le “amicizie”. Nel ciclo principale
del programma, chiediamo all’utente di specificare il nome di un account Twitter
o di digitare “exit” per uscire dal programma.
Se l’utente inserisce un account Twitter, recuperiamo l’elenco di amici e degli stati
per quell’utente e aggiungiamo ogni amico al database se non è già presente. Se
l’amico è già nell’elenco, aggiungiamo 1 al campo friends nella riga del database.
Se l’utente preme Invio, cerchiamo nel database il prossimo account Twitter che
non abbiamo ancora recuperato, recuperiamo gli amici e gli stati per quell’account,
li aggiungiamo al database o li aggiorniamo, e aumentiamo il loro conteggio come
amici. Una volta recuperato l’elenco di amici e stati, passiamo in rassegna tutti gli
elementi user in JSON restituito e recuperiamo lo screen_name per ogni utente.
Quindi usiamo l’istruzione SELECT per controllare se abbiamo già memorizzato que-
sto particolare screen_name nel database e recuperiamo il conteggio delle amicizie
(amici) se il record esiste.
countnew = 0
countold = 0
for u in js['users'] :
friend = u['screen_name']
print(friend)
cur.execute('SELECT friends FROM Twitter WHERE name = ? LIMIT 1',
(friend, ) )
try:
count = cur.fetchone()[0]
cur.execute('UPDATE Twitter SET friends = ? WHERE name = ?',
(count+1, friend) )
countold = countold + 1
except:
cur.execute('''INSERT INTO Twitter (name, retrieved, friends)
VALUES ( ?, 0, 1 )''', ( friend, ) )
countnew = countnew + 1
print('New accounts=',countnew,' revisited=',countold)
conn.commit()
Una volta che il cursore esegue l’istruzione SELECT, dobbiamo recuperare le righe.
Potremmo farlo con un’istruzione for, ma dal momento che stiamo recuperando
solo una riga (LIMIT 1), possiamo usare il metodo fetchone() per recuperare
la prima (e unica) riga risultato dell’operazione SELECT. Dato che fetchone()
restituisce la riga come una tupla (anche se esiste un solo campo), prendiamo il
primo valore dalla tupla e lo usiamo per ottenere il conteggio corrente delle amicizie
nella variabile count.
Se questo recupero ha esito positivo, usiamo l’istruzione SQL UPDATE con una
clausola WHERE per aggiungere 1 alla colonna friends per la riga che corrisponde
all’account dell’amico. Si noti che ci sono due segnaposto (per esempio punti
interrogativi) nel SQL, mentre il secondo parametro di ’execute()‘ è una tupla
di due elementi che contiene i valori da sostituire nel SQL al posto dei punti
interrogativi.
Se il codice nel blocco try fallisce, ciò è probabilmente dovuto al fatto che nessun
record corrisponde alla clausola WHERE name = ? nell’istruzione SELECT. Quindi
194 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
nel blocco except, usiamo l’istruzione SQL INSERT per aggiungere lo screen_name
dell’amico nella tabella, con l’indicazione che non abbiamo ancora recuperato lo
screen_name degli amici e impostato il conteggio delle amicizie su zero. Quin-
di, la prima volta che lanciamo il programma e inseriamo un account Twitter, il
programma viene eseguito come segue:
import sqlite3
conn = sqlite3.connect('spider.sqlite')
cur = conn.cursor()
cur.execute('SELECT * FROM Twitter')
count = 0
for row in cur:
print(row)
count = count + 1
print(count, 'rows.')
cur.close()
# Code: http://www.py4e.com/code3/twdump.py
('opencontent', 0, 1)
('lhawthorn', 0, 1)
('steve_coppin', 0, 1)
('davidkocher', 0, 1)
('hrheingold', 0, 1)
...
20 rows.
Vediamo una riga per ogni screen_name, del quale non abbiamo recuperato i dati e
che tutti nel database hanno un amico. A questo punto il nostro database riporta il
recupero degli amici del nostro primo account Twitter (drchuck). Possiamo eseguire
nuovamente il programma e dirgli di recuperare gli amici del prossimo account “non
elaborato” semplicemente premendo invio invece di digitare un account Twitter,
come segue:
15.6. UNO SPIDER SU TWITTER UTILIZZANDO UN DATABASE 195
Poiché abbiamo premuto Invio (cioè non abbiamo specificato un account Twitter),
viene eseguito il seguente codice:
if ( len(acct) < 1 ) :
cur.execute('SELECT name FROM Twitter WHERE retrieved = 0 LIMIT 1')
try:
acct = cur.fetchone()[0]
except:
print('No unretrieved twitter accounts found')
continue
Usiamo l’istruzione SQL SELECT per recuperare il nome del primo utente (LIMIT 1)
che ha ancora a zero il valore “have we retrieved this user”. Usiamo anche il modello
fetchone()[0] all’interno di un blocco try/except per estrarre uno screen_name
dai dati recuperati o lanciare un messaggio di errore e eseguire il loopback. Se
recuperiamo con successo uno screen_name non elaborato, recuperiamo i suoi dati
in questo modo:
Una volta recuperati i dati, usiamo l’istruzione UPDATE per impostare la colonna
retrieved su 1, per indicare che abbiamo completato il recupero degli amici di
questo account.
Questo ci impedisce di recuperare gli stessi dati più e più volte e ci fa progredire
attraverso la rete di amici di Twitter. Se eseguiamo il programma delle amicizie e
premiamo invio due volte per recuperare gli amici del prossimo amico non visitato,
quindi eseguiamo il programma di scarico, otterremo il seguente risultato:
('opencontent', 1, 1)
('lhawthorn', 1, 1)
('steve_coppin', 0, 1)
('davidkocher', 0, 1)
('hrheingold', 0, 1)
...
('cnxorg', 0, 2)
('knoop', 0, 1)
196 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
('kthanos', 0, 2)
('LectureTools', 0, 1)
...
55 rows.
Ogni volta che incontriamo una persona che “drchuck” sta seguendo, inseriremo
una riga del modulo:
Mentre elaboriamo i 20 amici del feed Twitter drchuck, inseriremo 20 record con
“drchuck” come primo parametro, quindi finiremo per duplicare molte volte la
stringa nel database. Questa duplicazione di dati di stringhe viola una delle migliori
15.8. PROGRAMMAZIONE CON PIÙ TABELLE 197
pratiche per la normalizzazione del database, secondo la quale non dovremmo mai
inserire più di una volta gli stessi dati di stringa nel database. Se abbiamo bisogno
dei dati più di una volta, creiamo una chiave numerica per i dati e facciamo
riferimento ai dati effettivi usando questa chiave. In termini pratici, una stringa
occupa molto più spazio di un numero sul disco e nella memoria del nostro computer
e richiede più tempo per il processore da essere confrontata e ordinata.
Se abbiamo solo poche centinaia di voci, il tempo di archiviazione e di elaborazione
non conta molto, ma se abbiamo un milione di persone nel nostro database e una
possibilità di 100 milioni di link di amici, è importante essere in grado di scansionare
i dati il più rapidamente possibile. Memorizzeremo i nostri account Twitter in una
tabella denominata Persone invece della tabella Twitter utilizzata nell’esempio
precedente. La tabella Persone ha una colonna aggiuntiva per memorizzare la
chiave numerica associata alla riga per questo utente di Twitter. SQLite ha una
funzione che aggiunge automaticamente il valore chiave per ogni riga che inseriamo
in una tabella utilizzando un tipo speciale di colonna di dati (INTEGER PRIMARY
KEY). Possiamo creare la tabella Persone con questa colonna aggiuntiva id in
questo modo:
Si noti che non stiamo più memorizzando un conteggio delle amicizie in ogni riga
della tabella Persone. Quando selezioniamo “INTEGER PRIMARY KEY” come
formato della nostra colonna id, stiamo indicando che vogliamo che SQLite gestisca
questa colonna assegnando automaticamente una chiave numerica unica per ogni
riga che inseriamo. Aggiungiamo anche la parola chiave UNIQUE per indicare
che non consentiremo a SQLite di inserire due righe con lo stesso valore in name.
Adesso, invece di creare la precedente tabella Pals, creiamo una tabella chiamata
Follows con due colonne numeriche from_id e to_id e un vincolo sulla tabella
che indica che la combinazione di from_id e to_id deve essere unica (cioè, non
possiamo inserire righe duplicate) nel nostro database.
People
Follows
id name retrieved
from_id to_id
1 drchuck 1
1 2
2 opencontent 1
1 3
3 lhawthorn 1
1 4
4 steve_coppin 0
...
...
TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'
conn = sqlite3.connect('friends.sqlite')
cur = conn.cursor()
while True:
acct = input('Enter a Twitter account, or quit: ')
if (acct == 'quit'): break
if (len(acct) < 1):
cur.execute('SELECT id, name FROM People WHERE retrieved=0 LIMIT 1')
try:
(id, acct) = cur.fetchone()
15.8. PROGRAMMAZIONE CON PIÙ TABELLE 199
except:
print('No unretrieved Twitter accounts found')
continue
else:
cur.execute('SELECT id FROM People WHERE name = ? LIMIT 1',
(acct, ))
try:
id = cur.fetchone()[0]
except:
cur.execute('''INSERT OR IGNORE INTO People
(name, retrieved) VALUES (?, 0)''', (acct, ))
conn.commit()
if cur.rowcount != 1:
print('Error inserting account:', acct)
continue
id = cur.lastrowid
url=twurl.augment(TWITTER_URL,{'screen_name':acct,'count':'100'})
print('Retrieving account', acct)
try:
connection = urllib.request.urlopen(url, context=ctx)
except Exception as err:
print('Failed to Retrieve', err)
break
data = connection.read().decode()
headers = dict(connection.getheaders())
print('Remaining', headers['x-rate-limit-remaining'])
try:
js = json.loads(data)
except:
print('Unable to parse json')
print(data)
break
# Debugging
# print(json.dumps(js, indent=4))
countnew = 0
countold = 0
for u in js['users']:
friend = u['screen_name']
200 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
print(friend)
cur.execute('SELECT id FROM People WHERE name = ? LIMIT 1',
(friend, ))
try:
friend_id = cur.fetchone()[0]
countold = countold + 1
except:
cur.execute('''INSERT OR IGNORE INTO People (name, retrieved)
VALUES (?, 0)''', (friend, ))
conn.commit()
if cur.rowcount != 1:
print('Error inserting account:', friend)
continue
friend_id = cur.lastrowid
countnew = countnew + 1
cur.execute('''INSERT OR IGNORE INTO Follows (from_id, to_id)
VALUES (?, ?)''', (id, friend_id))
print('New accounts=', countnew, ' revisited=', countold)
print('Remaining', headers['x-rate-limit-remaining'])
conn.commit()
cur.close()
# Code: http://www.py4e.com/code3/twfriends.py
Stabiliamo che la colonna name nella tabella People deve essere UNIQUE. Indichiamo
anche che la combinazione dei due numeri in ogni riga della tabella Follows deve
essere unica. Questi vincoli ci impediscono di commettere errori, come l’aggiunta
della stessa relazione più di una volta. Possiamo trarre vantaggio da questi vincoli
nel seguente codice:
Aggiungiamo la clausola OR IGNORE alla nostra istruzione INSERT per indicare che
se questo particolare INSERT dovesse causare una violazione della regola “name
deve essere univoco”, il sistema di database può ignorare l’istruzione INSERT .
Stiamo usando il vincolo del database come rete di sicurezza per assicurarci di non
fare inavvertitamente qualcosa di sbagliato. Allo stesso modo, il codice seguente
garantisce che non si aggiunga due volte la stessa relazione Follows.
friend = u['screen_name']
cur.execute('SELECT id FROM People WHERE name = ? LIMIT 1',
(friend, ) )
try:
friend_id = cur.fetchone()[0]
countold = countold + 1
except:
2 In generale, quando una frase inizia con “se tutto va bene” scoprirete che il codice deve usare
try/except.
202 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
Se finiamo nel codice except, significa semplicemente che la riga non è stata trovata,
quindi dobbiamo inserirla. Usiamo INSERT OR IGNORE solo per evitare errori e
quindi richiamiamo commit() per rendere effettivo l’aggiornamento del database.
Al termine della scrittura, possiamo controllare il valore di “cur.rowcount” per
vedere quante righe sono state interessate. Poiché stiamo tentando di inserire
una singola riga, se il numero di righe interessate è diverso da 1, si tratta di un
errore. Se “INSERT” ha successo, possiamo dare un’occhiata a “cur.lastrowid” per
scoprire quale valore il database ha assegnato alla colonna “id‘ nella nostra riga
appena creata.
Una volta che conosciamo il valore chiave sia per l’utente di Twitter che per l’amico
nel JSON, è semplice inserire i due numeri nella tabella Follows con il seguente
codice:
People:
(1, 'drchuck', 1)
(2, 'opencontent', 1)
(3, 'lhawthorn', 1)
(4, 'steve_coppin', 0)
(5, 'davidkocher', 0)
55 rows.
Follows:
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
60 rows.
• Una chiave logica è una chiave che il “mondo reale” potrebbe utilizzare per
cercare una riga. Nel nostro esempio di modello di dati, il campo name è una
chiave logica. È lo pseudonimo dell’utente e in effetti esaminiamo più volte
la riga di un utente nel programma usando il campo name. Spesso troverete
che ha senso aggiungere un vincolo UNIQUE a una chiave logica. Poiché la
chiave logica è il modo in cui cerchiamo una riga dal mondo esterno, non ha
senso consentire che più righe di una tabella abbiano lo stesso valore.
• Una chiave esterna è solitamente un numero che punta alla chiave primaria
di una riga associata in una tabella diversa. Un esempio di chiave esterna
204 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
La clausola JOIN indica che stiamo selezionando dei campi fra le tabelle Follows e
People. La clausola ON indica come le due tabelle devono essere unite: prendiamo
le righe da Follows e aggiungiamo la riga da People quando il campo from_id in
Follows è lo stesso valore di id nella tabella People.
People
Follows
id name retrieved
from_id to_id
1 drchuck 1
1 2
2 opencontent 1
1 3
3 lhawthorn 1 1 4
4 steve_coppin 0 ...
...
JOIN crea una metariga per ciascuna delle coppie di righe corrispondenti, du-
plicando i dati secondo necessità. Il seguente codice mostra i dati che avremo
nel database dopo che il (precedente) programma multitabella spider di Twitter è
stato eseguito più volte.
import sqlite3
conn = sqlite3.connect('friends.sqlite')
cur = conn.cursor()
cur.close()
# Code: http://www.py4e.com/code3/twjoin.py
python twjoin.py
People:
(1, 'drchuck', 1)
(2, 'opencontent', 1)
(3, 'lhawthorn', 1)
(4, 'steve_coppin', 0)
(5, 'davidkocher', 0)
55 rows.
206 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
Follows:
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
60 rows.
Connections for id=2:
(2, 1, 1, 'drchuck', 1)
(2, 28, 28, 'cnxorg', 0)
(2, 30, 30, 'kthanos', 0)
(2, 102, 102, 'SomethingGirl', 0)
(2, 103, 103, 'ja_Pac', 0)
20 rows.
Le colonne delle tabelle People e Follows e l’ultimo insieme di righe sono il ri-
sultato di SELECT con la clausola JOIN. Nell’ultima selezione, cerchiamo account
di amici di “opencontent” (ad es. People.id = 2). In ognuna delle “metarighe”
nell’ultima selezione, le prime due colonne provengono dalla tabella Follows segui-
ta dalle colonne da tre a cinque dalla tabella People. Potete anche vedere che la
seconda colonna (Follows.to_id) corrisponde alla terza colonna (People.id) in
ognuna delle “metarighe” unite.
15.11 Riassunto
Questo capitolo ha fatto un bel po’ di strada per darvi una panoramica elementare
sull’utilizzo di un database in Python. Scrivere il codice per utilizzare un database
per archiviare i dati è più complicato rispetto ai dizionari Python o ai semplici file,
quindi ci sono pochi motivi per utilizzare un database a meno che l’applicazione
non abbia realmente bisogno delle sue funzionalità. Le situazioni in cui un database
può essere abbastanza utile sono: (1) quando l’applicazione deve effettuare piccoli
aggiornamenti casuali all’interno di un insieme di dati di grandi dimensioni, (2)
quando i dati sono così grandi che non possono essere contenuti in un dizionario e
bisogna cercare ripetutamente delle informazioni o (3) quando si ha un processo di
lunga durata che si desidera essere in grado di interrompere e riavviare e conservare
i dati da una esecuzione all’altra.
È possibile creare un semplice database con una singola tabella per soddisfare
le esigenze di molte applicazioni, ma la maggior parte dei problemi richiederà
diverse tabelle e collegamenti/relazioni tra le righe in diverse tabelle. Quando si
inizia a creare collegamenti tra tabelle, è importante eseguire una progettazione
ponderata e seguire le regole della normalizzazione del database per sfruttare al
meglio le capacità del database. Poiché la motivazione principale per l’utilizzo
di un database è che avete una grande quantità di dati da trattare, è importante
modellare i vostri dati in modo efficiente in modo che i vostri programmi funzionino
il più velocemente possibile.
15.12. DEBUG 207
15.12 Debug
15.13 Glossario
Attributo Uno dei valori all’interno di una tupla. Più comunemente chiamato
“colonna” o “campo”.
Vincolo Quando diciamo al database di applicare una regola su un campo o una
riga in una tabella. Un vincolo comune è quello di stabilire che non ci possono
essere valori duplicati in un campo particolare (cioè, tutti i valori devono
essere unici).
Cursore Un cursore consente di eseguire comandi SQL in un database e recupe-
rare i dati dal database. Un cursore è simile a un socket o un handle di file
per connessioni di rete e file, rispettivamente.
Browser del database Un software che consente di connettersi direttamente a
un database e manipolare il database direttamente senza scrivere un pro-
gramma.
Chiave esterna Una chiave numerica che punta alla chiave primaria di una ri-
ga in un’altra tabella. Le chiavi esterne stabiliscono relazioni tra righe
memorizzate in tabelle diverse.
Indice Dati aggiuntivi che il software del database conserva come righe e inserisce
in una tabella per rendere le ricerche molto veloci.
Chiave logica Una chiave che il “mondo esterno” utilizza per cercare una parti-
colare riga. Ad esempio in una tabella di account utente, l’indirizzo email di
una persona potrebbe essere un buon candidato come chiave logica per i dati
dell’utente.
Normalizzazione Progettazione di un modello di dati in modo che nessun dato
sia replicato. Archiviamo ogni elemento di dati in un posto nel database e
facciamo altrove riferimento ad essi utilizzando una chiave esterna.
Chiave primaria Una chiave numerica assegnata a ciascuna riga che viene utiliz-
zata per fare riferimento a una riga in una tabella da un’altra tabella. Spesso
il database è configurato per assegnare automaticamente le chiavi primarie
quando vengono inserite le righe.
Relazione Un’area all’interno di un database che contiene tuple e attributi. Più
comunemente chiamata “tabella”.
208 CAPITOLO 15. UTILIZZO DEI DATABASE E SQL
Tupla Una singola voce in una tabella di database che è un insieme di attributi.
Più comunemente chiamata “riga”.
Capitolo 16
209
210 CAPITOLO 16. COME VISUALIZZARE DATI
Le prime cinque entità sono state saltate poiché erano già presenti nel database.
Il programma ha continuato la verifica fino a quando ha trovato una entità con la
posizione mancante e ne ha iniziato il recupero.
Tramite lo script geodump.py ti é visualizzare i dati già caricati nel database geoda-
ta.sqlite. Questo programma crea il file JavaScript eseguibile where.js contenente
l’indirizzo, la latitudine e la longitudine di ogni entità utilizzando i dati contenuti
nel tuo database.
myData = [
[42.3396998,-71.08975, 'Northeastern Uni ... Boston, MA 02115'],
[40.6963857,-89.6160811, 'Bradley University, ... Peoria,
IL 61625, USA'], [32.7775,35.0216667, 'Technion, Viazman 87,
Kesalsaba, 32000, Israel'],
...
];
Questa è una variabile JavaScript che contiene un elenco di elenchi. La sintassi per
le costanti dell’elenco JavaScript dovrebbe esserti familiare in quanto molto simile
a quella di Python.
Per poter vedere le posizioni geografiche delle entità devi aprire where.html in un
browser. Passando con il mouse su ciascun marker della mappa vedrai la posizione
che restituita dall’API di geocodifica. Se aprendo il file where.html non riesci a
vedere alcun dato, prova a verificare le impostazioni della console JavaScript o
sviluppatore del tuo browser.
212 CAPITOLO 16. COME VISUALIZZARE DATI
in altre parole nello stesso database/programma possono essere presenti più punti
di partenza potenziali, chiamati “web”. Lo spider sceglie casualmente la successiva
pagina da elaborare tra tutti i link non ancora visitati.
Tramite spdump.py puoi scaricare il contenuto del file spider.sqlite:
Potrai visualizzare la struttura dei nodi e dei collegamenti relativi a questi dati
aprendo il file force.html in un browser. una volta aperto il file ti sarà possibile,
tramite un clic, trascinare qualsiasi nodo e, tramite il doppio clic, visualizzare
l’URL di un nodo specifico.
Se nel frattempo utilizzi una delle funzioni precedenti, potrai consultare i dati
aggiornati contenuti in spider.json rieseguendo nuovamente spjson.py e premendo
F5 una volta ritornato sul browser.
Il primo passo è quello di lanciare lo spider sul repository gmane. L’URL di base
è inserito direttamente in gmane.py ed è presente nell’elenco degli sviluppatori di
Sakai. Puoi esaminare in qualunque momento un altro repository cambiando l’url
di base ed assicurandoti di eliminare il file content.sqlite.
Il file gmane.py opera come uno spider responsabile dato che la sua velocità di
esecuzione é lenta: recupera un messaggio di posta elettronica al secondo in modo
da evitare di essere limitato da gmane. Memorizza tutti i dati in un database
e può essere interrotto e riavviato tutte le volte che sia necessario. Dato che
probabilmente saranno necessarie molte ore per scaricare tutti i dati, potrebbe
essere necessario riavviare più volte questo script più volte.
Ecco un esempio dell’esecuzione di gmane.py durante il recupero degli ultimi cinque
messaggi dalla lista degli sviluppatori di Sakai:
La seconda parte del processo di analisi viene eseguita dallo script gmodel.py: i
dati grezzi contenuti in content.sqlite vengono letti ed analizzati e viene ne pro-
dotto una versione pulita e ben modellata nel file index.sqlite. Questo database
é normalmente molto più piccolo rispetto a content.sqlite (spesso fino a 10 volte
minore rispetto all’originario) perché viene compresso il testo dell’intestazione del
corpo.
Ogni volta che esegui gmodel.py, lo script elimina e ricostruisce index.sqlite, per-
mettendoti di regolare i suoi parametri e modificare le tabelle di mappatura in
content.sqlite per ottimizzare il processo di pulizia dei dati. Qui sotto è presente
un esempio della esecuzione di gmodel.py: viene Visualizzata una riga ogni volta
che vengono elaborati 250 messaggi di posta elettronica in modo da avvisarti dei
progressi dell’analisi dato che questa potrebbe protrarsi per un bel po’ prima di
terminare l’elaborazione di quasi un gigabyte di dati di posta.
vengono convertiti nell’indirizzo reale ogni volta che c’è una corrispondenza con un
indirizzo e-mail reale corrispondente in un punto qualsiasi del corpo del messaggio.
Nel database mapping.sqlite sono presenti due tabelle che ti consentono di mappare
sia i nomi di dominio sia i singoli indirizzi e-mail che cambiano nel corso della vita
della mailing list. Ad esempio, Steve Githens ha utilizzato i seguenti indirizzi email
quando ha cambiato lavoro durante la vita della lista degli sviluppatori di Sakai:
[email protected]
[email protected]
[email protected]
Possiamo aggiungere due voci alla tabella Mapping in content.sqlite per fare sì che
gmodel.py mappi correttamente tutti e tre gli indirizzi:
ti è inoltre possibile creare voci simili nella tabella DNSMapping se ci sono più
nomi DNS che desideri associare a un singolo DNS. La seguente mappatura è stata
aggiunta ai dati di Sakai:
218 CAPITOLO 16. COME VISUALIZZARE DATI
in modo che tutti gli account dei vari campus della Indiana University siano
tracciati insieme.
ti è possibile eseguire gmodel.py ripetutamente mentre esamini i dati ed aggiungere
tutte le mappature che ritieni necessarie per avere i dati sempre ordinati. Al
termine d di questa fase, avrai ottenuto una versione ben indicizzata delle email in
index.sqlite. Questo è il file che verrà utilizzato per eseguire l’analisi vera e propria
dei dati che sarà molto rapida.
La prima semplice analisi dei dati è determinare “chi ha inviato la maggior parte
dei messaggi” e “quale organizzazione ha inviato la maggior parte della email”. Ciò
é possibile tramite lo script gbasic.py:
Nota che quanto rapidamente viene eseguito gbasic.py rispetto a gmane.py o anche
gmodel.py. tutti gli script lavorando sugli stessi dati, ma gbasic.py sta usando i dati
compressi e normalizzati in index.sqlite. Se devi gestire molti dati, un processo a
più fasi come quello di questa applicazione potrebbe richiedere un po’ più di tempo
per essere sviluppato ma ti farà risparmiare un sacco di tempo quando inizierai
veramente a esplorare e visualizzare i tuoi dati.
Puoi ottenere una prima visualizzazione della frequenza delle parole dell’oggetto
delle email tramite gword.py:
Il risultato viene salvato nel file gword.js che puoi visualizzare tramite gword.htm
per produrre una nuvola di parole simile a quella mostrata all’inizio di questa
sezione.
Una seconda visualizzazione è prodotta da gline.py in cui viene calcolata la parte-
cipazione via e-mail da parte delle organizzazioni nel corso del tempo.
16.3. VISUALIZZAZIONE DEI DATI DELLA POSTA ELETTRONICA 219
L’output viene salvato in gline.js che può essere visualizzato tramite gline.htm.
Contributi
221
222 APPENDICE A. CONTRIBUTI
Edmonds, Maria Seiferle, Romer Kristi D. Aranas (RK), Grant Boyer, Hedemarrie
Dussan.
(Allen B. Downey)
Nel Gennaio 1999 mi stavo preparando a tenere un corso introduttivo sulla pro-
grammazione in Java. Lo avevo già tenuto tre volte ed ero frustrato: la percentua-
le di fallimento del corso era molto alta ed anche gli studenti che lo superavano,
raggiungevano un livello generale troppo basso.
Uno dei problemi che avevo notato erano i libri di testo. Troppo voluminosi, con
troppi dettagli non necessari su Java e non abbastanza istruzioni di alto livello su
come scrivere i programmi. E tutti risentivano dell’effetto botola: cominciavano
tranquillamente, procedevano gradualmente ma, intorno al Capitolo 5, la botola
si apriva e in molti vi cadevano. Gli studenti si ritrovavano con troppo materiale
nuovo, tutto insieme e troppo in fretta e io dovevo spendere il resto del semestre a
rimettere insieme i pezzi.
Due settimane prima dell’inizio del corso, decisi di scrivere il mio libro di testo. I
miei obiettivi erano:
• essere conciso. Per gli studenti è molto meglio leggere 10 pagine che non
leggerne 50,
• prestare attenzione al vocabolario. Ho cercato di minimizzare il gergo e
definire ogni termine sin dal primo uso,
• procedere gradualmente. Per evitare trappole ho preso gli argomenti più
difficili e li ho suddivisi in piccoli passi,
• focalizzarmi sulla programmazione, non sul linguaggio. Ho incluso la minima
selezione utile di Java ed ho tralasciato il resto.
Mi serviva un titolo e quasi per scherzo ho scelto How to Think Like a Computer
Scientist.
La prima versione era grezza ma funzionava. Gli studenti leggevano e capivano
abbastanza da permettermi di spendere il tempo in classe sugli argomenti più
ostici, più interessanti e (cosa più importante) facendo far pratica agli studenti.
Pubblicai il libro sotto la GNU Free Documentation License, che permette agli
utenti di copiare, modificare e ridistribuire il libro.
Quello che successe dopo fu la parte migliore. Jeff Elkner, un insegnate di scuola
superiore in Virginia, ha adottato il mio libro e lo ha “tradotto” per Python. Mi
inviò una copia del suo riadattamento ed ebbi la strana esperienza di imparare
Python leggendo il mio stesso libro.
Jeff ed io rivedemmo insieme il libro includendo anche uno caso di studio di Chris
Meyers e, nel 2001, pubblicammo How to Think Like a Computer Scientist: Lear-
ning with Python anch’esso sotto GNU Free Documentation License. Come Green
A.5. ELENCO DEI COLLABORATORI DI “THINK PYTHON” 223
Tea Press, pubblicai il libro ed iniziai a venderne copie via Amazon.com e librerie
dei college. Altri libri di Green Tea Press sono disponibili presso greenteapress.co
m.
Nel 2003 insegnai all’Olin College ed ebbi modo di insegnare Python per la prima
volta. Il contrasto con Java era impressionante: gli studenti avevano meno diffi-
coltà, imparavano di più, lavoravano a progetti più interessanti e, in generale, si
divertivano molto di più.
Nei seguenti 5 anni ho continuato a sviluppare il libro, correggere gli errori, mi-
gliorare gli esempi e ad aggiungere materiale, specialmente gli esercizi. Nel 2008
ho iniziato a lavorare ad una importante revisione e allo stesso tempo sono stato
contattato da un editore della Cambridge University Press che era intenzionato a
pubblicare la nuova versione del libro. Tempismo eccellente!
Spero che vi piaccia lavorare con questo libro, che vi aiuti ad imparare a program-
mare e a pensare, almeno un po’, come un Computer Scientist.
Per i dettagli sulla natura di ciascuno dei contributi di queste persone, fate riferi-
mento al testo “Think Python”.
Lloyd Hugh Allen, Yvon Boulianne, Fred Bremmer, Jonah Cohen, Michael Con-
lon, Benoit Girard, Courtney Gleason e Katherine Smith, Lee Harr, James Kaylin,
David Kershaw, Eddie Lam, Man-Yong Lee, David Mayo, Chris McAloon, Mat-
thew J. Moelter, Simon Dicon Montford, John Ouzts, Kevin Parks, David Pool,
Michael Schmitt, Robin Shaw, Paul Sleigh, Craig T. Snydal, Ian Thomas, Keith
Verheyden, Peter Winstanley, Chris Wrobel, Moshe Zadka, Christoph Zwersch-
ke, James Mayer, Hayden McAfee, Angel Arnal, Tauhidul Hoque e Lex Berezhny,
Dr. Michele Alzetta, Andy Mitchell, Kalin Harvey, Christopher P. Smith, David
Hutchins, Gregor Lingl, Julie Peters, Florin Oprina, D. J. Webre, Ken, Ivo We-
ver, Curtis Yanko, Ben Logan, Jason Armstrong, Louis Cordier, Brian Cain, Rob
Black, Jean-Philippe Rey alla Ecole Centrale Paris, Jason Mader della George Wa-
shington University made a number Jan Gundtofte-Bruun, Abel David e Alexis
Dinno, Charles Thayer, Roger Sperberg, Sam Bull, Andrew Cheung, C. Corey Ca-
pel, Alessandra, Wim Champagne, Douglas Wright, Jared Spindor, Lin Peiheng,
Ray Hagtvedt, Torsten Hübsch, Inga Petuhhov, Arne Babenhauserheide, Mark E.
Casida, Scott Tyler, Gordon Shephard, Andrew Turner, Adam Hobart, Daryl Ham-
mond e Sarah Zimmerman, George Sass, Brian Bingham, Leah Engelbert-Fenton,
Joe Funke, Chao-chao Chen, Jeff Paine, Lubos Pintes, Gregg Lind e Abigail Hei-
thoff, Max Hailperin, Chotipat Pornavalai, Stanislaw Antol, Eric Pashman, Miguel
Azevedo, Jianhua Liu, Nick King, Martin Zuther, Adam Zimmerman, Ratnakar
Tiwari, Anurag Goel, Kelli Kratzer, Mark Griffiths, Roydan Ongie, Patryk Wolo-
wiec, Mark Chonofsky, Russell Coleman, Wei Huang, Karen Barber, Nam Nguyen,
Stéphane Morin, Fernando Tardio e Paul Stoop.
Appendice B
225
226 APPENDICE B. DETTAGLI SUL COPYRIGHT
227
228 INDICE ANALITICO
variabile, 20, 29
aggiornamento, 57
vincolo, 207
virgola, 47
virgola mobile, 29
virgolette, 19, 20, 67
Visualizzazione
mappa, 209
rank della pagina, 212
reti, 212
web
scraping, 149
XML, 170