Sei sulla pagina 1di 161

Stefano Riccio

Redazione HTML.it

FONDAMENTI DI PROGRAMMAZIONE
IN
1
2
Lezione 1di 40
Introduzione e un po’ di storia
Python è un linguaggio di programmazione moderno, dalla sintassi
semplice e potente che ne facilita l’apprendimento. Gli ambiti di
applicazione di questo linguaggio di programmazione sono svariati:
sviluppo di siti o applicazioni Web e desktop, realizzazione di
interfacce grafiche, amministrazione di sistema, calcolo
scientifico e numerico, database, giochi, grafica 3D, eccetera.
L’obiettivo di questa guida è quello di fornire una panoramica
generale sulla programmazione in Python, che permetta al lettore
di sviluppare in modo facile e veloce le proprie applicazioni.
Nota: Questa è la riedizione della guida Python di Stefano Riccio,
revisionata e aggiornata da Ezio Melotti, Andrea Sindoni, e dalla
redazione di HTML.it
Prima di iniziare, può essere utile capire come e dove è nato
Python. Per farlo bisogna tornare un po’ indietro nel tempo
precisamente nei primi anni ottanta. In quegli anni, al National
Research Institute for Mathematics and Computer Science (CWI) di
Amsterdam, alcuni ricercatori tra cui Guido Van Rossum hanno
sviluppato un linguaggio di nome ABC, molto potente ed elegante,
che era diventato popolare nel mondo Unix.
Guido Van Rossum

Qualche anno dopo (fine anni ottanta) Guido Van Rossum ha avuto
una serie di idee mirate al miglioramento di ABC, e pertanto si
mise a lavorare allo sviluppo di un nuovo linguaggio: Python,
appunto.
Nel 1996 scrisse come prefazione del libro “Programming Python”,
prima edizione, le seguenti parole:
«Più di sei anni fa, nel dicembre 1989, stavo cercando un progetto
di programmazione per “hobby” che mi avrebbe dovuto tenere
occupato nella settimana vicina a Natale. Il mio ufficio… sarebbe
stato chiuso, ma io avevo un computer, e non molto di più. Decisi
di scrivere un interprete per un nuovo linguaggio di scripting a
cui avrei pensato dopo: un discendente dell’ABC, che sarebbe
dovuto appartenere agli hacker di Unix. Scelsi Python come nome

3
per il progetto, essendo leggermente irriverente (e perchè sono un
grande fan di Monty Python’s Flying Circus).»
Nel 2000 Van Rossum e il suo team si trasferiscono presso
BeOpen.com e formano i BeOpen PythonLabs team, con Python giunto
alla versione 1.6. Poco tempo dopo viene rilasciata la versione
2.0, che, tra le altre cose, migliorava il linguaggio con
l’aggiunta delle “list comprehension“.
Nel 2001 viene rilasciato Python 2.1, e ridefinita la licenza come
“Python Software Foundation License”. Python 2.2 fu considerato un
“rilascio puliza”, e la principale novità introdotta riguardò
l’unificazione dei tipi/classi.
Bisogna arrivare al Dicembre 2008 per assistere ad una vera
rivoluzione, con il rilascio della versione 3.0 di Python (o
“Python 3000” o “Py3k”). Questa nuova versione è molto simile alla
precedente, ma ha semplificato il linguaggio e introdotto diversi
miglioramenti (come ad esempio le stringhe Unicode di default).
Per via di questi cambiamenti, come vedremo in questa
guida, Python 3 non è compatibile con Python 2.
Al momento della scrittura di questa guida, ci sono stati altri 5
rilasci di Python 3 (fino ad arrivare a Python 3.5), che hanno
aggiunto ulteriori funzionalità e nuovi moduli. L’ultima versione
di Python 2 è invece Python 2.7, che ormai riceve solo bug fix.
Differenze tra Python 2 e Python 3
La seguente lista include alcuni tra i principali cambiamenti
introdotti da Python 3 (che saranno comunque più chiaro nella
prosieguo della guida):
 print è una funzione (in Python 2 era uno statement) e va
invocata usando le parentesi:
print('x')
Copy
 input è stato rimosso, e raw_input è stato rinominato input;
 tutte le stringhe sono ora Unicode di default, permettendo
l’utilizzo di qualsiasi alfabeto;
 i tipi unicode e str di Python 2 sono stati rinominati
rispettivamente in str e bytes;
 i tipi long e int sono stati unificati in int;
 la divisione tra due int ora ritorna un float (per esempio: 5 /
2 == 2.5);
 funzioni come range, map, e zip sono ora più efficienti;
 metodi come dict.keys(), dict.values(),
e dict.items() restituiscono view invece che creare nuove liste;
 l’operatore <> e altre sintassi duplicate e obsolete sono state
rimosse;
 alcuni moduli, metodi, e funzioni sono stati rinominati per
rispettare lo stile di scrittura PEP 8.
È possibile trovare in dettaglio tutte le novità

4
Lezione 2di 40

Perché usare Python

Oggi esistono numerosi linguaggi di programmazione, ma cosa spinge


ad usare Python, cosa c’è di particolare in questo linguaggio? Per
rendercene conto, in questa lezione esaminiamo alcuni dei punti di
forza di Python ed alcune delle sue applicazioni principali.
Python: i punti di forza

È free
Python è completamente gratuito ed è possibile usarlo e
distribuirlo senza restrizioni di copyright. Nonostante sia free,
da oltre 25 anni Python ha una comunità molto attiva, e riceve
costantemente miglioramenti che lo mantengono aggiornato e al
passo coi tempi.
È multi-paradigma
Python è un linguaggio multi-paradigma, che supporta sia la
programmazione procedurale (che fa uso delle funzioni), sia
la programmazione ad oggetti (includendo funzionalità come
l’ereditarietà singola e multipla, l’overloading degli operatori,
e il duck typing). Inoltre supporta anche diversi elementi della
programmazione funzionale (come iteratori e generatori).
È portabile
Python è un linguaggio portabile sviluppato in ANSI C. È possibile
usarlo su diverse piattaforme come: Unix, Linux, Windows, DOS,
Macintosh, Sistemi Real Time, OS/2, cellulari Android e iOS. Ciò è
possibile perché si tratta di un linguaggio interpretato, quindi
lo stesso codice può essere eseguito su qualsiasi piattaforma
purché abbia l’interprete Python installato.
È facile da usare
Python è un linguaggio di alto livello che è al tempo stesso
semplice e potente. La sintassi e i diversi moduli e funzioni che
sono già inclusi nel linguaggio sono consistenti, intuitivi, e
facili da imparare, e il design del linguaggio si basa sul
principio del least astonishment (cioè della “minor sorpresa”: il
comportamento del programma coincide con quanto ci si aspetta).
È ricco di librerie
Ogni installazione di Python include la standard library, cioè una
collezione di oltre 200 moduli per svolgere i compiti più
disparati, come ad esempio l’interazione con il sistema operativo
e il filesystem, o la gestione di diversi protocolli. Inoltre, il
Python Package Index consente di scaricare ed installare migliaia
di moduli aggiuntivi creati e mantenuti dalla comunità.

5
È performante
Anche se Python è considerato un linguaggio interpretato, i
programmi vengono automaticamente compilati in un formato
chiamato bytecode prima di essere eseguiti. Questo formato è più
compatto ed efficiente, e garantisce quindi prestazione elevate.
Inoltre, diverse strutture dati, funzioni, e moduli di Python sono
implementati internamente in C per essere ancora più performanti.
Gestisce automaticamente la memoria
Python è un linguaggio di alto livello che adotta un meccanismo
di garbage collection che si occupa automaticamente
dell’allocazione e del rilascio della memoria. Questo consente al
progammatore di usare variabili liberamente, senza doversi
preoccupare di dichiararle e di allocare e rilasciare spazi di
memoria manualmente (cosa che è invece necessaria in linguaggi di
più basso livello come il C o il C++).
È integrabile con altri linguaggi
Oltre all’interprete classico scritto in C (e chiamato CPython),
esistono anche altri interpreti che consentono l’integrazione con
diversi altri linguaggi. IronPython consente di utilizzare Python
all’interno del framework .NET, di usarne le sue funzioni, e di
interagire con altri linguaggi .NET. Per poter invece integrare
Python e Java è possibile utilizzare Jython. Esistono poi altri
interpreti, come PyPy: un’implementazione altamente performante
scritta in Python.
Cosa si può fare con Python
Come abbiamo accennato, la dotazione standard e le librerie di
terze parti completano Python con funzionalità che lo rendono uno
strumento duttile in svariati ambiti.
Programmazione GUI
Con Python è possibile scrivere interfacce grafiche (GUI) usando
tutti i maggiori toolkit:
 Tkinter: già incluso nella standard library e basato su Tcl/Tk.
 PyQt/PySide: permettono di utilizzare con Python il toolkit Qt
(sia la versione 4 che la 5), il framework multipiattaforma
storicamente realizzato da Nokia.
 PyGtk: basato sul popolare toolkit GTK.
 wxPython: un’interfaccia Python per il toolkit wxWidgets.
I programmi che usano questi toolkit sono in grado di essere
eseguiti su tutte le maggiori piattaforme (Linux, Windows, Mac).
Sviluppo Web
Esistono svariate possibilità per lo sviluppo Web sia ad alto che
a basso livello. Per realizzare siti ed applicazioni web sono
disponibili diversi web framework come:
 Django: uno dei framework web più popolari, che fornisce diversi
strumenti per realizzare siti e webapp.

6
 Flask: un “microframework” che permette si creare rapidamente
siti semplici.
 Web2py: un altro ottimo framework facile da usare.
Sono poi disponibili diversi altri web framework che permettono la
realizzazione di ogni tipologia di sito e webapp. Il sito
ufficiale di Python include un elenco di web framework (completi
di una breve descrizione) e una guida che spiega come usare Python
bel web.
La piattaforma Google App Engine permette di avviare le proprie
applicazioni Web nell’infrastruttura Google. App Engine ha un
ambiente runtime Python dedicato, che include l’interprete Python
e la libreria standard Python.
Se invece si vuole scendere più a basso livello esistono moduli
della standard library come socket, httplib, e urllib, ma anche
alcuni framework a supporto della programmazione di rete. Uno fra
tutti è Twisted: un potente network engine, event-driven – scritto
in Python – che supporta molti protocolli di rete inclusi SMTP,
POP3, IMAP, SSHv2 e DNS.
È possibile usare Python anche per accedere ai database. La
standard library include un’interfaccia per SQLite ed è anche
possibile installare moduli per interfacciarsi con altri database
(PostgreSQL, Oracle, MySQL, e altri).
Per la realizzazione di giochi, Pygame è un ottimo framework che
ne permette lo sviluppo in modo semplice e intuitivo.
Per realizzare applicazioni scientifiche, SciPy fornisce un
ecosistema di tool per la matematica, le scienze e l’ingegneria.
Chi usa Python
Oggi Python viene utilizzato in molte grandi realtà del mercato
informatico, tra cui:
 la NASA usa Python per lo sviluppo di sistemi di controllo;
 Yahoo! ha sviluppato in Python alcuni servizi di internet;
 Google, Youtube e RedHat usano Python.

7
Lezione 3di 40
Installare Python

In questa guida utilizzeremo Python 3. È possibile scaricare


liberamente l’ultima versione di Python 3 (3.5 al momento della
scrittura) dal sito ufficiale.
Installare Python su Windows
Nella pagina di download è possibile trovare il file .exe per le
versioni Windows a 32bit e 64bit. È sufficiente lanciarlo e
seguire i vari passaggi, ricordandosi di selezionare la voce Add
Python 3.X to PATH all’inizio dell’installazione. Dopo
l’installazione, per default Python verrà collocato
in C:\Python3X (ad esempio C:\Python35 per la versione 3.5). A
questo punto è già possibile utilizzarlo, tramite Start -> Tutti i
programmi -> Python3.5 -> Python.
Avendo selezionato Add Python 3.X to PATH durante l’installazione,
dovrebbe essere possibile avviare Python dal prompt dei comandi
semplicemente digitando il comando py o py -3.
Nel caso in cui, in fase di installazione, non avessimo
selezionato la suddetta opzione, è comunque possibile aggiungere
Python alla variabile d’ambiente PATH manualmente, seguendo i
seguenti passagi. Prima di tutto, andiamo su Pannello di controllo
-> Sistema -> Impostazioni di Sistema Avanzate -> Variabili
d’ambiente. Modifichiamo quindi la variabile PATH tra le variabili
di sistema aggiungendo C:\Python3X (dove la X corrisponde alla
versione installata).
Modifica della variabile Path

Per verificare che tutto è andato a buon fine, avviamo il prompt


dei comandi (Start -> Esegui -> cmd) e digitiamo:
echo %PATH%

Se l’output include la stringa C:\Python3X, sarà possibile avviare


Python digitando semplicemente py, py -3, o python:
Avvio di Python

8
Se Python è stato installato correttamente, sarà visualizzato un
breve messaggio che indica la versione dell’interprete di Python,
seguita dal prompt (>>>).
Sul sito ufficiale è inoltre disponibile una guida
all’installazione e all’uso di Python su Windows.
Installare Python su Linux
Nei sistemi Linux spesso Python è già presente nel setup di base.
È comunque possibile verificare se c’è e che versioni sono
presenti. Per farlo, digitiamo da shell quanto segue:
$ python

oppure:
$ python3

Se entrambi i comandi falliscono, Python non è installato.


Se python avvia Python 2 e il comando python3 non è presente, solo
Python 2 è installato. Se invece il
comando python o python3 avviano Python 3, allora Python 3 è
installato.
Nel caso in cui Python 3 non sia installato, sarà necessario
installarlo usando il gestore dei pacchetti per sistemi
Linux: apt per distribuzioni basate su Debian, rpm per
distribuzioni basate su Red Hat. In alternativa, è possibile
scaricare e compilare i sorgenti di Python manualmente.
Più in dettaglio, in base al sistema in uso è possibile avviare
uno dei seguenti comandi:

Comando Descrizione
$ yum Per distribuzioni basate su pacchetti rpm,
install come Red Hat, Fedora, Centos
9
python3
$ apt-get
install Per distribuzioni basate su Debian, come
python3 Ubuntu

Infine, vediamo come installare Python compilando i sorgenti.


Per prima cosa, dobbiamo verificare l’ultima versione dei sorgenti
dal sito, quindi lanciamo i seguenti comandi:
$ cd /tmp
$ wget http://www.python.org/ftp/python/3.x/python-3.x.tar.bz2

Così facendo, abbiamo scaricato un archivio .tar.bz2 contenente i


sorgenti Python. Estraiamone ora il contenuto:
$ tar -xjf python-3.x.tar.bz2
$ cd python-3.x

Non resta che avviare l’installazione:


$ ./configure
$ make
$ sudo make install

Nota: è possibile usare make altinstall invece di make install per


evitare che la versione che è appena stata installata sia
associata al comando python.
Installare Python su Mac
Come nei sistemi Linux, anche nei Mac dovremmo trovare Python già
pronto all’uso. Per verificarlo, ancora una volta basta avviare
una finestra di terminale e digitare python. Proabilmente verrà
mostrata la versione 2.7, ma noi vogliamo usare la 3.X. Quindi
bisognerà visita il sito e scaricare la versione per Mac OS più
adatta alle nostre esigenze. Infine, potremo procedere
all’installazione.
Al termine dell’installazione, lanciando python da terminale
apparirà ancora la vecchia versione. Pertanto, bisogna aprire il
terminale e digitare:
vim ~/.bash_profile

Una volta aperto il file, aggiungiamo la seguente riga:


alias python="python3"

Ora, provando di nuovo a lanciare python da terminale, di default


si avvierà la versione 3.X.

10
Lezione 4di 40

L’interprete Python e l’IDLE


Come abbiamo visto nelle lezioni precedenti, se lanciamo il
comando python da riga di comando, sia in ambiente Windows che
Linux, viene mostrato un nuovo prompt caratterizzato da 3
caratteri di maggiore (>>>), che da adesso chiameremo interprete
interattivo.
L’interprete interattivo è in grado di leggere e valutare man mano
le espressioni inserite dall’utente, ma è anche possibile eseguire
script contenenti sequenze di istruzioni Python, digitando il
comando:
python script.py

È interessante aprire una piccola parentesi su quello che succede


quando si esegue un script, scendendo un po’ più nel dettaglio.
Ogni volta che viene invocato il comando python, il codice scritto
viene scansionato per token, ognuno dei quali viene analizzato
dentro una struttura logica ad albero che rappresenta il
programma. Tale struttura viene, infine, trasformata
in bytecode (file con estensione .pyc o .pyo). Per potere eseguire
questi bytecode, si utilizza un apposito interprete noto
come macchina virtuale Python (PVM).
Sorgente (file.py) -> Bytecode (file.pyc) -> Runtime (PVM)

Esecuzione da riga di comando


Torniamo a concentrarci sul funzionamento base dell’interprete
interattivo. In questa modalità, digitando dei comandi Python si
ottiene subito una risposta:
11
>>> 5 * 3
15
>>>
>>> a = 5
>>> b = 6
>>> 2 * (a+b) + 3*a
37

Esecuzione di script .py


Ma come si fa a creare ed eseguire un file Python? Per prima cosa
bisogna generare un semplice file di testo e salvarlo con
estensione .py, per esempio, helloworld.py. A questo punto
possiamo aprire il file creato con un qualsiasi editor di testi
(non Word, bensì Notepad, Notepad++, Sublime Text o simili) e
scrivere:
print('Hello World!')

Si noti che, su Windows, possiamo creare un file di testo ma


potremmo non poterne modificare l’estensione. In questo caso,
possiamo creare il file direttamente da editor di testi con
l’estensione giusta, oppure cambiare l’impostazione del sistema
che nasconde le estensioni.
Come primo esempio, aggiungiamo al nostro file helloworld.py una
soola riga di codice, costituita dalla funzione print(), che come
risultato stamperà una stringa. Eseguendo il file da terminale,
otterremo quanto segue:
$ python helloworld.py
Hello World!

Usare IDLE
Ora che abbiamo visto brevemente come eseguire un file Python da
linea di comando, possiamo esaminare un ambiente visuale che
permette di modificare, eseguire, e fare il debug di programmi
Python da un’unica interfaccia: IDLE (Integrated Development
and Learning Environment).
IDLE offre un’interfaccia visuale per Python, e può essere
eseguito in ambiente Windows, Linux o Mac OS. Esso viene avviato
da uno script Python (idle.pyw), che nelle recenti versioni di
Windows si trova in C:\Python3X\Lib\idlelib.
È possibile però lanciarlo in modo veloce dal menu Start -> Tutti
i Programmi -> Python 3.X.

12
Avviando IDLE, apparirà una finestra con l’ormai noto prompt dei
comandi Python (>>>), e le voci di menu che si è abituati a vedere
in altre applicazioni simili.

Iniziamo a prendere confidenza con questo strumento.


Aprire ed eseguire un file
Per aprire un file Python già esistente possiamo selezionare File
-> Open (Ctrl+O), mentre per crearne uno nuovo possiamo
selezionare File -> New Window (Ctrl+N). In entrambe i casi, sarà
possibile eseguire lo script selezionando Run -> RunModule
(F5) dal menu:

Personalizzare l’IDE
È anche possibile personalizzare l’IDE secondo le proprie
esigenze. Per farlo, è sufficiente selezionare il menu Options ->
Configure IDLE:

13
Da questa schermata è possibile scegliere il tipo e la dimensione
del font, nonchè la larghezza dell’indentazione (di cui parleremo
meglio più avanti). È inoltre possibile personalizzare i colori
delle parole chiave e di diversi altri elementi, modificando le
impostazioni accessibili tramite la tab Highlighting.

14
Lezione 5di 40
Le funzioni print e input
In questa lezione facciamo la conoscenza di due funzioni basilari
di Python: print e input. Queste funzioni servono rispettivamente
a mostrare e a chiedere valori all’utente. Entrambe sono
particolarmente utili durante la creazione dei primi test e
programmi a riga di comando, ma lo saranno anche in futuro.
print
Abbiamo già visto, nelle passate lezioni, la funzione print, che
serve a “stampare” in output (tipicamente sullo schermo, ma
volendo anche su file o altri stream) il valore di una variabile o
di una espressione.

>>> a = 12
>>> b = 3
>>> print(a, b, a-b)
12 3 9
>>>
>>> c = 'Hello'
>>> print(c, 'World!')
Hello World!

Si può notare che la funzione print accetta in input un numero


variabile di argomenti (anche di tipi diversi), li converte in
stringhe, e li mostra in output separati da uno spazio e seguiti
da un carattere di ritorno a capo (\n). print consente inoltre di
specificare il separatore, il carattere finale, lo stream, e altri
parametri.
Quando si lavora dall’interprete interattivo non è necessario
utilizzare print per vedere il valore di una variabile o
espressione, ma è sufficiente digitarla:
>>> x = 10
>>> x
10
>>> x * 2
20

Questa possibilità è disponibile solo dall’interprete interattivo;


quando si esegue un programma, invece, è necessario
utilizzare print esplicitamente.
Nota: in Python 2.x, print era uno statement, e non era quindi
necessario inserire i parametri tra parentesi. Omettere le
parentesi in Python 3.x (dove print è una funzione) significa
commettere un errore di sintassi. In Python 2.x è inoltre
possibile usare from __future__ import print_function per
rendere print una funzione come in Python 3.
input

15
La funzione input viene usata per consentire all’utente di
immettere dati da tastiera, che verranno poi utilizzati dal
programma.
input accetta un singolo argomento opzionale: una stringa che
viene mostrata a video prima di leggere il valore digitato. Una
volta che l’utente ha digitato un valore e premuto il
tasto Invio, input restituisce il valore come stringa, come mostra
il seguente esempio:
>>> nome = input('Inserisci il tuo nome: ')
Inserisci il tuo nome: Ezio
>>> nome
'Ezio'

In questo esempio possiamo vedere che:


1. la stringa passata a input che chiede di inserire il nome viene
mostrata a video;
2. input attende che l’utente digiti il nome (Ezio) e prema Invio;
3. input restituisce il nome inserito come stringa;
4. la stringa restituita da input viene assegnata alla
variabile nome;
5. la variabile nome può poi essere utilizzata per accedere al nome
inserito dall’utente.
Se si desidera immettere valori numerici o di altri tipi, è
possibile convertire la stringa restituita da input usando
funzioni come int o float:
>>> raggio = input('Inserisci il raggio: ')
Inserisci il raggio: 8.4
>>> r = float(raggio)
>>> raggio, r
('8.4', 8.4)
>>> print('Area:', 3.14 * r**2)
Area: 221.5584
>>> print('Circonferenza:', 2 * 3.14 * r)
Circonferenza: 52.752

In questo esempio possiamo vedere che:


1. input chiede all’utente di inserire un raggio che viene
assegnato alla variabile raggio;
2. la funzione float viene usata per convertire raggio (che è una
stringa) in un valore numerico con la virgola che viene
assegnato alla variabile r;
3. le due variabili (raggio e r) vengono stampate per mostrare che
la prima è una stringa (quindi rappresentata tra '...') mentre
la seconda è un numero;
4. r viene poi usata per calcolare l’area del cerchio (** è
l’operatore di elevazione a potenza) e la funzione print viene
usata per stampare la stringa 'Area:' e il risultato
dell’espressione 3.14 * r**2;

16
5. r viene usata anche per calcolare la circonferenza e print per
stampare la stringa 'Circonferenza:' e il risultato
dell’espressione 2 * 3.14 * r;
Nota: in Python 2.x esistevano due diverse funzioni di
input: raw_input e input. La prima è equivalente a input di Python
3.x, mentre la seconda (che effettuava uno step aggiuntivo di
conversione implicita dell’input) è stata rimossa da Python 3.x.
Come abbiamo visto nel secondo esempio, in Python 3.x è possibile
convertire i valori letti da input esplicitamente.

Lezione 6di 40
Indentazione

In questa lezione vedremo più in dettaglio uno degli aspetti


fondamentali della programmazione con Python: l’indentazione.
Indentazione e blocchi di codice
A differenza di altri linguaggi che delimitano blocchi di codice
con parentesi grafe (come C, C++ e Java) o con parole riservate
come begin/end, Python usa l’indentazione. Indentare il codice è
una pratica comune in tutti i linguaggi, perché semplifica la
lettura del codice e la compresione della sua struttura. Anzichè
usare due meccanismi separati per compilatori/interpreti
(parentesi o keyword) e persone (indentazione), per Python si è
scelto di usare l’indentazione per entrambi. Questa scelta ha
diversi aspetti positivi, tra cui:
 il linguaggio risulta più chiaro e leggibile;
 la struttura del programma coincide sempre con quella
dell’indentazione;
 lo stile di indentazione è necessariamente uniforme in qualsiasi
listato.
Questo significa che, in Python, l’indentazione è significativa, e
che indentare in modo incorretto può portare a comportamenti
sbagliati del programma o a errori.
Vediamo un semplice esempio. Di seguito usiamo il costrutto if,
che approfondiremo in una lezione successiva.
17
print("eseguito sempre all'inizio")
if condizione:
print('eseguito in mezzo solo se la condizione è vera')
print('eseguito in mezzo solo se la condizione è vera')
print('eseguito in mezzo solo se la condizione è vera')
print('eseguito sempre alla fine')

In questo esempio possiamo vedere che:


1. il primo print, l’if, e l’ultimo print hanno lo stesso livello
di indentazione, e vengono eseguiti sempre;
2. dopo l’if c’è un blocco di codice con un livello di indentazione
maggiore, che include tre print;
3. se la condizione dell’if è vera, i tre print vengono eseguiti;
4. se la condizione dell’if è falsa, i tre print non vengono
eseguiti.
È quindi molto importante fare attenzione all’indentazione, perché
viene sempre valutata nel processo di parsing del programma. Di
seguito è mostrato un esempio di errore dovuto ad una indentazione
scorretta:

Convenzioni
Per avere codice più consistente, la Python Software Foundation ha
reso disponibile un documento chiamato PEP 8 (acronimo che sta
per Python Enhancement Proposal), che raccoglie diverse
convenzioni e linee guida.
La PEP 8 include anche una sezione sull’indentazione, che
suggerisce tra le altre cose di:
 usare sempre 4 spazi per livello di indentazione;
 evitare l’uso dei caratteri di tabulazione;
 non mischiare mai l’uso di tabulazioni e spazi.
Anche se la PEP 8 suggerisce di seguire queste convenzioni, è
possibile (sebbene fortemente sconsigliato) usare tab o un numero
18
diverso di spazi. Fintanto che i livelli di indentazione sono
consistenti, il programma comunque funzionerà.
Ci sono tuttavia alcune eccezioni. Se state lavorando su codice
che usa una convenzione diversa, è meglio adeguarsi ad essa o, in
alternativa, adattare tutto il codice esistente alla PEP 8.
Inoltre, è comunque possibile (e talvolta consigliato) usare più o
meno spazi per allineare elementi all’interno di una lista, o
argomenti all’interno di una chiamata a funzione:
lista = [0, 1, 2, 3, 4,
5, 6, 7, 8, 9]

funzione(arg1, arg2,
arg3, arg4)

Lezione 7di 40

Variabili e commenti in Python


Le Variabili
Per definire variabili in Python, è sufficiente utilizzare
l’operatore di assegnamento (=) come nei seguenti esempi:
numero = 10
stringa = "Python"
lista = [1, 2, 3]

Come si vede, in Python non è necessario né definire le variabili


prima di utilizzarle, né specificare il loro tipo.
Differenze con altri linguaggi
In linguaggi come C, le variabili fanno riferimento a specifiche
locazioni di memoria che hanno una dimensione fissa che dipende
dal loro tipo (per questo è necessario specificare il tipo quando
si dichiara una variabile). In Python invece, gli oggetti hanno un
tipo specifico (numero, stringa, lista, etc.), e le variabili sono
solo “etichette” che si si riferiscono a un determinato oggetto.
Per capire meglio questo fondamentale concetto, possiamo vedere un
semplice esempio.

19
/* Codice C */
int x;
x = 10;
x = 20;

In questo codice C:
 viene definita una variabile x di tipo int (cioè grande
abbastanza per contenere un numero intero);
 il valore 10 viene salvato nella locazione di memoria associata
a x;
 il valore 20 viene salvato nella locazione di memoria associata
a x, sovrascrivendo il 10.
# codice Python
x = 10
x = 20

Anche se questo codice Python può sembrare molto simile, quello


che accade è diverso:
 l’oggetto 10 viene creato, e l’operazione di assegnamento fa in
modo che la variabile x si riferisca a questo oggetto;
 l’oggetto 20 viene creato, e la variabile/etichetta x viene
“spostata” da 10 a 20;
 siccome non ci sono più variabili che fanno riferimento
all’oggetto 10, il garbage collector lo eliminirà
automaticamente.
In entrambi questi esempi, il valore finale associato con la
variabile x sarà 20.
Come abbiamo detto precedentemente, in Python le variabili non
hanno tipo e possono riferirsi a qualsiasi tipo di oggetto. Il
codice seguente è quindi perfettamente valido in Python:
x = 10
x = "Python"
x = [1, 2, 3]

In questo esempio la variabile/etichetta x viene “spostata”


dall’intero 10 alla stringa "Python", e infine alla lista [1, 2,
3].
Tipi di dato
Python possiede sia i classici tipi di dato, comuni alla maggior
parte dei linguaggio di programmazione, ma anche diversi tipi più
potenti e flessibili. Ecco una tabella che elenca alcuni dei tipi
di dati più comuni in Python:
Tipo di
dato Nome Descrizione Esempi

Intero int Intero di -


dimensione 42, 0, 1200, 99999999999999999
20
arbitraria 9

Numero a
virgola
Reale float mobile 3.14, 1.23e-10, 4.0E210

Per valori
Booleano bool veri o falsi True, False

Numeri
complessi
con parte
Compless reale e
o complex immaginaria 3+4j, 5.0+4.1j, 3j

Usata per
rappresentar
Stringhe str e testo '', 'stefano', "l'acqua"

Usata per
rappresentar b'', b'\x00\x01\x02', b'Python
Bytes bytes e bytes '

Una sequenza
mutabile di [], [1, 2, 3], ['Hello',
Liste list oggetti 'World']

Una sequenza
immutabile
Tuple tuple di oggetti (), (1, 2, 3), ('Python', 3)

Un’insieme
set/frozense di oggetti
Insiemi t unici {1, 2, 3}, {'World', 'Hello'}

Una
struttura
che associa
Dizionar chiavi a {}, {'nome': 'Ezio',
i dict valori 'cognome': 'Melotti'}

Nelle prossime lezioni affronteremo più in dettaglio questi tipi


di dato e vedremo le loro caratteristiche.
Nomi di variabili
Ricordiamo che le regole da seguire nella scelta dei nomi delle
variabili è simile a quella dei più comuni linguaggi di
programmazione, in particolare:

21
 ogni nome di variabile deve iniziare con una lettera o con il
carattere underscore (_), e può essere seguita da lettere,
numeri, o underscore;
 esistono delle parole riservate (keyword) che non possono essere
utilizzate come nomi di
variabili: False, None, True, and, as, assert, break, class, con
tinue, def, del, elif, else, except, finally, for, from, global,
if, import, in, is, lambda, nonlocal, not, or, pass, raise, ret
urn, try, while, with, yield;
 Python è un linguaggio case-sensitive, che distingue tra nomi di
variabili composti da caratteri minuscoli e maiuscoli;
 in Python 3 è possibile (ma generamente sconsigliato) usare
caratteri accentati o in altri alfabeti nei nomi delle
variabili, ad esempio: età = 20.

La PEP 8 include una sezione riguardante le convenzioni da usare
con i nomi delle variabili.

Assegnamento multiplo
Una singolare possibilità offerta da Python è rappresentata
dall’assegnamento multiplo, che permette di inizializzare più
variabili direttamente sulla stessa riga di codice. Per capire
quest’ultimo concetto, basta osservare l’esempio seguente:
>>> a, b, c = 2, 3, 5 # assegnamento multiplo
>>> a * b + c
11
>>> a
2
>>> b
3
>>> c
5

I commenti
In Python è possibile usare il carattere # per aggiungere commenti
al codice. Ogni riga di commento deve essere preceduta da un #, ed
è anche possibile aggiungere commenti in seguito a istruzioni:
# Questo commento occupa diverse righe, e ogni riga
# è preceduta da un # per indicare a Python di
# ignorarle tutte
a = 3 # questo commento segue un'istruzione

Python ignorerà automaticamente tutto ciò che segue il


carattere # fino al termine della riga.
La PEP 8 include anche una sezione riguardante i commenti.

22
lezione 8di 40

Numeri e operatori logici in Python


Come abbiamo visto nella lezione precedente, in Python ci sono 4
tipi di dati numerici: interi (int), razionali (float), complessi
(complex), e booleani (bool).
Abbiamo anche visto che è possibile usare l’interprete interattivo
come una calcolatrice, e come convertire diversi tipi di dato. È
inoltre possibile definire numeri interi usando la notazione
binaria, ottale, ed esadecimale:
>>> 6 + 4*3
18
>>> 9 / 2 # la divisione tra due numeri interi restituisce un float
4.5
>>> 2j * 2j # moltiplicazione tra numeri complessi
(-4+0j)
>>> int(10.6) # conversione esplicita dal tipo float al tipo int
10
>>> float(20) # conversione esplicita dal tipo int al tipo float
20.0
>>> 0b11111111 # intero in notazione binaria (prefisso 0b)
255
>>> 0o377 # intero in notazione ottale (prefisso 0o)
255
>>> 0xFF # intero in notazione esadecimale (prefisso 0x)
255

Nota: In Python 2 esistevano due tipi di intero: int e long. In


Python 3 questi due tipi sono stati unificati e il tipo int di
Python 3 è in grado di rappresentare numeri interi a grandezza
arbitraria (così come il tipo long di Python 2).
Operatori Aritmetici
Le operazioni sui tipi numerici sono le più classiche:

Operatore Descrizione Esempi


+ addizione 10 + 12 → 22
– sottrazione 5 - 1 → 4
* moltiplicazione 10 * 12 → 120
/ divisione 9 / 4 → 2.25
divisione
// intera 9 // 4 → 2
23
modulo (resto
della
% divisione) 9 % 4 → 1

Nota: In Python 2 la divisione tra due int ritornava un int, in


Python 3 ritorna un float. In Python 2 è possibile ottenere lo
stesso comportamento di Python 3 aggiungendo all’inizio del
programma la riga from __future__ import division.
Operatori di Confronto
Python supporta anche operatori di confronto, che
restituiscono True o False:

Operatore Descrizione Esempi


8 == 8 → True
== uguale a 3 == 5 → False
3 != 5 → True
!= diverso da 8 != 8 → False
3 < 5 → True
< minore di 5 < 3 → False
minore o 3 <= 5 → True
<= uguale a 8 <= 8 → True
5 > 3 → True
> maggiore di 3 > 5 → False
maggiore o 5 >= 3 → True
>= uguale a 8 >= 8 → True

Operatori Booleani
In Python esistono anche gli operatori booleani and, or, e not:

Operatore Descrizione
Ritorna True se entrambi gli operandi sono veri,
and altrimenti False
Ritorna True se almeno uno degli operandi è vero,
or altrimenti False
Ritorna False se l’operando è vero, True se l’operando
not è falso

In Python ogni oggetto è o vero (numeri diversi da 0, la


costante True, o contenitori che contengono almeno un elemento)
o falso (ovvero il numero 0, le costanti False e None, contenitori
vuoti). È possibile verificare se un oggetto
è vero o falso usando bool(oggetto).
Così come avviene in altri linguaggi di programmazione, anche in
Python gli operatori and e or funzionano in modo che, se il primo
operando è sufficiente a determinare il risultato, il secondo non
viene valutato. Inoltre, entrambi gli operatori ritornano sempre
uno dei due operandi.

24
Operatori Binari
Esistono poi gli operatori binari (o bitwise) che permettono di
lavorare al livello dei singoli bit e sono utili in particolari
circostanze:

Operatore Descrizione
x << n esegue uno shift a sinistra di n posizioni dei bit di x
x >> n esegue uno shift a destra di n posizioni dei bit di x
x & y esegue un and tra i bit di x e di y
x | y esegue un or tra i bit di x e di y
x ^ y esegue un or esclusivo tra i bit di x e di y
~x inverte i bit di x

Lezione 9di 40

Stringhe in Python

In questa lezione ci occupiamo di come trattare le stringhe in


Python. Abbiamo già visto che per dichiarare una stringa è
sufficiente assengare ad una nuova variabile un testo racchiuso
tra virgolette: è possibile racchiudere il suo valore
indifferentemente tra apici (carattere ') o doppi apici
(carattere ").
Questo permette di superare facilmente il problema dell’utilizzo
dei suddetti caratteri nel valore stesso della stringa, ad
esempio "l'acqua" o 'Egli disse: "Acqua"'. È anche possibile usare
il carattere di escape \ prima di ' o ": 'Egli disse: "L\'acqua"'.
Le stringhe, così come le liste o le tuple, sono un tipo
particolare di sequenze e perciò supportano tutte le operazioni
comuni alle sequenze. Vediamone alcune.
Indexing e Slicing
In Python, è possibile accedere agli elementi di una sequenza,
usando la sintassi sequenza[indice]. Questo restituirà l’elemento
in posizione indice (il primo elemento ha sempre indice 0). È
inoltre possibile specificare indici negativi che partono dalla
fine della sequenza (l’ultimo elemento ha indice -1, il
penultimo -2, ecc.). Questa operazione è chiamata indexing.
>>> s = 'Python'
>>> s[0] # elemento in posizione 0 (il primo)
'P'

25
>>> s[5] # elemento in posizione 5 (il sesto)
'n'
>>> s[-1] # elemento in posizione -1 (l'ultimo)
'n'
>>> s[-4] # elemento in posizione -4 (il quartultimo)
't'

La sintassi sequenza[inizio:fine] ci permette di ottenere una


nuova sequenza dello stesso tipo che include tutti gli elementi
partendo dall’indice inizio (incluso) all’indice fine (escluso).
Se inizio è omesso, gli elementi verranno presi dall’inizio,
se fine è omesso, gli elementi verranno presi fino alla fine.
Questa operazione è chiamata slicing (letteralmente “affettare”).
>>> s = 'Python'
>>> s[0:2] # sottostringa con elementi da 0 (incluso) a 2
(escluso)
'Py'
>>> s[:2] # dall'inizio all'elemento con indice 2 (escluso)
'Py'
>>> s[3:5] # dall'elemento con indice 3 (incluso) a 5 (escluso)
'ho'
>>> s[4:] # dall'elemento con indice 4 (incluso) alla fine
'on'
>>> s[-2:] # dall'elemento con indice -2 (incluso) alla fine
'on'
Contenimento
Gli operatori in e not in possono essere usati per verificare se
un elemento fa parte di una sequenza o no. Nel caso delle
stringhe, è anche possibile verificare se una sottostringa è
contenuta in una stringa:
>>> s = 'Python'
>>> 'P' in s # controlla se il carattere 'P' è contenuto nella
stringa s
True
>>> 'x' in s # il carattere 'x' non è in s, quindi ritorna False
False
>>> 'x' not in s # "not in" esegue l'operazione inversa
True
>>> 'Py' in s # controlla se la sottostringa 'Py' è contenuto
nella stringa s
True
>>> 'py' in s # il controllo è case-sensitive, quindi ritorna
False
False
Concatenamento, ripetizione e lunghezza
È possibile usare l’operatore + per concatenare sequenze, e * per
ripetere sequenze:
>>> 'Py' + 'thon'
'Python'
>>> 'Py' * 2
26
'PyPy'
>>> 'Ba' + 'na' * 2
'Banana'

La funzione built-in len() può essere usata per ottenere il numero


di elementi in una sequenza:
>>> len('Python')
6
>>> s = 'Precipitevolissimevolmente'
>>> len(s)
26
Funzioni e Metodi
Prima di procedere, è necessario fare una breve parentesi per
spiegare la differenza tra funzioni e metodi.
Abbiamo già visto alcune funzioni come len(). La funzioni
accettano 0 o più argomenti e possono essere usate con oggetti di
diversi tipi, usando la sintassi funzione(argomenti):
>>> len('Python') # lunghezza di una stringa
6
>>> len(['PyPy', 'Jython', 'IronPython']) # di una lista
3
>>> len({'a': 3, 'b': 5}) # di un dizionario
2
Copy
I metodi sono simili alle funzioni ma sono legati al tipo
dell’oggetto e hanno una sintassi
diversa: oggetto.metodo(argomenti). Così come le funzioni, i
metodi possono accettare 0 o più argomenti:
>>> s = 'Python'
>>> s.upper() # il metodo upper ritorna una nuova stringa tutta
uppercase
'PYTHON'
>>> s.lower() # il metodo lower ritorna una nuova stringa tutta
lowercase
'python'

In questo esempio potete vedere due metodi forniti dal tipo str,
che non sono disponibili per altri tipi.
help() e dir()
Due strumenti particolarmente utili per lavorare con funzioni e
metodi sono le funzioni built-
in help() e dir(). help(oggetto) restituisce una breve spiegazione
riguardo all’oggetto passato come
argomento. dir(oggetto) restituisce una lista di metodi e
attributi dell’oggetto:
>>> len # len è una funzione built-in
<built-in function len>
>>> help(len) # si può passare len a help() per vedere una breve
spiegazione
27
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
>>> dir(str) # restituisce una lista di metodi e attributi di str
[..., 'capitalize', 'casefold', 'center', 'count', 'encode',
'endswith', 'expandtabs',
'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha',
'isdecimal', 'isdigit',
'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace',
'istitle', 'isupper',
'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition',
'replace', 'rfind',
'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split',
'splitlines',
'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper',
'zfill']
>>> str.upper # upper è un metodo di str
<method 'upper' of 'str' objects>
>>> help(str.upper) # si può passare a help() per vedere una
breve spiegazione
Help on method_descriptor:
upper(...)
S.upper() -> str
Return a copy of S converted to uppercase.

Questi strumenti vi consentono di esplorare interattivamente


funzioni e metodi e capirne il funzionamento:
>>> help(str.replace) # visualizza l'help per il metodo
str.replace
Help on method_descriptor:
replace(...)
S.replace(old, new[, count]) -> str
Return a copy of S with all occurrences of substring
old replaced by new. If the optional argument count is
given, only the first count occurrences are replaced.
>>> 'Python'.replace('thon', 'Py') # sostituisce 'thon' con 'Py'
in 'Python'
'PyPy'
>>> s = 'Python, Python, Python!'
>>> s.replace('thon', 'Py', 2) # come sopra, ma massimo 2
sostituzioni
'PyPy, PyPy, Python!'

La documentazione ufficiale di Python include una sezione sui


metodi delle stringhe.
Formattazione delle Stringhe
La maggior parte dei metodi delle stringhe sono semplici da
capire. C’è tuttavia un metodo che richiede una spiegazione più
dettagliata: str.format(). Questo metodo permette

28
di formattare stringhe, ovvero inserire alcuni valori variabili
all’interno di una stringa predefinita:
>>> raggio = 8.4
>>> area = 3.14 * raggio**2
>>> circ = 2 * 3.14 * raggio
>>> s = "L'area è {}, la circonferenza è {}."
>>> s.format(area, circ)
"L'area è 221.5584, la circonferenza è 52.752."

In questo esempio possiamo vedere che:


 le variabili area e circ vengono definite a partire
dal raggio di un cerchio;
 la stringa s viene definita, e contiene
due placeholder (“segnaposto”): {};
 il metodo format viene chiamato sulla stringa s e l’area e la
circonferenza vengono passati come argomenti;
 il metodo format sostituisce i due placeholder con il valore dei
due argomenti che ha ricevuto e ritorna una nuova stringa;
Oltre ad occuparsi di convertire gli argomenti in stringa e
sostituirli ai placeholder, str.format() consente di controllare
l’output specificando determinate opzioni (come numero di
decimali, allineamento, ecc.) tra le {}. La documentazione
ufficiale include una sezione con numerosi esempi di utilizzo
di str.format().

Formattazione “vecchio stile”


Prima dell’implementazione di str.format() veniva usato un altro
metodo ispirato alla formattazione eseguita dalla
funzione printf di C. Questo metodo usa l’operatore % per separare
la stringa di formattazione dagli argomenti, e, anche se il
funzionamento è simile a str.format(), questo metodo è meno
flessibile.
>>> raggio = 8.4
>>> area = 3.14 * raggio**2
>>> circ = 2 * 3.14 * raggio
>>> s = "L'area è %f, la circonferenza è %f."
>>> s % (area, circ)
"L'area è 221.558400, la circonferenza è 52.752000."

Potete notare che questo metodo usa %f come placeholder invece


di {}. Oltre a %f (per i float), esistono anche altri tipi
di placeholder, come %d per gli interi o %s per le stringhe. La
documentazione ufficiale include una sezione che spiega i vari
tipi di placeholder e opzioni aggiuntive.

29
Lezione 10di 40
Tuple

Python fornisce un tipo built-in chiamato tupla, che viene


solitamente usato per rappresentare una sequenza immutabile di
oggetti, in genere eterogenei.
Definire le tuple
L’operatore che definisce le tuple è la virgola (,), anche se per
evitare ambiguità la sequenze di elementi vengono spesso racchiuse
tra parentesi tonde. In alcune espressioni, dove le virgole hanno
già un significato diverso (ad esempio separare gli argomenti di
una funzione), le parentesi sono necessarie.
>>> t = 'abc', 123, 45.67 # la virgola crea la tupla
>>> t # la rappresentazione di una tupla include sempre le ()
('abc', 123, 45.67)
>>> type(t)
<class 'tuple'>
>>> tp = ('abc', 123, 45.67) # le () evitano ambiguità
>>> t == tp # il risultato è equivalente
True
>>> len((1, 'a', 2.3)) # in questo caso le () sono necessarie
3
>>> len(1, 'a', 2.3) # perché qua la , separa gli argomenti
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: len() takes exactly one argument (3 given)

Per creare una tupla di un elemento, è comunque necessario l’uso


della virgola, mentre per creare una tupla vuota bastano le
parentesi tonde:
>>> t = 'abc', # tupla di un solo elemento
>>> t
('abc',)
>>> tv = () # tupla vuota, senza elementi
>>> tv
()
>>> type(tv) # verifichiamo che sia una tupla
<class 'tuple'>
>>> len(tv) # verifichiamo che abbia 0 elementi
0

Usare le tuple
Le tuple sono un tipo di sequenza (come le strighe), e supportano
le operazioni comuni a tutte le sequenze, come indexing, slicing,
contenimento, concatenazione, e ripetizione:
>>> t = ('abc', 123, 45.67)
>>> t[0] # le tuple supportano indexing
30
'abc'
>>> t[:2] # slicing
('abc', 123)
>>> 123 in t # gli operatori di contenimento "in" e "not in"
True
>>> t + ('xyz', 890) # concatenazione (ritorna una nuova tupla)
('abc', 123, 45.67, 'xyz', 890)
>>> t * 2 # ripetizione (ritorna una nuova tupla)
('abc', 123, 45.67, 'abc', 123, 45.67)

Le tuple sono immutabili, quindi una volta create non è possibile


aggiungere, rimuovere, o modificare gli elementi:
>>> t[0] = 'xyz' # non è possibile modificare gli elementi
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

È anche possibile usare funzioni e metodi comuni a tutte le


sequenze: len() per contare gli elementi, min() e max() per
trovare l’elemento più piccolo/grande (a patto che i tipi degli
elementi siano comparabili), .index() per trovare l’indice di un
elemento, e .count() per contare quante volte un elemento è
presente nella tupla:
>>> len(('abc', 123, 45.67, 'xyz', 890)) # numero di elementi
5
>>> min((4, 1, 7, 5)) # elemento più piccolo
1
>>> max((4, 1, 7, 5)) # elemento più grande
7
>>> t = ('a', 'b', 'c', 'b', 'a')
>>> t.index('c') # indice dell'elemento 'c'
2
>>> t.count('c') # numero di occorrenze di 'c'
1
>>> t.count('b') # numero di occorrenze di 'b'
2

31
Lezione 11di 40
Liste in Python

Python fornisce anche un tipo built-in chiamato lista, che viene


solitamente usato per rappresentare una sequenza mutabile di
oggetti, in genere omogenei.
Definire le liste
Le liste vengono definite elencando tra parentesi quadre ([]) una
serie di oggetti separati da virgole (,). È possibile creare una
lista vuota usando le parentesi quadre senza nessun elemento
all’interno.
>>> nums = [0, 1, 2, 3] # nuova lista di 4 elementi
>>> nums
[0, 1, 2, 3]
>>> type(nums) # verifichiamo che il tipo sia "list"
<class 'list'>
>>> empty = [] # nuova lista vuota
>>> empty
[]
>>> one = ['Python'] # nuova lista con un elemento
>>> one
['Python']
Usare le liste
Così come le tuple e le stringhe, anche le liste sono un tipo
di sequenza, e supportano quindi le operazioni comuni a tutte le
sequenze, come indexing, slicing, contenimento, concatenazione, e
ripetizione:
>>> letters = ['a', 'b', 'c', 'd', 'e']
>>> letters[0] # le liste supportano indexing
'a'
>>> letters[-1]
'e'
>>> letters[1:4] # slicing
['b', 'c', 'd']
>>> 'a' in letters # gli operatori di contenimento "in" e "not in"
True
>>> letters + ['f', 'g', 'h'] # concatenazione (ritorna una nuova
lista)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> [1, 2, 3] * 3 # ripetizione (ritorna una nuova lista)
[1, 2, 3, 1, 2, 3, 1, 2, 3]

Le liste supportano anche funzioni e metodi comuni alle altre


sequenze: len() per contare gli elementi, min() e max() per
trovare l’elemento più piccolo/grande (a patto che i tipi degli
elementi siano comparabili), .index() per trovare l’indice di un
elemento, e .count() per contare quante volte un elemento è
presente nella lista:
32
>>> letters = ['a', 'b', 'c', 'b', 'a']
>>> len(letters) # numero di elementi
5
>>> min(letters) # elemento più piccolo (alfabeticamente nel caso
di stringhe)
'a'
>>> max(letters) # elemento più grande
'c'
>>> letters.index('c') # indice dell'elemento 'c'
2
>>> letters.count('c') # numero di occorrenze di 'c'
1
>>> letters.count('b') # numero di occorrenze di 'b'
2

A differenza di tuple e stringhe che sono immutabili, le liste


possono essere mutate. È quindi possibile assegnare un nuovo
valore agli elementi, rimuovere elementi usando la keyword del, o
cambiare gli elementi usando uno dei metodi aggiuntivi delle
liste:

 lista.append(elem): aggiunge elem alla fine della lista;


 lista.extend(seq): estende la lista aggiungendo alla fine gli
elementi di seq;
 lista.insert(indice, elem): aggiunge elem alla lista in
posizione indice;
 lista.pop(): rimuove e restituisce l’ultimo elemento
della lista;
 lista.remove(elem): trova e rimuove elem dalla lista;
 lista.sort(): ordina gli elementi della lista dal più piccolo al
più grande;
 lista.reverse(): inverte l’ordine degli elementi della lista;
 lista.copy(): crea e restituisce una copia della lista;
 lista.clear(): rimuove tutti gli elementi della lista;
Vediamo alcuni esempi:
>>> letters = ['a', 'b', 'c']
>>> letters.append('d') # aggiunge 'd' alla fine
>>> letters
['a', 'b', 'c', 'd']
>>> letters.extend(['e', 'f']) # aggiunge 'e' e 'f' alla fine
>>> letters
['a', 'b', 'c', 'd', 'e', 'f']
>>> letters.append(['e', 'f']) # aggiunge la lista come elemento
alla fine
>>> letters
['a', 'b', 'c', 'd', 'e', 'f', ['e', 'f']]
>>> letters.pop() # rimuove e ritorna l'ultimo elemento (la lista)
['e', 'f']
>>> letters.pop() # rimuove e ritorna l'ultimo elemento ('f')
'f'
33
>>> letters.pop(0) # rimuove e ritorna l'elemento in posizione 0
('a')
'a'
>>> letters.remove('d') # rimuove l'elemento 'd'
>>> letters
['b', 'c', 'e']
>>> letters.reverse() # inverte l'ordine "sul posto" e non ritorna
niente
>>> letters # gli elementi sono ora in ordine inverso
['e', 'c', 'b']
>>> letters[1] = 'x' # sostituisce l'elemento in posizione 1 ('c')
con 'x'
>>> letters
['e', 'x', 'b']
>>> del letters[1] # rimuove l'elemento in posizione 1 ('x')
>>> letters
['e', 'b']
>>> letters.clear() # rimuove tutti gli elementi rimasti
>>> letters
[]

Alcuni di questi metodi accettano anche argomenti aggiuntivi che


potrete vedere usando help(list.metodo) o consultando
la documentazione ufficiale sulle liste.
Differenze tra tuple e liste
Anche se a prima vista le liste possono sembrare delle “tuple
mutabili”, in realtà vengono usate in situazioni diverse.
Riassumiamo le differenze tra tuple e liste:

tuple liste
Mutabilità immutabili mutabili
Lunghezza fissa variabile
Accesso agli elementi
avviene tramite indexing iterazione
Di solito contengono oggetti eterogenei omogenei
Simile in C al tipo di dati struct array
Dato che le tuple sono immutabili, la loro lunghezza è fissa, e in
genere ogni elemento della tupla ha un ruolo preciso. Questi
elementi, che possono essere di tipi diversi, in genere vengono
letti tramite indexing (tupla[indice]).
Le liste, invece, sono mutabili, quindi la loro lunghezza è
variabile (elementi possono essere aggiunti o rimossi). Questo
significa che gli elementi singoli non hanno un ruolo preciso e
vengono in genere letti mediante iterazione (ad esempio usando
un for), e perciò devono anche essere dello stesso tipo.
Un altro modo per capire la differenza, è pensare a una tabella di
un database, come ad esempio:

34
Nome Cognome Età
John Smith 20
Jane Johnson 30
Jack Williams 28
Mary Jones 25
Mark Brown 23
Ogni riga della tabella può essere rappresentata con una tupla
(es. ('John', 'Smith', 20)) di 3 elementi (lunghezza fissa)
eterogenei (stringhe e interi) a cui possiamo accedere tramite
indexing (es. t[1] per il cognome). Ogni colonna può essere
rappresentata con una lista (es. ['John', 'Jane', 'Jack', ...]])
di lunghezza variabile (persone possono essere aggiunte o tolte) e
elementi omogenei (la colonna del nome ha solo stringhe, quella
dell’età solo numeri) che vengono accessi tramite iterazione (es.
stampare la lista di cognomi usando un loop).
È inoltre possibile combinare i due tipi e rappresentare l’intera
tabella come una lista di tuple:
[('John', 'Smith', 20),
('Jane', 'Johnson', 30),
('Jack', 'Williams', 28),
...]

Lezione 12di 40
Dizionari

I dizionari (dict) sono un tipo built-in, mutabile e non ordinato


che contiene elementi (items) formati da una chiave (key) e un
valore (value). Una volta che il dizionario è creato e valorizzato
con un insieme di coppie <chiave, valore>, si può usare la chiave
(che deve essere univoca) per ottenere il valore corrispondente.
I dizionari sono implementati usando delle tabelle hash che
consentono di ottenere il valore corrispondente alla chiave in
modo molto efficiente, indipendentemente dal numero di elementi
nel dizionario. La maggior parte dei linguaggi contiene una

35
struttura dati simile, anche se chiamata in modo diverso: il tipo
hash in Perl, Hashtable in Java o C#, le mappe MFC per Visual C++,
gli array associativi in PHP, eccetera.

Definire i dizionari
I dizionari vengono definiti elencando tra parentesi graffe ({})
una serie di elementi separati da virgole (,), dove ogni elemento
è formato da una chiave e un valore separati dai due punti (:). È
possibile creare un dizionario vuoto usando le parentesi graffe
senza nessun elemento all’interno.
>>> d = {'a': 1, 'b': 2, 'c': 3} # nuovo dizionario di 3 elementi
>>> d
{'c': 3, 'a': 1, 'b': 2}

In questo esempio possiamo vedere che d è un dizionario che


contiene 3 elementi formati da una chiave e un
valore. 'a', 'b' e 'c' sono le chiavi, mentre 1, 2 e 3 sono i
valori. Possiamo anche notare come l’ordine degli elementi sia
arbitrario, dato che i dizionari non sono ordinati.
>>> d = {'a': 1} # dizionario di un elemento
>>> d
{'a': 1}
>>> type(d) # verifichiamo che il tipo sia "dict"
<class 'dict'>
>>> d = {} # dizionario vuoto
>>> d
{}

Le chiavi di un dizionario sono solitamente stringhe, ma è


possibile usare anche altri tipi, a patto che
siano “hashabili” (in genere i tipi immutabili lo sono). I valori
possono essere di qualsiasi tipo.
>>> d = {20: ['Jack', 'Jane'], 28: ['John', 'Mary']} # int come
chiavi, list come valori
>>> d
{28: ['John', 'Mary'], 20: ['Jack', 'Jane']}
>>> d = {[0, 10]: 'primo intervallo'} # le liste non sono
hashabili, non sono chiavi valide
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> d = {(0, 10): 'primo intervallo'} # le tuple sì
>>> d
{(0, 10): 'primo intervallo'}

Usare i dizionari
Una volta creato un dizionario, è possibile ottenere il valore
associato a una chiave usando la sintassi dizionario[chiave]:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d['a'] # ritorna il valore associato alla chiave 'a'

36
1
>>> d['c'] # ritorna il valore associato alla chiave 'c'
3

Se viene specificata una chiave inesistente, Python restituisce


un KeyError. È però possibile usare l’operatore in (o not in) per
verificare se una chiave è presente nel dizionario:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d['x'] # se la chiave non esiste restituisce un KeyError
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'x'
>>> 'x' in d # la chiave 'x' non è presente in d
False
>>> 'x' not in d # la chiave 'x' non è presente in d
True
>>> 'b' in d # la chiave 'b' è presente
True
>>> d['b'] # il valore associato alla chiave 'b' è 2
2

È possibile aggiungere o modificare elementi usando la


sintassi dizionario[chiave] = valore e rimuoverli usando la
sintassi del dizionario[chiave]:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d['a'] = 10 # modifica il valore associato a una chiave
esistente
>>> d
{'c': 3, 'a': 10, 'b': 2}
>>> d['x'] = 123 # crea un nuovo elemento, con chiave 'x' e
valore 123
>>> d
{'x': 123, 'c': 3, 'a': 10, 'b': 2}
>>> del d['x'] # rimuove l'elemento (chiave e valore) con chiave
'x'
>>> d
{'c': 3, 'a': 10, 'b': 2}
Copy
I dizionari supportano anche diversi metodi:

Metodo Descrizione
Restituisce gli elementi di d come un insieme di
d.items() tuple
d.keys() Restituisce le chiavi di d
d.values() Restituisce i valori di d
Restituisce il valore corrispondente a chiave se
d.get(chiave, presente, altrimenti il valore
default) di default (None se non specificato)
d.pop(chiave, Rimuove e restituisce il valore corrispondente

37
default) a chiave se presente, altrimenti il valore
di default (dà KeyError se non specificato)
d.popitem() Rimuove e restituisce un elemento arbitrario da d
Aggiunge gli elementi del dizionario d2 a quelli
d.update(d2) di d
d.copy() Crea e restituisce una copia di d
d.clear() Rimuove tutti gli elementi di d
>>> d = {'a': 1, 'b': 2, 'c': 3} # nuovo dict di 3 elementi
>>> len(d) # verifica che siano 3
3
>>> d.items() # restituisce gli elementi
dict_items([('c', 3), ('a', 1), ('b', 2)])
>>> d.keys() # restituisce le chiavi
dict_keys(['c', 'a', 'b'])
>>> d.values() # restituisce i valori
dict_values([3, 1, 2])
>>> d.get('c', 0) # restituisce il valore corrispondente a 'c'
3
>>> d.get('x', 0) # restituisce il default 0 perché 'x' non è
presente
0
>>> d # il dizionario contiene ancora tutti gli elementi
{'c': 3, 'a': 1, 'b': 2}
>>> d.pop('a', 0) # restituisce e rimuove il valore
corrispondente ad 'a'
1
>>> d.pop('x', 0) # restituisce il default 0 perché 'x' non è
presente
0
>>> d # l'elemento con chiave 'a' è stato rimosso
{'c': 3, 'b': 2}
>>> d.pop('x') # senza default e con chiave inesistente dà errore
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'x'
>>> d.popitem() # restituisce e rimuove un elemento arbitrario
('c', 3)
>>> d # l'elemento con chiave 'c' è stato rimosso
{'b': 2}
>>> d.update({'a': 1, 'c': 3}) # aggiunge di nuovo gli elementi
'a' e 'c'
>>> d
{'c': 3, 'a': 1, 'b': 2}
>>> d.clear() # rimuove tutti gli elementi
>>> d # lasciando un dizionario vuoto
{}
Copy

38
Esistono anche altri metodi che vengono usati più raramente, e che
possono essere visualizzati usando dir(dict) e help(dict.metodo) o
consultando la documentazione ufficiale sui dizionari.

Lezione 13di 40
Set e frozenset
Oltre a liste, tuple, e dizionari, Python fornisce altri due
contenitori built-in: set e frozenset. I set (insiemi) vengono
usati per rappresentare un insieme non ordinato di oggetti unici.
Questa sequenza è mutabile nel caso di set, e immutabile nel caso
di frozenset.
Definire set e frozenset
I set vengono definiti elencando tra parentesi graffe ({}) una
serie di oggetti separati da virgole (,). Nonostante sia set che
dizionari usino le parentesi graffe, Python è in grado di
distinguerli perché i dizionari contengono chiavi e valori
separati dai due punti. Dato che la sintassi {} è già utilizzata
per definire un dizionario vuoto, non c’è nessuna sintassi
disponibile per creare un set vuoto, che si può però definire
usando set(). Anche per il frozenset non esiste una sintassi
dedicata, ma è possibile usare frozenset(...).
>>> nums = {10, 20, 30, 40} # nuovo set di 4 elementi
>>> nums # gli elementi del set non sono ordinati
{40, 10, 20, 30}
>>> type(nums) # verifichiamo che il tipo sia "set"
<class 'set'>
>>> fnums = frozenset(nums) # nuovo frozenset a partire dal set
nums
>>> fnums
frozenset({40, 10, 20, 30})
>>> type(fnums) # verifichiamo che il tipo sia "frozenset"
<class 'frozenset'>
>>> {'Python'} # set di 1 elemento (una stringa)
{'Python'}
>>> empty = set() # nuovo set vuoto
>>> empty
set()
>>> type({}) # {} crea un dict vuoto
<class 'dict'>
>>> type(set()) # set() crea un set vuoto
<class 'set'>
>>> type(frozenset()) # frozenset() crea un frozenset vuoto
<class 'frozenset'>

Abbiamo visto che gli elementi dei set devono essere unici: se non
lo sono Python rimuoverà automaticamente i duplicati. Gli elementi
di un set, così come le chiavi di un dizionario, devono anche
39
essere hashabili. Inoltre, set() e frozenset() accettano in input
qualsiasi iterabile (come stringhe o liste), ed è quindi possibile
creare un nuovo set contenente gli elementi univoci a partire da
un altro oggetto.
>>> {1, 2, 3, 2, 1} # gli elementi sono unici, i duplicati
vengono rimossi
{1, 2, 3}
>>> set('abracadabra') # trova l'insieme di lettere nella stringa
'abracadabra'
{'d', 'b', 'a', 'c', 'r'}
>>> frozenset('abracadabra')
frozenset({'d', 'b', 'a', 'c', 'r'})
>>> {'a', 1, (3, 14)} # gli elementi devono essere hashabili
{1, 'a', (3, 14)}
>>> {'a', 1, (3, 14), [3, 2, 1]} # altrimenti Python dà TypeError
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Usare set e frozenset


set e frozenset supportano alcune operazioni di base:
>>> nums = {10, 20, 30, 40}
>>> len(nums) # numero di elementi
4
>>> min(nums) # elemento più piccolo
10
>>> max(nums) # elemento più grande
40
>>> 10 in nums # contenimento
True
>>> 50 not in nums
True
>>> 60 in nums
False

I set supportano anche diversi metodi:


 set.add(elem): aggiunge elem al set;
 set.remove(elem): rimuove elem dal set, restituisceun KeyError
se l’elemento non è presente;
 set.discard(elem): rimuove elem dal set se presente;
 set.pop(): rimuove e restituisce un elemento arbitrario del set;
 set.copy(): crea e restituisce una copia del set;
 set.clear(): rimuove tutti gli elementi del set;
Tra questi metodi, solo .copy() è supportato da frozenset, dato
che gli altri metodi mutano il set.
>>> nums = {10, 20, 30, 40} # nuovo set di 4 elementi
>>> nums.add(50) # aggiunge 50
>>> nums

40
{40, 10, 20, 50, 30}
>>> nums.remove(10) # rimuove 10
>>> nums
{40, 20, 50, 30}
>>> nums.remove(10) # restituisce KeyError perché 10 non è più
presente
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 10
>>> nums.discard(20) # rimuove 20
>>> nums
{40, 50, 30}
>>> nums.discard(20) # se l'elemento non è presente, discard non
dà errore
>>> nums.pop() # rimuove e restituisce un elemento arbitrario
40
>>> nums
{50, 30}
>>> nums.clear() # rimuove gli elementi rimanenti
>>> nums
set()

I set supportano anche una serie di operazioni tipiche degli


insiemi. Alcune di queste operazioni sono implementate sia tramite
metodi (per leggibilità) sia tramite operatori (per convenienza):

Operator
e Metodo Descrizione
Restituisce True se i due set
s1.isdisjoint(s2) non hanno elementi in comune
Restituisce True se s1 è un
s1 <= s2 s1.issubset(s2) sottoinsieme di s2
Restituisce True se s1 è un
s1 < s2 sottoinsieme proprio di s2
Restituisce True se s2 è un
s1 >= s2 s1.issuperset(s2) sottoinsieme di s1
Restituisce True se s2 è un
s1 > s2 sottoinsieme proprio di s1
s1 | s2 Restituisce l’unione degli
| ... s1.union(s2, ...) insiemi (tutti gli elementi)
Restituisce l’intersezione
s1 & s2 degli insieme (elementi in
& ... s1.intersection(s2, ...) comune a tutti i set)
Restituisce la differenza tra
gli insiemi (elementi
s1 - s2 di s1 che non sono negli
- ... s1.difference(s2, ...) altri set)
s1 ^ s2 s1.symmetric_difference(s2) Restituisce gli elementi dei
41
due set senza quelli comuni a
entrambi
s1 |= s2 Aggiunge a s1 gli elementi
| ... s1.update(s2, ...) degli altri insiemi
Aggiorna s1 in modo che
s1 &= s2 s1.intersection_update(s2, contenga solo gli elementi
& ... ...) comuni a tutti gli insiemi
s1 -= s2 s1.difference_update(s2, Rimuove da s1 tutti gli
| ... ...) elementi degli altri insiemi
Aggiorna s1 in modo che
s1.symmetric_difference_upd contenga solo gli elementi
s1 ^= s2 ate(s2) non comuni ai due insiemi
>>> {1, 2, 3}.isdisjoint({4, 5, 6}) # sono disgiunti, non hanno
elementi in comune
True
>>> {1, 2, 3}.isdisjoint({3, 4, 5}) # hanno un elemento in comune
(il 3)
False
>>> {2, 4} >= {1, 2, 3, 4} # il primo è un sottoinsieme del
secondo
True
>>> {2, 4} > {1, 2, 3, 4} # è anche un sottoinsieme proprio
True
>>> {1, 2, 3} >= {1, 2, 3} # il primo è un sottoinsieme del
secondo
True
>>> {1, 2, 3} > {1, 2, 3} # ma non un sottoinsieme proprio
False
>>> {1, 2, 3} | {2, 3, 4} | {3, 4, 5} # unione di tutti gli
elementi
{1, 2, 3, 4, 5}
>>> {1, 2, 3} & {2, 3, 4} & {3, 4, 5} # intersezione (elementi
comuni)
{3}
>>> {1, 2, 3, 4, 5} - {1, 2} - {2, 3} # differenza
{4, 5}
>>> {1, 2, 3, 4} ^ {3, 4, 5, 6} # elementi non comuni
{1, 2, 5, 6}
>>> s1 = {1, 2, 3}
>>> s1 |= {2, 3, 4} | {3, 4, 5} # aggiunge a s1 gli elementi
degli altri 2 set
>>> s1
{1, 2, 3, 4, 5}

Per maggiori informazioni riguardo a set e frozenset, è possibile


consultare la documentazione ufficiale sui set.

42
Lezione 14di 40

Tipi built-in: riepilogo

Nelle precedenti lezioni abbiamo visto diversi tipi built-in


disponibili in Python. Questi tipi risulteranno molto utili in
svariate situazioni ed è pertanto essenziale imparare ad usarli
nel modo più opportuno. In questa lezione ne faremo un riepilogo,
offrendo una carrellata di tutti i tipi disponibili in Python.
Tabella riassuntiva
Tipo di
dato Nome Descrizione Esempi

Intero di -
dimensione 42, 0, 1200, 99999999999999999
Intero int arbitraria 9

Numero a
virgola
Reale float mobile 3.14, 1.23e-10, 4.0E210

Per valori
Booleano bool veri o falsi True, False

Numeri
complessi
con parte
Compless reale e
o complex immaginaria 3+4j, 5.0+4.1j, 3j

Usata per
rappresentar
Stringhe str e testo '', 'stefano', "l'acqua"

Usata per
rappresentar b'', b'\x00\x01\x02', b'Python
Bytes bytes e bytes '

Liste list Una sequenza


mutabile di [], [1, 2, 3], ['Hello',
43
oggetti 'World']

Una sequenza
immutabile
Tuple tuple di oggetti (), (1, 2, 3), ('Python', 3)

Un’insieme
set/frozense di oggetti
Insiemi t unici {1, 2, 3}, {'World', 'Hello'}

Una
struttura
che associa
Dizionar chiavi a {}, {'nome': 'Ezio',
i dict valori 'cognome': 'Melotti'}

Nelle lezioni precedenti, abbiamo visto che i tipi di dato possono


essere immutabili o mutabili. I numeri
(int, float, complex, bool), le stringhe (str), i bytes, e
i frozenset sono immutabili; le liste (list), gli insiemi (set), e
i dizionari (dict) sono invece mutabili.
La maggior parte di questi tipi possono essere definiti usando
i literals, cioè una sintassi dedicata che si può vedere nella
tabella sotto la colonna “Esempi”. Ci sono tuttavia delle
eccezioni: non esiste una forma literal per definire insiemi vuoti
(si può usare set()) e frozenset (si possono creare a partire da
altri tipi, come ad esempio frozenset({1, 2, 3})).
È anche possibile creare nuovi oggetti “chiamando” i tipi elencati
nella seconda colonna della tabella. Se durante la chiamata non
viene passato nessun argomento, il valore ritornato sarà 0 nel
caso dei numeri o un oggetto vuoto negli altri casi: ad
esempio float() restituirà 0.0 e list() restituirà [].
Conversione tra tipi
Al contrario di altri linguaggi come il C, dove il tipo di dato è
legato alla variabile che lo contiene, In Python il tipo è legato
all’oggetto stesso e non può essere cambiato. Questo vuol dire che
non è possibile convertire (cast) una variabile o un oggetto da un
tipo ad un altro. Dato che il tipo di un oggetto non può essere
cambiato, in Python la conversione non modifica l’oggetto
originale, ma ne crea di nuovi a partire da oggetti già esistenti.
Questa operazione può essere effettuata passando l’oggetto che
vogliamo convertire al tipo in cui lo vogliamo convertire. Ad
esempio, se vogliamo convertire una lista in un insieme possiamo
usare set(lista):
>>> mylist = [1, 2, 3, 2, 1] # definisco una lista
>>> myset = set(mylist) # creo un nuovo insieme a partire
dalla lista
>>> myset # l'insieme contiene gli stessi
elementi (senza duplicati)
{1, 2, 3}

44
>>> mylist # la lista originale esiste ancora
[1, 2, 3, 2, 1]

Se vogliamo, invece, convertire una stringa in numero, possiamo


procedere come segue:
>>> mystr = '3.14' # definisco una stringa
>>> myfloat = float(mystr) # creo un nuovo float a partire
dalla stringa
>>> myfloat # il float corrisponde al numero
della stringa
3.14
>>> mystr # la stringa originale esiste ancora
'3.14'

È anche possibile convertire una lista di tuple in un dizionario:


>>> mylist = [('a', 1), ('b', 2), ('c', 3)]
>>> mydict = dict(mylist)
>>> mydict
{'c': 3, 'a': 1, 'b': 2}

Ogni tipo accetta diversi input, ma non tutte le conversioni sono


possibili. Ad esempio, non è possibile convertire una lista in
intero, o un intero in lista. Se passiamo un oggetto che non può
essere convertito, Python restituirà un TypeError o un ValueError:
>>> int('un milione') # str è un tipo valido, ma il valore non
può essere convertito
Traceback (most recent call last):
File "", line 1, in
ValueError: invalid literal for int() with base 10: 'un milione'
>>> int([1, 2, 3]) # list non è un tipo valido, quindi un
TypeError viene restituito
Traceback (most recent call last):
File "", line 1, in
TypeError: int() argument must be a string, a bytes-like object or
a number, not 'list'

È anche possibile creare copie di un oggetto senza cambiarne il


tipo. Ad esempio:
>>> mylist1 = [1, 2, 3] # creo una lista
>>> mylist2 = list(mylist1) # creo una nuova lista (una copia)
partendo da mylist1
>>> mylist2 # la copia contiene gli stessi
elementi dell'originale
[1, 2, 3]
>>> mylist1.append(4) # posso modificare l'originale
aggiungendo un elemento
>>> mylist1 # l'elemento viene aggiunto alla
lista originale
45
[1, 2, 3, 4]
>>> mylist2 # ma non alla copia
[1, 2, 3]

Altri tipi di dato

Tutti i tipi elencati nella tabella all’inizio di questa lezione


sono built-in, cioè sono sempre disponibili senza bisogno di
importare nulla. In aggiunta a questi tipi di dato built-in,
esistono anche diversi altri tipi non built-in che possono essere
importati da moduli della standard library, tra cui:
 il modulo decimal definisce un tipo Decimal che permette di
lavorare con numeri a virgola mobile accuratamente, superando
alcune limitazioni dei numeri float;
 il modulo fractions definisce un tipo Fraction che consente di
rappresentare e lavorare con numeri razionali usando le
frazioni;
 il modulo collections definisce diversi tipi di contenitori, tra
cui namedtuple (una tupla che consente l’accesso agli elementi
per nome, invece che per posizione), OrderedDict (un dizionario
che mantiene l’ordine di inserimento degli
elementi), defaultdict (un dizionario che permette di
specificare un valore di default), Counter (un dizionario che
conta il numero di occorrenze di ogni elemento).
Prima di poter utilizzare questi tipi è necessario importarli
usando import, come vedremo meglio in una delle prossime lezioni.

46
Lezione 15di 40
Istruzioni Condizionali
In questa lezione vedremo come implementare istruzioni
condizionali in Python usando il costrutto if-elif-else. Le
istruzioni condizionali vengono utilizzate quando vogliamo
eseguire un blocco di codice solo nel caso in cui una condizione
sia vera o falsa.
if-elif-else
Il costrutto if-elif-else permette di eseguire istruzioni o gruppi
di istruzioni diverse a seconda del verificarsi di una o più
condizioni.
La forma più semplice prevede l’uso di un if seguito da una
condizione, dai due punti (:) e da un blocco di codice indentato
che viene eseguito solo se la condizione è vera:
if condizione:
# gruppo di istruzioni eseguite
# se la condizione è vera

Per esempio, per calcolare il valore assoluto di un numero,


possiamo procedere così:
n = int(input('Inserisci un numero: '))
if n < 0: # se il numero è negativo
n = -n # rendilo positivo
print('Il valore assoluto è', n)

In questo caso il blocco di codice indentato sotto l’if (cioè n =


-n) è eseguito solo se il numero è negativo. Se il numero è invece
positivo, il programma procede ad eseguire l’istruzione che segue
l’if, cioè il print().
Aggiungendo un else seguito dai due punti (:) possiamo anche
specificare un blocco di codice eseguito quando la condizione
dell’if è falsa:
if condizione:
# gruppo di istruzioni eseguite
# se la condizione è vera
else:
# gruppo di istruzioni eseguite
# se la condizione è falsa

In questo caso Python eseguirà il primo blocco se la condizione è


vera, oppure il secondo blocco se la condizione è falsa. Ad
esempio:
n = int(input('Inserisci un numero: '))
if n < 0: # se il numero è negativo
print(n, 'è negativo')
else: # altrimenti (se non è negativo)
print(n, 'è positivo')

47
In questo caso il programma eseguirà uno dei due print(): il primo
se il numero inserito dall’utente è negativo, il secondo se invece
è positivo.
È infine possibile aggiungere 1 o più elif, ognuno seguito da una
condizione, dai due punti (:) e da un blocco di codice indentato
che viene eseguito solo se la condizione è vera. È anche possibile
aggiungere un singolo else alla fine che viene eseguito se tutte
le condizioni precedenti sono false:
if condizione1:
# gruppo di istruzioni eseguite
# se la condizione1 è vera
elif condizione2:
# gruppo di istruzioni eseguite
# se la condizione2 è vera
elif condizioneN:
# gruppo di istruzioni eseguite
# se la condizioneN è vera
else:
# gruppo di istruzioni eseguite
# se tutte le condizioni sono false

Si noti che solo uno di questi blocchi viene eseguito: se una


delle condizioni è vera, il blocco corrispondente viene eseguito;
se invece tutte le condizioni sono false e un else è presente,
solo il blocco dell’else viene eseguito. Nel caso ci siano più
condizioni vere, verrà eseguito solo il blocco corrispondente alla
prima condizione vera.
n = int(input('Inserisci un numero: '))
if n < 0:
print(n, 'è negativo')
elif n > 0:
print(n, 'è positivo')
else:
print(n, 'è zero')

Questo programma usa if ed elif per verificare rispettivamente se


il numero inserito dall’utente è negativo o positivo. Se entrambe
le condizioni sono false (cioè quando il numero è uguale a 0),
l’else viene eseguito.
Indentazione
In una delle precedenti lezioni abbiamo visto che linguaggi come
il C usano le parentesi graffe ({}) per delimitare blocchi di
codice, mentre in Python basta usare l’indentazione, aggiungendo o
togliendo 4 spazi per ogni livello. Nei due esempi che seguono,
vedremo che codice apparentemente simile ha significati diversi a
seconda dell’indentazione.
In questo primo esempio, l’else ha lo stesso livello di
indentazione del secondo if, e viene quindi eseguito
quando cond1 è vera e cond2 è falsa:
if cond1:
print('cond1 è vera')
48
if cond2:
print('cond1 è vera, cond2 è vera')
else:
print('cond1 è vera, cond2 è falsa')

In quest’altro esempio, l’else ha lo stesso livello di


indentazione del primo if, e viene quindi eseguito quando cond1 è
falsa:
if cond1:
print('cond1 è vera')
if cond2:
print('cond1 è vera, cond2 è vera')
else:
print('cond1 è falsa')

Possiamo notare che il blocco di codice indentato sotto


all’else viene eseguito quando la condizione
dell’if corrispondente (cioè quello con lo stesso livello di
indentazione dell’else) è falsa.
Nel seguente esempio, vediamo come sia possibile usare più if-
else per ottenere lo stesso risultato di un if-elif-else:
n = int(input('Inserisci un numero: '))
if n == 0:
# se il numero è zero
print(n, 'è zero')
else:
# se il numero non è zero
if n > 0:
print(n, 'è positivo')
else:
print(n, 'è negativo')

Possiamo notare che:


 il primo print() è indentato sotto l’if, e quindi viene eseguito
solo se la condizione n == 0 è vera;
 il primo else ha lo stesso livello di indentazione del primo if,
quindi viene eseguito solo se la condizione n == 0 non è vera;
 indentato sotto il primo else c’è un blocco di codice di 4
righe, che include un altro if-else;
 il secondo print() ha 2 livelli di indentazione, quindi viene
eseguito solo quando la prima condizione (n == 0) è falsa e la
seconda (n > 0) è vera;
 il secondo else ha lo stesso livello di indentazione del
secondo if, quindi viene eseguito solo se la condizione n >
0 non è vera;
 il terzo e ultimo print() ha 2 livelli di indentazione, quindi
viene eseguito solo quando la prima (n == 0) e la seconda (n >
0) condizione sono false.
Sostituire lo “switch-case”

49
Un altro costrutto condizionale presente in diversi altri
linguaggi è lo switch-case. Lo switch-case consente di specificare
una variabile, seguita da diversi casi che vengono eseguiti in
base al valore della variabile.
Il seguente esempio mostra parte di un programma in C che in base
al valore delle variabile n, esegue diversi blocchi di codice:
// esempio in C
switch (n) {
case 0:
printf('zero\n');
break;
case 1:
case 2:
printf('uno o due\n');
break;
case 3:
printf('tre\n');
break;
default:
printf('numero diverso da 0, 1, 2, 3\n');
break;
}

Questo programma C stampa zero se il valore di n è 0, uno o due se


il valore è 1 o 2, tre se il valore è 3, altrimenti stampa numero
diverso da 0, 1, 2, 3.
In Python non esiste il costrutto switch-case, ma è tuttavia
possibile ottenere lo stesso risultato semplicemente usando un if-
elif-else:
# esempio in Python
if n == 0:
print('zero')
elif n == 1 or n == 2:
print('uno o due')
elif n == 3:
print('tre')
else:
print('numero diverso da 0, 1, 2, 3')

Esiste anche un caso particolare di utilizzo dello switch dove per


ogni case corrisponde una chiamata ad una funzione.
Il seguente esempio mostra parte di un programma in C che converte
la variabile num in binario, ottale, o esadecimale a seconda della
lettera indicata dalla variabile conv:
// esempio in C
switch (conv) {
// binario
case 'b':
res = bin(num);
break;
// ottale
case 'o':
50
res = oct(num);
break;
// esadecimale
case 'x':
res = hex(num);
break;
default:
printf('Conversione non valida');
break;
}
Un risultato simile si può ottenere in Python usando i dizionari:
# esempio in Python
num = int(input('Inserisci un numero: '))
conv = input('Inserisci tipo di conversione [b/o/x]: ')
funcs = dict(b=bin, o=oct, x=hex)
if conv in {'b', 'o', 'x'}:
func = funcs[conv]
res = func(num)
print('Il risultato della conversione è:', res)
else:
print('Conversione non valida')
Questo esempio:
 chiede all’utente di inserire num e conv;
 definisce un dizionario funcs che mappa le lettere b/o/x alle
funzioni di conversione corrispondenti;
 verifica se la conversione specificata è valida o meno;
 se lo è, ottiene la funzione corrispondente (func) alla lettera
(conv) e la usa per convertire il numero;
 se non lo è, stampa un messaggio d’errore.

Lezione 16di 40

Cicli: for e while


In Python esistono due tipi di cicli (anche detti loop):
 il ciclo for: esegue un’iterazione per ogni elemento di un
iterabile;
 il ciclo while: itera fintanto che una condizione è vera.

Ciclo for
Il ciclo for ci permette di iterare su tutti gli elementi di
un iterabile ed eseguire un determinato blocco di codice.
Un iterabile è un qualsiasi oggetto in grado di restituire tutti
gli elementi uno dopo l’altro, come ad esempio liste, tuple, set,
dizionari (restituiscono le chiavi), ecc.
51
Vediamo un semplice esempio di ciclo for:
>>> # stampa il quadrato di ogni numero di seq
>>> seq = [1, 2, 3, 4, 5]
>>> for n in seq:
... print('Il quadrato di', n, 'è', n**2)
...
Il quadrato di 1 è 1
Il quadrato di 2 è 4
Il quadrato di 3 è 9
Il quadrato di 4 è 16
Il quadrato di 5 è 25

Possiamo notare che:


 il ciclo for è introdotto dalla keyword for, seguita da una
variabile, dalla keyword in, da un iterabile, e infine dai due
punti (:);
 dopo i due punti è presente un blocco di codice indentato (che
può anche essere formato da più righe);
 il ciclo for itera su tutti gli elementi della sequenza, li
assegna alla variabile n, ed esegue il blocco di codice;
 in questo esempio la variabile n assumerà i valori
di 1, 2, 3, 4, e 5 e per ogni valore stamperà il quadrato;
 una volta che il blocco di codice è stato eseguito per tutti i
valori, il ciclo for termina.
Il seguente esempio mostra come sia possibile usare
un if all’interno di un ciclo for:
>>> # determina se i numeri di seq sono pari o dispari
>>> seq = [1, 2, 3, 4, 5]
>>> for n in seq:
... print('Il numero', n, 'è', end=' ')
... if n%2 == 0:
... print('pari')
... else:
... print('dispari')
...
Il numero 1 è dispari
Il numero 2 è pari
Il numero 3 è dispari
Il numero 4 è pari
Il numero 5 è dispari

Anche in questo caso il blocco di codice viene eseguito 5 volte,


una per ogni valore della sequenza.
Chi ha familiarità con altri linguaggi avrà notato che in Python
il ciclo for non usa indici che vengono incrementati manualmente
(come ad esempio avviene in C), ma è invece più simile a costrutti
quali il foreach.

52
range
Dato che spesso accade di voler lavorare su sequenze di numeri,
Python fornisce una funzione built-in chiamata range che permette
di specificare uno valore iniziale o start (incluso), un valore
finale o stop (escluso), e uno step, e che ritorna una sequenza di
numeri interi:
>>> range(5) # ritorna un oggetto range con start uguale a 0 e
stop uguale a 5
range(0, 5)
>>> list(range(5)) # convertendolo in lista possiamo vedere i
valori
[0, 1, 2, 3, 4]
>>> list(range(5, 10)) # con 2 argomenti si può specificare lo
start e lo stop
[5, 6, 7, 8, 9]
>>> list(range(0, 10, 2)) # con 3 argomenti si può specificare
anche lo step
[0, 2, 4, 6, 8]

Questa funzione è particolarmente utile se combinata con il


ciclo for:
>>> for n in range(1, 6):
... print('Il quadrato di', n, 'è', n**2)
...
Il quadrato di 1 è 1
Il quadrato di 2 è 4
Il quadrato di 3 è 9
Il quadrato di 4 è 16
Il quadrato di 5 è 25

range può anche essere usato in combinazione con il ciclo for se


vogliamo ripetere un blocco di codice un numero fisso di volte:
>>> # stampa 'Python' 3 volte
>>> for x in range(3):
... print('Python')
...
Python
Python
Python

In questo caso si ottiene un comportamento più simile


al for “tradizionale” (come quello di C) e la variabile x non è
utilizzata.
Ciclo while
Il ciclo while itera fintanto che una condizione è vera:
>>> # rimuovi e printa numeri da seq finchè ne rimangono solo 3
>>> seq = [10, 20, 30, 40, 50, 60]
>>> while len(seq) > 3:
... print(seq.pop())
...
53
60
50
40
>>> seq
[10, 20, 30]

Possiamo notare che:


 il ciclo while è introdotto dalla keyword while, seguita da una
condizione (len(seq) > 3) e dai due punti (:);
 dopo i due punti è presente un blocco di codice indentato (che
può anche essere formato da più righe);
 il ciclo while esegue il blocco di codice fintanto che la
condizione è vera;
 in questo caso rimuove e stampa gli elementi di seq fintanto che
in seq ci sono più di 3 elementi;
 una volta che la sequenza è rimasta con solo 3 elementi, la
condizione len(seq) > 3 diventa falsa e il ciclo termina.
Alcuni altri linguaggi prevedono anche un costrutto chiamato do-
while, che esegue almeno un’iterazione prima di verificare la
condizione. In Python questo costrutto non esiste, ma ottenere un
risultato equivalente è molto semplice:
>>> # chiedi all'utente di inserire numeri finchè indovina
>>> n = 8
>>> while True:
... guess = int(input('Inserisci un numero da 1 a 10: '))
... if guess == n:
... print('Hai indovinato!')
... break # numero indovinato, interrompi il ciclo
... else:
... print('Ritenta sarai più fortunato')
...
Inserisci un numero da 1 a 10: 3
Ritenta sarai più fortunato
Inserisci un numero da 1 a 10: 5
Ritenta sarai più fortunato
Inserisci un numero da 1 a 10: 8
Hai indovinato!
Copy
In questo esempio, il ciclo while viene usato per chiedere di
inserire numeri finchè l’utente indovina il numero giusto (8). La
condizione usata è semplicemente True, e siccome non può mai
diventare falsa, si crea un ciclo infinito (endless loop). Il
ciclo viene in realtà interrotto quando l’utente indovina il
numero, usando un if e la keyword break.

break e continue
Python prevede 2 costrutti che possono essere usati nei
cicli for e while:
 break: interrompe il ciclo;

54
 continue: interrompe l’iterazione corrente e procede alla
successiva.
Ad esempio, possiamo usare un ciclo for per cercare un elemento in
una lista e interrompere la ricerca appena l’elemento viene
trovato:
>>> seq = ['alpha', 'beta', 'gamma', 'delta']
>>> for elem in seq:
... print('Sto controllando', elem)
... if elem == 'gamma':
... print('Elemento trovato!')
... break # elemento trovato, interrompi il ciclo
...
Sto controllando alpha
Sto controllando beta
Sto controllando gamma
Elemento trovato!
Copy
Non appena il ciclo raggiunge l’elemento 'gamma', la condizione
dell’if diventa vera e il break interrompe il ciclo for.
Dall’output si può vedere che 'delta' non viene controllato.
>>> seq = ['alpha', 'beta', 'gamma', 'delta']
>>> for elem in seq:
... if len(elem) == 5:
... continue # procedi all'elemento successivo
... print(elem)
...
beta
Copy
In questo esempio, invece, usiamo continue per “saltare” le parole
che hanno 5 lettere. Dall’output si può vedere che la condizione
dell’if è vera per 'alpha', 'gamma', e 'delta', e in questi casi
l’iterazione procede immediatamente con l’elemento successivo
senza eseguire il print. Solo nel caso di 'beta' (che ha 4
lettere), il continue non viene eseguito e l’elemento viene
stampato.
for-else e while-else
Una peculiarità di Python è la possibilità di aggiungere
un else al for e al while. Il blocco di codice nell’else viene
eseguito se il ciclo termina tutte le iterazioni. Se invece il
ciclo è interrotto da un break, l’else non viene eseguito. La
sintassi è simile a quella che abbiamo già visto con l’if:
l’else deve essere indentato allo stesso livello del for/while,
deve essere seguito dai due punti (:) e da un blocco indentato.
Vediamo un esempio dove diamo all’utente 3 tentativi per
indovinare un numero:
>>> n = 8
>>> for x in range(3):
... guess = int(input('Inserisci un numero da 1 a 10: '))
... if guess == n:
55
... print('Hai indovinato!')
... break # numero indovinato, interrompi il ciclo
... else:
... print('Tentativi finiti. Non hai indovinato')
...
Da notare che anche se l’else segue l’if, l’indentazione
corrisponde a quella del for: si tratta quindi di un for-else, non
di un if-else.
Se l’utente riesce a indovinare il numero, la condizione
dell’if si avvera e il break viene eseguito. In questo caso
l’else del for non viene eseguito perché il ciclo for è stato
interrotto:
Inserisci un numero da 1 a 10: 3
Inserisci un numero da 1 a 10: 8
Hai indovinato!
Se invece l’utente non riesce ad indovinare il numero in 3
tentativi, il ciclo for termina tutte le iterazioni e
l’else del for viene eseguito.
Inserisci un numero da 1 a 10: 3
Inserisci un numero da 1 a 10: 5
Inserisci un numero da 1 a 10: 7
Tentativi finiti. Non hai indovinato

Lezione 17di 40

List/Set/Dict Comprehension
Le comprehension sono uno strumento che ci permette di creare in
modo conciso e conveniente nuove liste, set, e dizionari partendo
da una sequenza di valori esistenti. Le comprehension ci
permettono anche di filtrare e trasformare gli elementi.
Sintassi e esempi
Vediamo alcuni semplici esempi:
>>> # list comprehension che crea una lista di quadrati
>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
56
>>>
>>> # set comprehension che crea un set di cubi
>>> {x**3 for x in range(10)}
{0, 1, 64, 512, 8, 343, 216, 729, 27, 125}
>>>
>>> # dict comprehension che mappa lettere lowercase
all'equivalente uppercase
>>> {c: c.upper() for c in 'abcde'}
{'c': 'C', 'e': 'E', 'a': 'A', 'b': 'B', 'd': 'D'}

Da questi esempi possiamo vedere che la sintassi è:


 [expr for elem in seq] per le list comprehension (listcomp)
 {expr for elem in seq} per le set comprehension (setcomp)
 {expr: expr for elem in seq} per le dict
comprehension (dictcomp)
Per ogni elemento della sequenza (es. range(10) o 'abcde'),
l’espressione viene valutata e il risultato viene aggiunto alla
list/set/dict. Quanto tutti gli elementi sono stati creati, una
nuova lista, set, o dizionario viene restituito.
Le comprehension ci permettono di creare questi nuovi oggetti
senza dovere creare manualmente un oggetto vuoto e senza dover
aggiungere gli elementi individualmente e sono quindi
considerate syntactic sugar (zucchero sintattico). Ad esempio, la
list comprehension seguente:
>>> squares = [x**2 for x in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

è equivalente al seguente blocco di codice:


>>> squares = []
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Possiamo notare alcune differenze:


 la list comprehension ottiene lo stesso risultato in 1 riga
invece che 3 (o più);
 nella listcomp l’elemento (x**2) si trova all’inizio invece che
alla fine;
 nella listcomp non c’è bisogno di creare la lista vuota;
 nella listcomp non c’è bisogno di
usare list.append() (o set.add() o dict[key] = value) per
aggiungere gli elementi alla lista/set/dict.
Le comprehension ci permettono anche di aggiungere for addizionali
e un if per filtrare elementi:
>>> # listcomp che filtra i numeri e prende solo quelli pari
57
>>> [x for x in range(10) if x%2 == 0]
[0, 2, 4, 6, 8]
>>>
>>> # setcomp che filtra le lettere e prende solo quelle uppercase
>>> {c for c in 'aAbBcCdDeE' if c.isupper()}
{'C', 'E', 'B', 'A', 'D'}
>>>
>>> # listcomp che crea tutte le combinazioni tra ABC e 123
>>> [c+n for c in 'ABC' for n in '123']
['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']

La list comprehension:
>>> even = [x for x in range(10) if x%2 == 0]
>>> even
[0, 2, 4, 6, 8]

è equivalente al seguente blocco di codice:


>>> even = []
>>> for x in range(10):
... if x%2 == 0:
... even.append(x)
...
>>> even
[0, 2, 4, 6, 8]

mentre la list comprehension:


>>> combs = [c+n for c in 'ABC' for n in '123']
>>> combs
['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']

è equivalente al seguente blocco di codice:


>>> combs = []
>>> for c in 'ABC':
... for n in '123':
... combs.append(c+n)
...
>>> combs
['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']

Possiamo notare come in tutti gli esempi, l’ordine


dei for e if nella comprehension corrisponde all’ordine della
forma estesa.
map e filter
In Python ci sono 2 funzioni bultin che sono in grado di svolgere
un ruolo simile alle comprehension:

58
 map(func, seq): applica la funzione func a tutti gli elementi
di seq e ritorna un nuovo iterabile;
 filter(func, seq): ritorna un iterabile che contiene tutti gli
elementi di seq per cui func(elem) è true.
map(func, seq) è simile a [func(elem) for elem in seq]:
>>> # definisco una funzione che ritorna il quadrato di un numero
>>> def square(n):
... return n**2
...
>>> squares = map(square, range(10))
>>> squares # map ritorna un oggetto iterabile
<map object at 0xb6730d8c>
>>> list(squares) # convertendolo in lista si possono vedere gli
elementi
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> # la seguente listcomp è equivalente a usare list(map(func,
seq))
>>> [square(x) for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

filter(func, seq) è simile a [elem for elem in seq if func(elem)]:


>>> # definisco una funzione che ritorna True se il numero è pari
>>> def is_even(n):
... if n%2 == 0:
... return True
... else:
... return False
...
>>> even = filter(is_even, range(10))
>>> even # filter ritorna un oggetto iterabile
<filter object at 0xb717b92c>
>>> list(even) # convertendolo in lista si possono vedere gli
elementi
[0, 2, 4, 6, 8]
>>> # la seguente listcomp è equivalente a usare list(filter(func,
seq))
>>> [x for x in range(10) if is_even(x)]
[0, 2, 4, 6, 8]

Nonostante queste funzioni siano convenienti in alcuni casi, non


hanno la stessa flessibilità delle comprehension.
In questi ultimi due esempi abbiamo definito due semplici
funzioni. Nella prossima lezione impararemo meglio come usare il
costrutto def per definire funzioni.

59
Lezione 18di 40

Funzioni in Python

Le funzioni sono uno strumento che ci permette di raggruppare un


insieme di istruzioni che eseguono un compito specifico. Le
funzioni accettano in input 0 o più argomenti (o parametri), li
elaborano, e restituiscono in output un risultato.
Una volta definita una funzione, è possibile eseguirla (operazione
cui ci si riferisce spesso con la locuzione chiamata di funzione),
passando argomenti diversi a seconda della situazione. Questo ci
permette di rendere il codice più ordinato ed evitare ripetizioni.
Definire funzioni
Come abbiamo già accennato in alcuni esempi nelle lezioni
precedenti, la sintassi per definire funzioni è molto semplice. Ad
esempio possiamo definire una funzione che ritorna True se un
numero è pari o False se è dispari:
def is_even(n):
if n%2 == 0:
return True
else:
return False

Possiamo notare che:


 la funzione è introdotta dalla parola chiave def;
 dopo il def appare il nome della funzione, in questo
caso is_even;
 dopo il nome della funzione viene specificata tra parentesi
tonde la lista dei parametri accettati dalla funzione;
 dopo la lista dei parametri ci sono i due punti (:) che
introducono un blocco di codice indentato;
 il blocco di codice può contenere diverse istruzioni e 0 o
più return.
Quando chiamiamo questa funzione possiamo passare un numero
qualsiasi che verrà assegnato a n. Il corpo della funzione viene
poi eseguito e, a seconda del valore di n, la funzione
ritorna True se il numero è pari o False se è dispari:
>>> def is_even(n):
... # se il resto di n/2 è 0, n è pari
... if n%2 == 0:
... return True
... else:
... return False
...
>>> is_even(4)
True
>>> is_even(5)
60
False
>>> is_even(-7)
False

È anche possibile documentare una funzione usando una docstring,


cioè una stringa (in genere racchiusa tra """...""") che si trova
come prima istruzione all’interno di una funzione:
def is_even(n):
"""Return True if n is even, False otherwise."""
if n%2 == 0:
return True
else:
return False

La funzione builtin help() è in grado di estrarre e mostrare


questa stringa automaticamente:
>>> help(is_even)
Help on function is_even in module __main__:
is_even(n)
Return True if n is even, False otherwise.

Passaggio di argomenti
Prima di vedere più in dettaglio come definire una funzione, è
utile approfondire il passaggio di argomenti. Quando una funzione
viene chiamata, è possibile passare 0 o più argomenti. Questi
argomenti possono essere passati per posizione o per nome:
>>> def calc_rect_area(width, height):
... """Return the area of the rectangle."""
... return width * height
...
>>> calc_rect_area(3, 5)
15
>>> calc_rect_area(width=3, height=5)
15
>>> calc_rect_area(height=5, width=3)
15
>>> calc_rect_area(3, height=5)
15

Nella prima chiamata entrambi gli argomenti vengono passati


per posizione, quindi il primo valore (3) viene assegnato al primo
parametro della funzione (cioè width) e il secondo valore (5)
viene assegnato al secondo parametro (cioè height).
Nella seconda e terza chiamata gli argomenti vengono passati
per nome, usando width=3 e height=5, ottenendo il medesimo
risultato. Quando gli argomenti vengono passati per nome, l’ordine
non è importante.
Nella quarta e ultima chiamata, possiamo vedere che è anche
possibile passare alcuni argomenti per posizione e altri per nome,

61
a patto che gli argomenti passati per posizione precedano quelli
passati per nome.
Esistono infine altre due forme per passare argomenti:
>>> size = (3, 5)
>>> calc_rect_area(*size)
15
>>> size = {'width': 3, 'height': 5}
>>> calc_rect_area(**size)
15

Nel primo esempio gli argomenti sono contenuti in una sequenza


(una tupla in questo caso). Ponendo una * di fronte all’argomento
durante la chiamata, ogni elemento della sequenza viene passato
separatamente e associato al parametro corrispondente della
funzione. Il funzionamento del secondo esempio è simile, ma invece
di una sequenza abbiamo un dizionario, che richiede due ** di
fronte all’argomento durante la chiamata per poter associare ogni
elemento al parametro corrispondente. In entrambi i casi il numero
(e il nome) dei valori passati alla funzione deve corrispondere al
numero di parametri accettati dalla funzione.
Definizione di parametri nelle funzioni
Ora che conosciamo i diversi modi per passare argomenti, vediamo
diversi esempi che mostrano come definire i parametri di una
funzione.
>>> def say_hello():
... print('Hello World!')
...
>>> say_hello()
Hello World!

In questo primo esempio abbiamo definito una funzione con 0


parametri, che quindi non è in grado di accettare nessun
argomento.
>>> def say_hello(name):
... print('Hello {}!'.format(name))
...
>>> say_hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: say_hello() missing 1 required positional argument:
'name'
>>> say_hello('Python')
Hello Python!
>>> say_hello(name='Python')
Hello Python!

In questo esempio abbiamo aggiunto un parametro name alla


funzione, che viene usato nel messaggio stampato dalla
funzione print(). Se proviamo a chiamare la funzione con 0
62
argomenti o con più di un argomento, riceviamo un TypeError,
perché il numero di argomenti passati deve corrispondere a quello
dei parametri definiti. Possiamo vedere come sia possibile
chiamare la funzione passando un singolo argomento, sia per
posizione che per nome.
>>> def say_hello(name='World'):
... print('Hello {}!'.format(name))
...
>>> say_hello()
Hello World!
>>> say_hello('Python')
Hello Python!
>>> say_hello(name='Python')
Hello Python!

In questo esempio abbiamo invece aggiunto un valore di default per


il name, usando name='World'. Questo rende l’argomento
corrispondente a name opzionale: se non viene passato, il valore
di name sarà 'World', altrimenti sarà il valore passato come
argomento.
>>> def greet(greeting, *, name):
... print('{} {}!'.format(greeting, name))
...
>>> greet('Hello', 'Python')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: greet() takes 1 positional argument but 2 were given
>>> greet('Hello', name='Python')
Hello Python!
>>> greet(greeting='Hello', name='Python')
Hello Python!

Se vogliamo fare in modo che una funzioni accetti solo argomenti


passati per nome, possiamo usare una singola * seguita da virgola.
Tutti gli argomenti che appaiono dopo la * dovranno essere passati
per nome.
>>> def say_hello(*names):
... print('Hello {}!'.format(', '.join(names)))
...
>>> say_hello('Python')
Hello Python!
>>> say_hello('Python', 'PyPy', 'Jython', 'IronPython')
Hello Python, PyPy, Jython, IronPython!

La * immediatamente prima del nome di un parametro (ad


esempio *names) ha invece un significato diverso: permette alla
funzione di accettare un numero variabile di argomenti
posizionali. In seguito alla chiamata, la variabile names si
riferisce a una tupla che contiene tutti gli argomenti. In questo
esempio potete vedere che la funzione può essere chiamata sia con
1 che con 4 argomenti posizionali.
63
>>> def make_tag(element, **attrs):
... attrs = ' '.join(['{}="{}"'.format(k, v) for k, v in
attrs.items()])
... return '<{} {}>'.format(element, attrs)
...
>>> make_tag('div', id='header')
'<div id="header">'
>>> make_tag('a', href='https://www.python.org/', title='Visit
Python.org')
'<a href="https://www.python.org/" title="Visit Python.org">'
>>> make_tag('img', src='logo.png', alt='Python logo')
'<img src="logo.png" alt="Python logo">'

È anche possibile definire una funzione che accetta un numero


variabile di argomenti passati per nome (anche noti come keyword
argument): basta aggiungere ** immediatamente prima del nome di un
parametro (ad esempio **attrs). In questo esempio la funzione
accetta un argomento element seguito da un numero variabile di
argomenti passati per nome, che vengono salvati in un dizionario a
cui fa riferimento la variabile attrs. In questo esempio la
funzione crea una stringa combinando i nomi degli attributi e i
loro valori e la usa in combinazione con il nome dell’elemento per
creare tag HTML.
Ritorno di valori
La parola chiave return viene usata per restituire un valore
al chiamante, che può assegnarlo a una variabile o utilizzarlo per
altre operazioni:
>>> def square(n):
... return n**2
...
>>> x = square(5)
>>> x
25
>>> square(square(5))
625
>>> square(3) + square(4) == square(5)
True

Una funzione può contenere 0 o più return, e una volta che


un return viene eseguito, la funzione termina immediatamente.
Questo vuol dire che solo uno dei return viene eseguito ad ogni
chiamata:
>>> def abs(n):
... if n < 0:
... return -n # eseguito se n è negativo
... return n # eseguito se n è positivo
...
>>> abs(-5)
5
>>> abs(5)
5
64
return è in genere seguito dal valore di ritorno, ma è anche
possibile omettere il valore e usare return per terminare la
funzione: in questo caso None viene ritornato automaticamente. Se
si raggiunge il termine della funzione senza incontrare neanche
un return, None viene restituito automaticamente:
>>> def print_twice(text):
... if not text:
... # termina immediatamente se text è una stringa vuota
... return
... print(text)
... print(text)
... # ritorna None automaticamente al termine della funzione
...
>>> # stampa 2 volte e ritorna None al termine della funzione
>>> res = print_twice('Python')
Python
Python
>>> print(res)
None
>>> # entra nell'if e ritorna None prima di stampare
>>> res = print_twice('')
>>> print(res)
None

Nel caso sia necessario ritornare più valori, è possibile fare:


>>> def midpoint(x1, y1, x2, y2):
... """Return the midpoint between (x1; y1) and (x2; y2)."""
... xm = (x1 + x2) / 2
... ym = (y1 + y2) / 2
... return xm, ym
...
>>> x, y = midpoint(2, 4, 8, 12)
>>> x
5.0
>>> y
8.0

In questo caso il valore ritornato è sempre uno: una singola tupla


di 2 elementi. Python supporta un’operazione chiamata unpacking,
che ci permette di assegnare contemporaneamente diversi valori a
più variabili, permettendo quindi operazioni come la seguente:
x, y = midpoint(2, 4, 8, 12)
Copy
In tal modo, possiamo assegnare il primo valore della tupla a x e
il secondo a y.
Scope delle variabili
Tutti i parametri e le variabili create all’interno di una
funzione, sono locali alla funzione, cioè possono essere usate
65
solo da codice che si trova all’interno della funzione. Se
proviamo ad accedere a queste variabili dall’esterno della
funzione otteniamo un NameError:
>>> def calc_circle_area(r):
... pi = 3.14
... return pi * r**2
...
>>> calc_circle_area(5)
78.5
>>> r
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'r' is not defined
>>> pi
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'pi' is not defined

Le funzioni possono però accedere in lettura a valori globali,


cioè definiti fuori dalla funzione:
>>> pi = 3.14
>>> def calc_circle_area(r):
... return pi * r**2
...
>>> calc_circle_area(5)
78.5

Python segue una semplice regola di risoluzione dei nomi:


1. prima verifica se il nome esiste nel namespace locale;
2. se non esiste lo cerca nel namespace globale;
3. se non esiste neanche nel namespace globale, lo cerca tra gli
oggetti builtin.
Se un nome non è presente neanche tra gli oggetti builtin, Python
restituisce un NameError.
Ad esempio:
>>> pi = 3.14
>>> def print_circle_area(r):
... print(pi * r**2)
...
>>> print_circle_area(5)
78.5

La riga print(pi * r**2) contiene 3 nomi:


1. r è una variabile locale alla funzione: Python la trova subito
nel namespace locale;
2. pi è una variabile globale definita fuori dalla funzione: Python
non la trova nel namespace locale ma la trova in quello globale;
3. print è una funzione builtin: Python non la trova nel namespace
locale né in quello globale, ma la trova tra gli oggetti
builtin.
66
4.
È anche importante notare che variabili diverse in namespace
diversi possono riferirsi allo stesso oggetto, ad esempio:
>>> lista = [1, 2, 3, 4, 5]
>>> def add_elem(seq, elem):
... seq.append(elem)
...
>>> lista
[1, 2, 3, 4, 5]
>>> add_elem(lista, 6)
>>> lista
[1, 2, 3, 4, 5, 6]

Sia la variabile globale lista che la variabile locale seq fanno


riferimento alla stessa lista. Questo vuol dire che le modifiche
fatte alla lista dalla funzione add_elem() saranno visibili anche
all’esterno della funzione. Questo ovviamente può accadere solo
quando vengono passati a una funzione oggetti mutabili e quando la
funzione modifica l’oggetto che è stato passato.

67
Lezione 19di 40

Gestione delle eccezioni

In Python gli errori vengono riportati e gestiti usando


le eccezioni. Ogni volta che un programma esegue un’operazione non
valida, viene generata un’eccezione. Al contrario dei normali
valori di ritorno che possono essere restituiti usando return, le
eccezioni si propagano automaticamente finchè vengono catturate e
gestite; se non vengono gestite, il programma mostra un messaggio
di errore e termina. In questa lezione vedremo più in dettaglio
come catturare, gestire, e riportare eccezioni.
Tipi di eccezioni
Durante le precedenti lezioni abbiamo già visto diverse eccezioni,
come ad esempio SyntaxError, NameError, ValueError, TypeError,
ecc.:
>>> print 'Hello World!'
File "<stdin>", line 1
print 'Hello World!'
^
SyntaxError: Missing parentheses in call to 'print'
>>> test
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'test' is not defined
>>> int('five')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'five'
>>> list(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> 8 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Possiamo vedere che ogni eccezione ha un tipo (es. NameError) e un


messaggio che descrive l’errore (es. name 'test' is not defined).
Questi tipi sono organizzati in una gerarchia, che include
eccezioni più o meno specifiche che vengono utilizzate per
situazioni diverse. Per esempio, l’eccezione ZeroDivisionError è
un caso particolare di ArithmeticError, che è un sotto-tipo
di Exception, che a sua volta è un sotto-tipo di BaseException.
Possiamo anche notare che alcuni errori (i SyntaxError) non hanno
un traceback, mentre altri sì. Questo è dovuto al fatto che
i SyntaxError vengono riportati quando il codice che abbiamo
68
scritto non è valido e avvengono in fase di parsing, quindi prima
che l’interprete possa eseguire il codice. Gli altri errori invece
includono anche un traceback che riporta informazioni sulla
sequenza di operazioni che hanno portato all’errore durante
l’esecuzione del programma. Ad esempio:
>>> def a(x, y):
... return x / y
...
>>> def b(x, y):
... return a(x, y)
...
>>> def c(x, y):
... return b(x, y)
...
>>> c(8, 2)
4.0
>>> c(8, 0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in c
File "<stdin>", line 2, in b
File "<stdin>", line 2, in a
ZeroDivisionError: division by zero

In questo caso la funzione c chiama la funzione b, e la


funzione b chiama la funzione a. Se a restituisce un errore,
l’errore si propaga prima a b, poi a c e infine, visto che nessuno
ha gestito l’eccezione, il programma mostra un errore e termina.
Il traceback mostra questa sequenza di chiamate in ordine
cronologico (la chiamata più recente si trova alla fine).

Gestire le eccezioni
Abbiamo visto che diverse operazioni in Python possono restituire
un’eccezione:
>>> n = int('five')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'five'
Copy
Python ci da modo di catturare queste eccezioni e di gestirle,
mediante il try/except:
>>> try:
... n = int('five')
... except ValueError:
... print('Invalid number!')
...
Invalid number!

Possiamo notare che:


69
 La keyword try è seguita dai due punti (:) e da un blocco
indentato di codice
 Il blocco di codice contiene il codice che potrebbe generare
un’eccezione
 Dopo il primo blocco di codice, indentato allo stesso livello
del try abbiamo l’except, seguito dal nome dell’eccezione che
vogliamo catturare e dai due punti (:)
 Indentato sotto l’except abbiamo un blocco di codice usato per
gestire l’eccezione
Il funzionamento è semplice: se il codice nel blocco
del try genera un’eccezione del tipo specificato dall’except,
allora il blocco dell’except viene eseguito per gestirla. Se il
codice nel blocco del try non genera un’eccezione, o l’eccezione
generata non è del tipo specificato dall’except, allora
l’eccezione si propaga e il blocco dell’except viene ignorato.
È importante notare che l’except cattura tutte le eccezioni del
tipo specificato, ma anche tutti i suoi sotto-tipi, quindi se
specifichiamo un ArithmeticError nell’except,
l’except catturerà ArithmeticError, ma anche i suoi tre sotto-
tipi ZeroDivisionError, OverflowError, e FloatingPointError:
>>> try:
... n = 5 / 0
... except ArithmeticError:
... print('Invalid operation!')
...
Invalid operation!
Questa forma di try/except è quella più semplice, in realtà
il try/except è un costrutto molto flessibile e potente. Vediamo
alcuni altri esempi.
>>> try:
... n = 5 / 0
... except ZeroDivisionError as err:
... print('Invalid operation ({})!'.format(err))
...
Invalid operation (division by zero)!
Copy
È possibile aggiungere dopo l’except, la keyword as seguita dal
nome di una variabile (ad esempio err). Questo rende accessibile
l’errore all’interno del blocco di codice
dell’except permettendoci, tra le altre cose, di stamparlo.
>>> def try_except_else_test(x):
... try:
... n = int(x) # prova a convertire x in intero
... except ValueError:
... # eseguito in caso di ValueError
... print('Invalid number!')
... else:
... # eseguito se non ci sono errori
... print('Valid number!')
...

70
>>> try_except_else_test('five') # numero non valido: esegue
l'except
Invalid number!
>>> try_except_else_test('5') # numero valido: esegue l'else
Valid number!

È possibile aggiungere un else dopo l’except che viene chiamato se


il codice nel blocco del try viene eseguito senza che ritorni
nessuna eccezione.
>>> def try_except_except_test(x):
... try:
... n = int(x) # prova a convertire x in intero
... except ValueError:
... # eseguito in caso di ValueError
... print('Invalid number!')
... except TypeError:
... # eseguito in caso di TypeError
... print('Invalid type!')
... else:
... # eseguito se non ci sono errori
... print('Valid number!')
...
>>> try_except_except_test('five') # tipo valido ma valore
invalido: esegue il primo except
Invalid number!
>>> try_except_except_test([1, 2, 3]) # tipo invalido: esegue il
secondo except
Invalid type!
>>> try_except_except_test('5') # numero valido: esegue l'else
Valid number!

È possibile aggiungere più di un except in modo da gestire


eccezioni diverse in modo diverso. Quando il codice nel blocco
del try genera un’eccezione, Python eseguire il primo except che
specifica un’eccezione del tipo corretto.
>>> f = open('test.txt', 'w') # apre un file in scrittura
>>> try:
... f.read() # prova a leggere e fallisce
... finally:
... f.close() # il file viene chiuso nonostante l'errore
riportato
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
io.UnsupportedOperation: not readable
>>> f.closed # verifichiamo che il file sia chiuso
True
>>> f = open('test.txt') # riapriamo lo stesso file in lettura
(default)
>>> try:
... f.read() # proviamo a leggere (ora funziona senza errori)
... finally:
71
... f.close() # il file viene chiuso
...
''
>>> f.closed # verifichiamo che il file sia chiuso
True

Se vogliamo specificare una o più operazioni che vanno eseguite


sia in caso di errore che in caso di successo, possiamo aggiungere
un finally seguito dai due punti e da un blocco di codice
indentato che viene sempre eseguito. È anche possibile aggiungere
un finally dopo else/except. (Nella prossima lezione vedremo in
modo più approfondito i file.)
Riportare eccezioni
Oltre alla keyword return, usata per restituire un risultato, in
Python esiste anche la keyword raise usata per riportare
un’eccezione.
>>> def div(num, den):
... if den == 0:
... # se il denominatore è 0 riporta un'eccezione
... raise ZeroDivisionError('Impossibile dividere per 0')
... return num / den
...
>>> div(8, 2)
4.0
>>> div(8, 0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in div
ZeroDivisionError: Impossibile dividere per 0

Per riportare un’eccezione, bisogna prima di tutto creare un


oggetto Exception (o un sotto-tipo di Exception). Per farlo
chiamiamo l’eccezione passando un messaggio d’errore come stringa
(ad esempio ZeroDivisionError('Impossibile dividere per 0')). Una
volta che abbiamo creato questo oggetto, possiamo usare la
keyword raise per riportare l’eccezione. Quando il raise viene
eseguito, il flusso del programma viene interrotto, e l’eccezione
viene riportata al chiamante. Se nessuno cattura l’eccezione
usando un try/except, il programma termina con un messaggio
d’errore.
Il raise viene generalmente usato per riportare una nuova
eccezione, ma si può anche usare all’interno di un try/except per
consentire all’eccezione originale di propagarsi:
>>> try:
... 5 / 0
... except ZeroDivisionError as err:
... # stampa informazioni sull'errore
... print('* Logged exception:', err)
... print('* Re-raising exception.')
... raise # lascia che l'eccezione originale si propaghi
...
72
* Logged exception: division by zero
* Re-raising exception.
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

In questo caso è possibile usare semplicemente raise all’interno


dell’except senza specificare nessuna eccezione. Questo ci
consente di intercettare momentaneamente un’eccezione, eseguire
delle operazioni (ad esempio salvare l’errore su un file), e
lasciare che l’eccezione originale continui a propagarsi.
Infine è possibile concatenare eccezioni:
>>> try:
... 5 / 0
... except ZeroDivisionError:
... # cattura l'errore originale e ne riporta uno nuovo
... raise ValueError('Invalid denominator value!')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception
occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: Invalid denominator value!

Questo esempio ci mostra come sia possibile creare e riportare una


nuova eccezione all’interno di un except. Python mostrerà sia il
traceback dell’eccezione originale sia quello della nuova
eccezione.
È anche possibile creare nuovi tipi di eccezioni creando
delle sub-classi (dei sotto-tipi) di Exception, ma per poterlo
fare bisogna prima capire il funzionamento delle classi, che
verranno trattate in una lezione futura.

73
Lezione 20di 40

Gestire i file
Per permetterci di interagire con il filesystem, Python ci
fornisce la funzione built-in open(). Questa funzione può essere
invocata per aprire un file e ritorna un file object. Quest’ultimo
ci permette di eseguire diverse operazioni sul file, come ad
esempio la lettura e la scrittura. Quando abbiamo finito di
interagire con il file, dobbiamo infine ricordarci di chiuderlo,
usando il metodo file.close().
La funzione open
La funzione open() accetta diversi argomenti ma i due argomenti
più importanti sono il nome del file che vogliamo aprire, e
il modo di apertura.
Il nome del file deve essere una stringa che rappresenta un
percorso in grado di identificare la posizione del file nel
filesystem. Il percorso può essere relativo alla directory
corrente (ad esempio 'file.txt', 'subdir/file.txt', '../file.txt',
ecc.) o assoluto (ad esempio '/home/ezio/file.txt').
Il modo è opzionale, e il suo valore di default è la stringa 'r',
cioè read (lettura). Questo vuol dire che se non specifichiamo il
modo, Python aprirà il file in lettura. Se invece vogliamo poter
scrivere sul file, possiamo specificare come modo la stringa 'w',
cioè write (scrittura). Quando apriamo un file in scrittura,
specificando quindi il modo 'w', possono succedere due cose: se il
file non esiste, viene creato al percorso specificato; se esiste,
il contenuto del file viene eliminato.
>>> open('test.txt') # il file non esiste, quindi Python dà
errore (FileNotFoundError)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'test.txt'
>>>
>>> open('test.txt', 'w') # aprendolo in scrittura, il file viene
creato
<_io.TextIOWrapper name='test.txt' mode='w' encoding='UTF-8'>
>>>
>>> open('test.txt', 'r') # ora che è stato creato, possiamo
anche aprirlo in lettura
<_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'>
>>>
>>> open('test.txt') # se omettiamo il modo, il file viene aperto
in lettura ('r')
<_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'>

Se vogliamo continuare ad aggiungere contenuto alla fine di un


file, senza cancellare il contenuto esistente, possiamo usare la
stringa 'a' (append) come modo. Le stringhe 'r+' e 'w+' ci
permettono di leggere e scrivere contemporaneamente (come 'w',
74
anche 'w+' elimina il contenuto del file). Il modo 'x' (creazione
esclusiva) crea e apre un nuovo file in scrittura, restituendo un
errore (FileExistsError) se il file esiste già.
Esistono infine due modi di aprire file: il modo testuale (usato
per file di testo, come ad esempio file .txt e .html) e il
modo binario (usato per file binari, come ad esempio
immagini jpg o audio .mp3). Questi modi possono essere specificati
aggiungendo alla stringa del modo una t per i file testuali
(default, quindi può essere omessa) e una b per i file binari. Ad
esempio per aprire un file binario in lettura possiamo usare 'rb',
per aprire un file testuale in scrittura possiamo usare 'wt' o
semplicemente 'w'.
Riassumendo:
Modalità Descrizione

Apre un file di testo in lettura. Modo di apertura


'r' di default dei file.

Apre un file di testo in scrittura. Se il file non


esiste lo crea, altrimenti cancella il contenuto
'w' del file.

Apre un file di testo in append. Il contenuto


viene scritto alla fine del file, senza modificare
'a' il contenuto esistente.

Apre un file di testo in creazione esclusiva. Se


il file non esiste, restituisce un errore,
altrimenti apre in scrittura cancellando il
'x' contenuto del file.

Apre un file di testo in modifica. Permette di


'r+' leggere e scrivere contemporaneamente.

Apre un file di testo in modifica. Permette di


leggere e scrivere contemporaneamente. Cancella il
'w+' contenuto del file.

Di default, questi modi vengono usati per aprire file testuali, e


sono quindi equivalenti a 'rt', 'wt', 'at', 'xt', 'r+t', e 'w+t'.
Se invece vogliamo lavorare con file binari, è possibile
aggiungere una 'b' per specificare il modo binario, usando
quindi 'rb', 'wb', 'ab', 'xb', 'r+b', e 'w+b'.
I file object
Ora che abbiamo visto i diversi modi di invocare la
funzione open(), vediamo come possiamo interagire con i file
object che restituisce.
I file object hanno diversi attributi e metodi:

75
>>> f = open('test.txt', 'w') # apriamo il file test.txt in
scrittura
>>> f # open() ci restituisce un file object
<_io.TextIOWrapper name='test.txt' mode='w' encoding='UTF-8'>
>>> dir(f) # possiamo usare dir() per vedere l'elenco di
attributi e metodi
[..., '_CHUNK_SIZE', '_checkClosed', '_checkReadable',
'_checkSeekable', '_checkWritable',
'_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding',
'errors', 'fileno', 'flush',
'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read',
'readable', 'readline',
'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable',
'write', 'writelines']
>>> f.name # l'attributo .name corrisponde al nome del file
'test.txt'
>>> f.mode # l'attributo .mode corrisponde al modo di apertura
'w'
>>> f.closed # l'attributo .closed è True se il file è stato
chiuso, altrimenti False
False
>>> f.read # read è un metodo che, quando chiamato, legge e
ritorna il contenuto
<built-in method read of _io.TextIOWrapper object at 0xb67cb734>
>>> f.write # write è un metodo che, quando chiamato, ci consente
di scrivere nel file
<built-in method write of _io.TextIOWrapper object at 0xb67cb734>
>>> f.close # close è un metodo che, quando chiamato, chiude il
file,
<built-in method close of _io.TextIOWrapper object at 0xb67cb734>

Vediamo alcuni esempi:


>>> f = open('test.txt', 'w') # apriamo il file in scrittura
>>> f.write('prima riga del file\n') # scriviamo una riga nel
file
20
>>> f.write('seconda riga del file\n') # scriviamo un'altra riga
nel file
22
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> content = f.read() # leggiamo tutto il contenuto del file
>>> print(content)
prima riga del file
seconda riga del file
>>> f.close() # chiudiamo il file

I metodi file.read() e file.write() ci permettono di leggere e


scrivere in un file. Il metodo file.read() restituisce tutto il

76
contenuto di un file come stringa (o byte string), ma è anche
possibile passare come argomento un numero specifico di caratteri
(o bytes). Il metodo file.write() ci permette di aggiungere del
contenuto al file e restituisce il numero di caratteri (o byte)
scritti. In entrambi i casi è importante ricordarsi di chiudere il
file usando il metodo file.close().
>>> # definiamo una lista di righe
>>> lines = [
... 'prima riga del file\n',
... 'seconda riga del file\n',
... 'terza riga del file\n',
... ]
>>> f = open('test.txt', 'w') # apriamo il file in scrittura
>>> f.writelines(lines) # usiamo il metodo writelines per
scrivere le righe nel file
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> f.readlines() # usiamo il metodo readlines per ottenere una
lista di righe del file
['prima riga del file\n', 'seconda riga del file\n', 'terza riga
del file\n']
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> f.readline() # usiamo il metodo readline per ottenere una
singola riga del file
'prima riga del file\n'
>>> f.readline() # usiamo il metodo readline per ottenere una
singola riga del file
'seconda riga del file\n'
>>> f.readline() # usiamo il metodo readline per ottenere una
singola riga del file
'terza riga del file\n'
>>> f.readline() # quando abbiamo letto tutto, il metodo
restituisce una stringa vuota
''
>>> f.close() # chiudiamo il file
>>>
>>> # È possibile utilizzare un for per iterare sulle righe di un
file:
>>> f = open('test.txt') # riapriamo il file in lettura
>>> for line in f: # iteriamo sulle righe del file
... line
...
'prima riga del file\n'
'seconda riga del file\n'
'terza riga del file\n'
>>> f.close() # chiudiamo il file

I metodi file.readlines() e file.writelines() possono essere usati


per leggere e scrivere una lista di righe in un file. Il
77
metodo file.readline() ci permette di leggere una singola riga del
file. Se vogliamo leggere il contenuto di un file riga per riga,
possiamo semplicemente iterare sul file object usando un
ciclo for.
In questo esempio possiamo anche notare che quando abbiamo
chiamato ripetutamente il metodo file.readline(), abbiamo ottenuto
righe consecutive, invece che ottenere 3 volte la prima riga. Ogni
file object memorizza la posizione raggiunta durante la lettura
e/o scrittura, e ogni operazione successiva riprende dallo stesso
punto. Se eseguiamo letture successive, ogni lettura riprenderà
dalla posizione memorizzata al termine della lettura precedente.
Quando viene raggiunta la fine del file, le operazioni di lettura
restituiscono una stringa vuota. È anche possibile utilizzare i
metodifile.tell() e file.seek() per verificare e modificare la
posizione memorizzata dal file object:
>>> # definiamo una lista di righe
>>> lines = [
... 'prima riga del file\n',
... 'seconda riga del file\n',
... 'terza riga del file\n',
... ]
>>> f = open('test.txt', 'w') # apriamo il file in scrittura
>>> f.writelines(lines) # usiamo il metodo writelines per
scrivere le righe nel file
>>> f.seek(0, 0) # eseguiamo un seek per spostarci all'inizio del
file (il secondo 0 indica l'inizio)
0
>>> f.write('PRIMA') # scriviamo 'PRIMA' all'inizio del file
sovrascrivendo 'prima'
5
>>> f.seek(0, 2) # eseguiamo un seek per spostarci alla fine del
file (il 2 indica la fine)
62
>>> f.write('quarta riga del file\n') # aggiungiamo una riga alla
fine
21
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> f.readline() # usiamo il metodo readline per ottenere una
singola riga del file
'PRIMA riga del file\n'
>>> f.readline() # usiamo il metodo readline per ottenere
un'altra riga del file
'seconda riga del file\n'
>>> f.tell() # vediamo che la posizione nel file è avanzata
42
>>> f.read() # usiamo il metodo read per leggere il resto del
contenuto del file
'terza riga del file\nquarta riga del file\n'
>>> f.tell() # vediamo che la posizione nel file è avanzata
ulteriormente
78
83
>>> f.read() # quando abbiamo letto tutto, il metodo restituisce
una stringa vuota
''
>>> f.seek(0) # eseguiamo un seek per spostarci all'inizio del
file
0
>>> f.tell() # vediamo che la posizione è ritornata a 0
0
>>> f.readlines() # rileggiamo l'intero contenuto del file come
lista di stringhe
['PRIMA riga del file\n', 'seconda riga del file\n', 'terza riga
del file\n', 'quarta riga del file\n']
>>> f.close() # chiudiamo il file

La seguente tabella riassume i metodi più comuni dei file object:


metodo Descrizione

Legge e restituisce l’intero contenuto


file.read() del file come una singola stringa.

file.read(n) Legge e restituisce n caratteri (o byte).

file.readline() Legge e restituisce una riga del file.

Legge e restuisce l’intero contenuto del


file.readlines() file come lista di righe (stringhe).

Scrive nel file la stringa s e ritorna il


file.write(s) numero di caratteri (o byte) scritti.

file.writelines(lines) Scrive nel file la lista in righe lines.

Restituisce la posizione corrente


file.tell() memorizzata dal file object.

Modifica la posizione corrente


file.seek(offset, pos) memorizzata dal file object.

file.close() Chiude il file.

Il costrutto with
Abbiamo visto negli esempi precedenti, che ogni volta che apriamo
un file è anche necessario invocare il metodo file.close() per
chiuderlo. Così facendo, non solo siamo costretti a ripetere la
chiusura ogni volta, ma corriamo anche il rischio di
dimenticarcene. Inoltre, se il programma viene interrotto a causa
di un’eccezione, il file.close() potrebbe non essere mai chiamato.
Per risolvere questi (e altri) problemi, in Python esiste il
costrutto with. Questo costrutto può essere usato con dei context
79
manager (manager di contesti), cioè degli oggetti particolari che
specificano delle operazioni che vanno eseguite all’entrata e
all’uscita del contesto. I file object supportano il protocollo
dei context manager, e possono quindi essere usati con il with.
Vediamo un esempio pratico:
>>> f = open('test.txt', 'w') # creiamo il file object
>>> with f: # usiamo il file object come context manager nel with
... f.write('contenuto del file') # scriviamo il file
... f.closed # verifichiamo che il file è ancora aperto
...
18
False
>>> f.closed # verifichiamo che dopo il with il file è chiuso
True
Nell’esempio possiamo notare che:
 La parola chiave with è seguita da un oggetto che supporta il
protocollo dei context manager (in questo caso il file
object f).
 Dopo l’oggetto ci sono i due punti (:) e un blocco di codice
indentato.
 Prima di eseguire il blocco di codice indentato, il metodo
speciale f.__enter__() viene chiamato (nel caso dei file non fa
niente).
 Il blocco di codice viene eseguito: all’interno del blocco il
file è aperto e quindi possiamo scrivere sul file.
 Una volta eseguito il blocco di codice indentato, il metodo
speciale f.__exit__() viene chiamato (nel caso dei file chiude
il file).
 Una volta terminato il with verifichiamo che il file è stato
chiuso automaticamente (da f.__exit__()).
In altre parole, usando il with con i file object non dobbiamo più
preoccuparci di chiudere il file. Esiste anche una forma più
concisa per ottenere lo stesso risultato:
>>> with open('test.txt', 'w') as f:
... f.write('contenuto del file')
...
18
>>> f.closed
True
Questa forma del with ci permette di creare l’oggetto direttamente
e di assegnargli un nome dopo la keyword as. Anche in questo caso,
il with chiamerà automaticamente f.__exit__() che a sua volta
chiamerà f.close() e chiuderà il file automaticamente.
Il with ci garantisce la chiusura del file anche nel caso in cui
il programma venga interrotto da un’eccezione, e ci evita di dover
ripetere f.close() ogni volta. Il with può inoltre essere usato
anche con altri tipi di oggetti che supportano il protocollo dei
context manager, ed è anche possibile definire nuovi oggetti di
questo tipo.

80
Lezione 21di 40

Moduli

I moduli, anche conosciuti come librerie in altri linguaggi, sono


dei file usati per raggruppare costanti, funzioni e classi, che ci
consentono di suddividere e organizzare meglio i nostri progetti.
Python include già una lista estensiva di moduli standard (anche
conosciuti come standard library), ma è anche possibile scaricarne
o definirne di nuovi.
Importare moduli
Prima di poter accedere ai contenuti di un modulo, è necessario
importarlo. Per farlo, usiamo il costrutto import:
>>> import math # importiamo il modulo math della libreria
standard
>>> math # l'import crea una nuova variabile che si riferisce al
modulo
<module 'math' (built-in)>
>>> help(math) # possiamo usare help() per vedere la
documentazione del modulo
Help on built-in module math:
NAME
math

DESCRIPTION
This module is always available. It provides access to the
mathematical functions defined by the C standard.
FUNCTIONS
...
DATA
e = 2.718281828459045
inf = inf
nan = nan
pi = 3.141592653589793
FILE
(built-in)
>>> dir(math) # possiamo usare dir() per vedere il contenuto del
modulo
['__doc__', '__loader__', '__name__', '__package__', '__spec__',
'acos', 'acosh', 'asin',
'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos',
'cosh', 'degrees', 'e',
'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor',
'fmod', 'frexp', 'fsum',
'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf',
'isnan', 'ldexp',
'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi',
'pow', 'radians',
'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']

81
>>>
>>> math.pi # math.pi è una costante che corrisponde al valore di
pi greco
3.141592653589793
>>> math.sqrt # math.sqrt è una funzione in grado di calcolare la
radice quadrata
<built-in function sqrt>
>>> help(math.sqrt) # possiamo usare help() per vedere la
documentazione della funzione
Help on built-in function sqrt in module math:
sqrt(...)
sqrt(x)
Return the square root of x.
>>> math.sqrt(64) # possiamo chiamarla per calcolare la radice
quadrata di 64
8.0

Come si vede nell’esempio, la forma più semplice del


costrutto import e import nome_modulo. In questo caso abbiamo
importato il modulo della standard library chiamato math, che
include diverse costanti e funzioni matematiche. Dopo l’esecuzione
dell’import, Python ha definito una nuova variabile
chiamata math che si riferisce al modulo math. Possiamo quindi
usare le funzioni dir() e help() per esplorare i contenuti del
modulo, e ottenere quindi una lista di nomi definiti nel modulo e
la loro documentazione. Per accedere a questi nomi, possiamo usare
la sintassi modulo.nome: ad esempio math.pi è una costante che
rappresenta il valore di pi greco, mentre math.sqrt è una funzione
che può essere usata per calcolare la radice quadrata (square
root) di un numero.
Questa forma di import ci consente di accedere a qualsiasi nome
definito all’interno del modulo, ma per farlo dobbiamo sempre
usare la sintassi modulo.nome. È anche possibile importare nomi
direttamente usando la sintassi from modulo import nome:
>>> from math import pi, sqrt # importa solo i nomi pi e sqrt dal
modulo math
>>> pi # è ora possibile accedere a pi senza usare math.pi
3.141592653589793
>>> sqrt # è ora possibile accedere a sqrt senza usare math.sqrt
<built-in function sqrt>

Questa forma di import ci permette di importare uno o più nomi


direttamente, evitandoci quindi di ripetere il nome del modulo
ogni volta. Tuttavia, ciò non ci consente di accedere agli altri
nomi del modulo e può risultare ambigua se il nome della funzione
non è abbastanza chiaro. Quest’ultimo problema può essere risolto
usando i costrutti import modulo as nuovonome e from modulo import
nome as nuovonome:
>>> import math as matematica # importa il modulo math
chiamandolo matematica
>>> matematica.pi # possiamo accedere agli oggetti di math
facendo matematica.nome
82
3.141592653589793
>>>
>>> from math import sqrt as radice_quadrata # importa sqrt
chiamandolo radice_quadrata
>>> radice_quadrata # radice_quadrata si riferisce lo stesso a
sqrt
<built-in function sqrt>
>>> radice_quadrata(64) # e funziona come sqrt originale, ma ha
un nome diverso
8.0

Questa tecnica può essere usata anche nel caso in cui i nomi siano
particolarmente lunghi o per evitare conflitti tra nomi simili o
uguali che appartengono a moduli diversi.
Esiste infine un’altra forma che ci permette di importare tutti i
nomi di un modulo, usando la sintassi from modulo import *:
>>> from math import * # importa tutti i nomi definiti nel modulo
math
>>> pi # possiamo ora accedere direttamente a tutti i nomi di
math
3.141592653589793
>>> e # dalle costanti
2.718281828459045
>>> sqrt # alle funzioni
<built-in function sqrt>
>>> sin
<built-in function sin>
>>> cos
<built-in function cos>
>>> tan
<built-in function tan>
Copy
Anche se possibile, questa forma di import è generalmente
sconsigliata.
Per riassumere:
 import modulo va bene quando abbiamo bisogno di accedere a
diversi nomi di un modulo senza doverli importare
individualmente e a quando vogliamo sapere esplicitamente a che
modulo appartiene ogni funzione che chiamiamo (ad
esempio math.pi ci dice esplicitamente che stiamo accedendo alla
costante matematica pi);
 from modulo import nome va bene quando abbiamo bisogno di
accedere a un numero limitato di nomi, e quando questi nomi sono
abbastanza chiari da evitare ambiguità (ad esempio una
funzione search potrebbe cercare diverse cose a seconda del
modulo, mentre la funzione sqrt serve chiaramente a calcolare la
radice quadrata);

83
 import modulo as nuovonome e from modulo import nome as
nuovonome va bene quando i nomi che vogliamo importare sono
ambigui o particolarmente lunghi;
 from modulo import * può andare bene dall’interprete interattivo
(se ad esempio vogliamo usarlo come una potente calcolatrice
facendo from math import *) o quando conosciamo esattamente il
contenuto del modulo, abbiamo bisogno di tutti i nomi, e siamo
sicuri che questi nomi non creano conflitti con nomi esistenti
(se ad esempio un modulo contiene una funzione chiamata open,
l’esecuzione di from modulo import * andrà a sovrascrivere la
funzione builtin open).
Quando importiamo un modulo, Python deve trovare il file
corrispondente, e per farlo controlla in ordine le directory
elencate nella lista sys.path. Una volta trovato il file, Python
lo importa, crea un module object (oggetto modulo), e lo salva nel
dizionario sys.modules. Se il modulo viene importato nuovamente in
un altro punto del programma, Python è in grado di recuperare il
modulo da sys.modules senza doverlo importare nuovamente. Inoltre,
i moduli .py vengono compilati in bytecode: un formato più
efficiente che viene salvato in file con estensione .pyc che a
loro volta vengono salvati in una cartella chiamata __pycache__.
Quando viene eseguito un programma che ha bisogno di importare un
modulo, se modulo.pyc esiste già ed è aggiornato, allora Python
importerà quello invece di ricompilare il modulo in bytecode ogni
volta.
La libreria standard e PyPI
Oltre al modulo math che abbiamo visto nei precedenti esempi,
Python include già dozzine di moduli che coprono la maggior parte
delle operazioni più comuni. Tutti questi moduli sono già presenti
in Python e possono essere importati direttamente senza dover
scaricare nulla. Alcuni tra i moduli più comunemente usati, divisi
per aree, sono:
 elaborazione di testo: re (che fornisce supporto per le
espressioni regolari)
 tipi di dato: datetime (per rappresentare date e
ore), collections (diversi tipi di oggetti
contenitori), enum (per enumerazioni)
 moduli numerici e matematici: math (funzioni
matematiche), decimal (supporto per aritmetica a virgola
mobile), random (generazione di numeri pseudo-
casuali), statistics (funzioni statistiche)
 programmazione funzionale: itertools (funzioni per lavorare con
gli iteratori), functools (funzioni di ordine superiore)
 accesso a file e directory: pathlib (oggetti per la
rappresentazione e manipolazione di file e
directory), shutil (operazioni di alto livello sui file)
 persistenza di dati: sqlite3 (interfaccia a database SQLite)
 compressione e archiviazione di dati: zipfile (supporto per
archivi ZIP), tarfile (supporto per archivi tar), gzip (supporto
per file gzip)

84
 formati di file: csv (supporto per file
CSV), configparser (supporto per file CFG/INI)
 servizi generici del sistema operativo: os (diverse interfacce
del sistema operativo), io (strumenti per lavorare con file e
stream), time (funzioni relative al tempo), argparse (per
passare argomenti ricevuti dalla linea di
comando), logging (funzioni e classi per il logging)
 esecuzione concorrente: subprocess (gestione di sotto-
processi), multiprocessing (parallelismo basato su
processi), threading (parallismo basato su thread)
 comunicazione tra processi e networking: asyncio (I/O
asincrono), socket (interfaccia di rete di basso
livello), ssl (wrapper TLS/SSL per i socket)
 gestione di formati di internet: json (supporto per file
JSON), email (supporto per email)
 elaborazione di formati di markup: il package html (strumenti
per lavorare con HTML), il package xml (strumenti per lavorare
con xml)
 protocolli internet: il package urllib (gestione di
URL), httplib (strumenti per lavorare con il protocollo
HTTP), ftplib (strumenti per lavorare con FTP)
 internazionalizzazione: gettext (strumenti per supportare
linguaggi multipli)
 interfacce grafiche: il package tkinter (interfaccia con Tcl/Tk)
 tool per lo sviluppo: unittest (strumenti per testare il codice)
 debugging e profiling: pdb (debugger per
Python), timeit (strumenti per misurare il tempo di esecuzione
di brevi pezzi di codice), cProfile (profiler per identificare
le parti più lente di un programma)
 servizi di runtime: sys (funzioni e parametri di
sistema), contextlib (strumenti per lavorare con i context
manager)
Nella documentazione ufficiale possiamo trovare l’elenco completo
dei moduli della libreria standard di Python.
Se invece necessitiamo di moduli che non sono inclusi nella
libreria standard, possiamo consultare PyPI: il Python Package
Index. PyPI è un repository che include decine di migliaia di
moduli che possono essere scaricati e installati in modo molto
semplice usando un tool chiamato pip, che approfondiremo in una
apposita lezione successiva.

85
Lezione 22di 40
Creare nuovi moduli

Abbiamo visto come utilizzare i moduli su Python, al fine di


estenderne le funzionalità. È però altresì possibile creare nuovi
moduli personalizzati: in questa lezione vedremo come.
In Python, non esiste una vera distinzione tra i moduli/librerie e
il file principale che viene eseguito (anche conosciuto in altri
linguaggi come main). Qualsiasi file con estensione .py può essere
sia eseguito che importato. Per esempio, possiamo creare un file
che definisce le quattro operazioni aritmetiche di base:
# aritmetica.py
def add(a, b):
return a + b
def sub(a, b):
return a - b

def mul(a, b):


return a * b
def div(a, b):
return a / b

Dopo aver salvato questo file come aritmetica.py, possiamo


importarlo dall’interprete interattivo o da un altro modulo:
>>> import aritmetica # importiamo il modulo
>>> aritmetica # viene creata la variabile aritmetica che si
riferisce al modulo
<module 'aritmetica' from '/home/ezio/aritmetica.py'>
>>> aritmetica.add # possiamo accedere alle funzioni definite
all'interno del modulo
<function add at 0xb70d9854>
>>> aritmetica.add(3, 5) # e possiamo chiamarle
8
>>> aritmetica.mul
<function mul at 0xb70d98e4>
>>> aritmetica.mul(3, 5)
15

Se proviamo a eseguire il file, non otteniamo nessun output ma


neanche nessun errore. Questo accade perché aritmetica.py non
esegue nessuna operazione che produce output, ma semplicemente
definisce quattro funzioni senza mai chiamarle:
$ python3 aritmetica.py
$

In Python esiste una variabile speciale chiamata __name__, il cui


valore è la stringa '__main__' se il file è stato eseguito
direttamente, o una stringa che rappresenta il nome del modulo se
86
il file è stato importato. Il valore di questa variabile speciale
può quindi essere usato per determinare se il file è stato
eseguito o importato, usando un idioma molto comune in Python:
if __name__ == '__main__':
# il file è stato eseguito direttamente

Ad esempio, aggiungendo questo if alla fine di aritmetica.py,


possiamo trasformarlo in un programma interattivo che può essere
eseguito direttamente:
# aritmetica.py
def add(a, b):
return a + b
def sub(a, b):
return a - b
def mul(a, b):
return a * b
def div(a, b):
return a / b
if __name__ == '__main__':
import sys # importiamo il modulo sys della libreria standard
# definiamo un dict di operazioni che mappa i nomi con le
funzioni corrispondenti
ops = dict(add=add, sub=sub, mul=mul, div=div)
# chiediamo all'utente di scegliere l'operazione
choice = input("Seleziona un'operazione [add/sub/mul/div]: ")
if choice not in ops:
# se la scelta non è valida terminiamo il programma con un
messaggio d'errore
sys.exit('Operazione non valida!')
# assegnamo a op la funzione scelta dall'utente
op = ops[choice]
try:
# chiediamo all'utente di inserire i due valori, e
proviamo a convertirli in float
a = float(input('Inserisci il primo valore: '))
b = float(input('Inserisci il secondo valore: '))
except ValueError as err:
# se la conversione fallisce terminiamo il programma con
un messaggio d'errore
sys.exit('Valore non valido: {}'.format(err))
# stampiamo il risultato dell'operazione
print('Il risultato è:', op(a, b))

Se ora proviamo a eseguire aritmetica.py direttamente, il valore


di __name__ sarà '__main__', quindi il contenuto dell’if verrà
eseguito:
$ python3 aritmetica.py
Seleziona un'operazione [add/sub/mul/div]: add
Inserisci il primo valore: 3
Inserisci il secondo valore: 5
Il risultato è: 8.0
$ python3 aritmetica.py
87
Seleziona un'operazione [add/sub/mul/div]: mul
Inserisci il primo valore: 3
Inserisci il secondo valore: 5
Il risultato è: 15.0
$ python3 aritmetica.py
Seleziona un'operazione [add/sub/mul/div]: pow
Operazione non valida!
$ python3 aritmetica.py
Seleziona un'operazione [add/sub/mul/div]: sub
Inserisci il primo valore: 3
Inserisci il secondo valore: x
Valore non valido: could not convert string to float: 'x'

Se invece importiamo il modulo, il valore


di __name__ sarà 'aritmetica' e quindi l’if non verrà eseguito.
Questo ci permette di avere file che funzionano sia quando
eseguiti direttamente che quando importati come moduli.

Lezione 23di 40
Package
Oltre ai moduli, Python offre un ulteriore livello di
organizzazione: i package. Un package è una raccolta di moduli,
che in genere corrisponde alla directory che li contiene. Prima di
poter importare i moduli da una directory, è necessario aggiungere
un file vuoto chiamato __init__.py all’interno della directory.
Vediamo ad esempio la struttura che potrebbe avere un ipotetico
progetto Python:
ProgettoPython/
|- main.py
`- progetto/
|- __init__.py
|- core.py
|- utils.py
|- gui/
| |- __init__.py
| |- widgets.py
| `- windows.py
`- test/
|- __init__.py
|- test_core.py
|- test_utils.py
|- test_widgets.py
`- test_windows.py

88
La directory che contiene il nostro progetto si
chiama ProgettoPython e contiene un file main.py e il
package progetto. La directory progetto contiene un
file __init__.py che la rende un package importabile da Python,
oltre ai file core.py e utils.py e le sottocartelle gui e test. Le
sottocartelle contengono a loro volta due file __init__.py che le
rendono parte del package, e diversi altri file .py.
In questo caso la root (radice) del package è progetto, e per
poterla importare dobbiamo garantire che la cartella che lo
contiene (cioè ProgettoPython) sia inclusa in sys.path. Possiamo
farlo sia lanciando python3 direttamente dalla
cartella ProgettoPython (la cartella corrente viene aggiunta
automaticamente a sys.path), oppure aggiungendo il percorso
completo della directory (es. /home/ezio/ProgettoPython) alla
variabile di ambiente PYTHONPATH.
Possiamo ora identificare i package e sub-package all’intero
degli import, separandoli con un .. Ad esempio, per
importare utils possiamo scrivere import progetto.utils o from
progetto import utils. Se vogliamo importare una funzione
da utils, possiamo usare from progetto.utils import funzione. Se
vogliamo importare un widget da widgets, possiamo usare from
progetto.gui.widgets import widget.

Lezione 24di 40

La programmazione ad oggetti
In uno dei capitoli introduttivi di questa guida abbiamo visto che
Python è un linguaggio multi-paradigma, che supporta cioè sia la
programmazione procedurale (che fa uso delle funzioni), sia la
programmazione funzionale (includendo iteratori e generatori), sia
la programmazione ad oggetti (includendo funzionalità come
l’ereditarietà singola e multipla, l’overloading degli operatori,
e il duck typing).
In questo capitolo introdurremo i concetti della programmazione
orientata agli oggetti, e vedremo alcuni semplici esempi. Nei
capitoli successivi vedremo più in dettaglio come usare gli
oggetti in Python, la sintassi necessaria per crearli, e le
operazioni che supportano.
Gli oggetti
Mentre nella programmazione procedurale le funzioni (o procedure)
sono l’elemento organizzativo principale, nella programmazione ad
oggetti (anche conosciuta come OOP, ovvero object-Oriented
Programming) l’elemento organizzativo principale sono gli oggetti.
Nella programmazione procedurale, i dati e le funzioni sono
separate, e questo può creare una serie di problemi, tra cui:
 è necessario gestire dati e funzioni separatamente;

89
 è necessario importare le funzioni che vogliamo usare;
 è necessario passare i dati alle funzioni;
 è necessario verificare che i dati e le funzioni siano
compatibili;
 è più difficile estendere e modificare le funzionalità;
 il codice è più difficile da mantenere;
 è più facile introdurre bug.
Nella programmazione ad oggetti, gli oggetti svolgono la funzione
di racchiudere in un’unica unità organizzativa sia i dati che il
comportamento. Questo ha diversi vantaggi:
 dati e funzioni sono raggruppati;
 è facile sapere quali operazioni possono essere eseguite sui
dati;
 non è necessario importare funzioni per eseguire queste
operazioni;
 non è necessario passare i dati alle funzioni;
 le funzioni sono compatibili con i dati;
 è più facile estendere e modificare le funzionalità;
 il codice è più semplice da mantenere;
 è più difficile introdurre bug.
Vediamo un semplice esempio: abbiamo la base e l’altezza di 100
diversi rettangoli e vogliamo sapere area e perimetro di ogni
rettangolo. Usando un approccio procedurale, possiamo risolvere il
problema creando due funzioni separate che accettano base e
altezza:
>>> # definiamo due funzioni per calcolare area e perimetro
>>> def calc_rectangle_area(base, height):
... """Calculate and return the area of a rectangle."""
... return base * height
...
>>> def calc_rectangle_perimeter(base, height):
... """Calculate and return the perimeter of a rectangle."""
... return (base + height) * 2
...

Possiamo poi creare una lista di tuple casuali (base, altezza),


iterarla con un for e passare i valori alle funzioni:
>>> from random import randrange
>>> # creiamo una lista di 100 tuple (base, altezza) con valori
casuali
>>> rects = [(randrange(100), randrange(100)) for x in range(100)]
>>> rects
[(16, 39), (92, 96), (60, 72), (99, 32), (39, 5), (43, 6), (51,
28), ...]
>>> # iteriamo la lista di rettangoli e printiamo
>>> # base, altezza, area, perimetro di ogni rettangolo
>>> for base, height in rects:
90
... print('Rect:', base, height)
... print(' Area:', calc_rectangle_area(base, height))
... print(' Perimeter:', calc_rectangle_perimeter(base,
height))
...
Rect: 16 39
Area: 624
Perimeter: 110
Rect: 92 96
Area: 8832
Perimeter: 376
Rect: 60 72
Area: 4320
Perimeter: 264
...

Usando la programmazione orientata agli oggetti, possiamo invece


creare una classe che rappresenta l’oggetto rettangolo. Invece che
rappresentare i rettangoli come una lista di tuple, usiamo la
classe per creare 100 istanze della classe Rettangolo, e invece
che chiamare le funzioni passando la base e l’altezza, chiamiamo
i metodi dell’istanza:
>>> # definiamo una classe che rappresenta un rettangolo generico
>>> class Rectangle:
... def __init__(self, base, height):
... """Initialize the base and height attributes."""
... self.base = base
... self.height = height
... def calc_area(self):
... """Calculate and return the area of the rectangle."""
... return self.base * self.height
... def calc_perimeter(self):
... """Calculate and return the perimeter of a
rectangle."""
... return (self.base + self.height) * 2
...

Nella prossima lezione vedremo piu in dettaglio come definire le


classi, ma come possiamo vedere dal prossimo esempio, creare e
usare istanze è abbastanza intuitivo:
>>> # creiamo un'istanza della classe Rectangle con base 3 e
altezza 5
>>> myrect = Rectangle(3, 5)
>>> myrect.base # l'istanza ha una base
3
>>> myrect.height # l'istanza ha un'altezza
5
>>> myrect.calc_area() # è possibile calcolare l'area
direttamente
15

91
>>> myrect.calc_perimeter() # e anche il perimetro
16

Ora che abbiamo un’idea di base del funzionamento delle classi,


possiamo creare i 100 rettangoli e calcolare area e perimetro:
>>> from random import randrange
>>> # creiamo una lista di 100 istanze di Rectangle con valori
casuali
>>> rects = [Rectangle(randrange(100), randrange(100)) for x in
range(100)]
>>> # iteriamo la lista di rettangoli e printiamo
>>> # base, altezza, area, perimetro di ogni rettangolo
>>> for rect in rects:
... print('Rect:', rect.base, rect.height)
... print(' Area:', rect.calc_area())
... print(' Perimeter:', rect.calc_perimeter())
...
Rect: 78 44
Area: 3432
Perimeter: 244
Rect: 0 85
Area: 0
Perimeter: 170
Rect: 32 2
Area: 64
Perimeter: 68

Come possiamo vedere confrontando i due esempi, usando la


programmazione ad oggetti possiamo lavorare direttamente con
oggetti singoli (le istanze di Rectangle). La lista non contiene
più tuple, ma rettangoli, e per calcolare area e perimetro non è
più necessario passare la base e l’altezza esplicitamente.
Inoltre, calc_area() e calc_perimeter() sono associati
all’istanza, e quindi non è necessario importare le funzioni, non
si rischia di usare la funzione sbagliata (ad esempio una funzione
che calcola l’area di un triangolo), non si rischia di passare la
base o l’altezza del rettangolo sbagliato o passarli nell’ordine
sbagliato.
Termini e concetti
Nei precedendi esempi abbiamo introdotto alcuni termini e concetti
nuovi che sono usati comunemente nella programmazione ad oggetti.
Classi
Le classi sono usate per definire le caratteristiche di un
oggetto, i suoi attributi (ad esempio la base e l’altezza) e i
suoi metodi (ad esempio calc_area() e calc_perimeter()). Le classi
sono “astratte” – non si riferiscono a nessun oggetto specifico,
ma rappresentano un modello che può essere usato per
creare istanze. Ad esempio la classe Rectangle specifica che i

92
rettangoli hanno una base, un’altezza, un’area e un perimetro, ma
la classe non si riferisce a nessun rettangolo in particolare.
Istanze
Le istanze sono oggetti creati a partire da una classe. Ad
esempio Rectangle(3, 5) ci restituisce un’istanza della
classe Rectangle che si riferisce a uno specifico rettangolo che
ha base 3 e altezza 5. Una classe può essere usata per creare
diverse istanze dello stesso tipo ma con attributi diversi, come i
100 diversi rettangoli che abbiamo visto nell’esempio precedente.
È possibile usare i metodi definiti dalla classe con ogni istanza,
semplicemente facendo istanza.metodo() (ad
esempio myrect.calc_area()).
Attributi
Gli attributi sono dei valori associati all’istanza, come ad
esempio la base e l’altezza del rettangolo. Gli attributi di ogni
istanza sono separati: ogni istanza di Rectangle ha una base e
un’altezza diversa. Per accedere a un attributo basta
fare istanza.attributo (ad esempio myrect.base).
Metodi
I metodi descrivono il comportamento dell’oggetto, sono simili
alle funzioni, e sono specifici per ogni classe. Ad esempio sia la
classe Rectangle che la classe Triangle possono definire un metodo
chiamato calc_area(), che ritornerà risultati diversi in base al
tipo dell’istanza. I metodi possono accedere altri attributi e
metodi dell’istanza: questo ci permette ad esempio di
chiamare myrect.calc_area() senza dover passare la base e
l’altezza esplicitamente. Per chiamare un metodo basta
fare istanza.metodo() (ad esempio myrect.calc_area()).
Ereditarietà
Un altro concetto importante della programmazione è
l’ereditarietà. L’ereditarietà ci permette di creare una nuova
classe a partire da una classe esistente e di estenderla o
modificarla.
Per esempio possiamo creare una classe Square che eredita dalla
classe Rectangle. Dato che i 4 lati di un quadrato hanno la stessa
lunghezza, non è più necessario richiedere base e altezza
separatamente, quindi nella classe Square possiamo modificare
l’inizializzazione in modo da richiedere la lunghezza di un
singolo lato. Così facendo possiamo definire una nuova classe che
invece di accettare e definire i due
attributi base e height definisce e accetta un singolo
attributo side. Dato che il quadrato è un tipo particolare di
rettangolo, i metodi per calcolare area e perimetro funzionano
senza modifiche e possiamo quindi
utilizzare calc_area() e calc_perimeter() ereditati
automaticamente dalla classe Rectangle senza doverli ridefinire.
È inoltre possibile definire gerarchie di classi, ad esempio si
può definire la clase Husky che eredita dalla classe Cane che
93
eredita dalla classe Mammifero che eredita dalla classe Animale.
Ognuna di queste classi può definire attributi e comportamenti
comuni a tutti gli oggetti di quella classe, e le sottoclassi
possono aggiungerne di nuovi.
Python supporta anche l’ereditarietà multipla: è possibile
definire nuovi classi che ereditano metodi e attributi da diverse
altre classi, combinandoli.
Nella prossima lezione vedremo esempi concreti di utilizzo
dell’ereditarietà.
Superclassi e sottoclassi
Se la classe Square eredita dalla classe Rectangle, possiamo dire
che Rectangle è la superclasse (o classe base – base class in
inglese) mentre Square è la sottoclasse (o subclasse).
L’operazione di creare una sottoclasse a partire da una classe
esistente è a volte chiamata subclassare.
Overloading degli operatori
In Python, le classi ci permettono anche di ridefinire il
comportamento degli operatori: questa operazione è
chiamata overloading degli operatori. È possibile definire metodi
speciali che vengono invocati automaticamente quando un operatore
viene usato con un’istanza della classe.
Ad esempio possiamo definire che myrect1 <
myrect2 ritorni True quando l’area di myrect1 è inferiore a quella
di myrect2, oppure possiamo definire che myrect1 + myrect2 ritorni
un nuova istanza di Rectangle creata dalla combinazione
di myrect1 e myrect2.
Quando usare la programmazione ad oggetti
Anche se la programmazione ad oggetti è uno strumento molto utile
e potente, non è la soluzione a tutti i problemi. Spesso creare
una semplice funzione è sufficiente e non è necessario definire
una classe.
In genere la programmazione ad oggetti può essere la soluzione
adatta se:
 la classe che vogliamo creare rappresenta un oggetto
(es. Rectangle, Person, Student, Window, Widget, Connection,
ecc.);
 vogliamo associare all’oggetto sia dati che comportamenti;
 vogliamo creare diverse istanze della stessa classe.
La programmazione ad oggetti potrebbe non essere la soluzione
migliore se:
 la classe che vogliamo creare non rappresenta un oggetto, ma ad
esempio un verbo (es. Find, Connect, ecc.);
 vogliamo solo rappresentare dati (meglio usare una struttura
dati come list, dict, namedtuple, ecc.) o solo comportamenti
(meglio usare funzioni, eventualmente raggruppate in un modulo
separato);

94
 vogliamo creare una sola istanza della stessa classe (meglio
usare un modulo per raggruppare dati e funzioni).
Ovviamente ci sono anche delle eccezioni (ad esempio il
pattern singleton, che definisce una classe che prevede una sola
istanza). Python è un linguaggio multiparadigma, ed è quindi
importante scegliere il paradigma che meglio si adatta alla
situazione.
Nella prossima lezione vedremo in dettaglio come definire e usare
classi in Python.

Lezione 25di 40

Classi in Python

Nella precedente lezione abbiamo introdotto i concetti principali


della programmazione ad oggetti e abbiamo visto alcuni semplici
esempi in Python. In questa lezione vedremo in maggiore dettaglio
come definire classi, metodi e attributi, e come usarli. Vedremo
inoltre come creare istanze e sottoclassi.
Definire classi
La classe più semplice che possiamo definire in Python è:
>>> class Test:
... pass
...

Come possiamo vedere dall’esempio, per definire una classe basta


usare la parola chiave class, seguita dal nome che vogliamo dare
alla classe (in questo caso Test), seguita dai due punti (:),
seguita infine da un blocco di codice indentato (in questo caso
c’è solo il pass).
Questa sintassi è simile a quella usata per definire le funzioni,
con la differenza che si usa la parola chiave class invece
di def e che la lista di argomenti dopo il nome non è presente. È
inoltre importante notare che, per convenzione, i nomi delle
classi generalmente usano il CamelCase, fatta eccezione per alcuni
tipi di base come int, str, list, dict, ecc.
Una volta definita la classe Test, possiamo verificare che il
nome Test fa riferimento alla classe. Possiamo quindi usare la
classe per creare diverse istanze, semplicemente usando le () per
“chiamarla”:
>>> Test # Test si riferisce alla classe
95
<class '__main__.Test'>
>>> Test() # Test() ritorna una nuova istanza
<__main__.Test object at 0x7f87ed6a26d8>
>>> Test() # Test() ritorna una nuova istanza
<__main__.Test object at 0x7f87ed6a26a0>

Ogni chiamata ci restituisce una nuova e separata istanza, come si


può vedere dal diverso ID delle due istanze create nell’esempio
(0x7f87ed6a26d8 e 0x7f87ed6a26a0).
Si noti che in Python 2 è importante definire le classi usando la
sintassi class Test(object): ... per fare in modo che ereditino
da object. In Python 3 tutte le classi ereditano automaticamente
da object.
Metodi
Nella precedente lezione abbiamo anche visto che i metodi sono
simili a funzioni definite all’interno della classe:
>>> # definiamo una classe Test
>>> class Test:
... # definiamo un metodo che stampa un messaggio
... def method(self):
... print('method called')
...
>>> inst = Test() # creiamo un'istanza di Test
>>> inst.method() # chiamiamo il metodo dell'istanza
method called

Da questo esempio possiamo notare diverse cose:


 le definizioni dei metodi si trovano indentate all’interno della
classe;
 la sintassi usata per definire i metodi è uguale a quella usata
per definire le funzioni;
 i metodi devono definire un parametro aggiuntivo che per
convenzione è chiamato self;
 per chiamare un metodo basta usare la sintassi istanza.metodo().
self
Come si nota nell’esempio precedente, la differenza più importante
tra la definizione di metodi e funzioni, è la presenza
del self. self è un argomento che si riferisce all’istanza, e
anche se i metodi devono dichiararlo esplicitamente, non è
necessario passarlo esplicitamente. Vediamo un altro esempio per
capire meglio il funzionamento di self:
>>> # definiamo una classe Test
>>> class Test:
... # definiamo un metodo che stampa self
... def method(self):
... print('self is:', self)
...
>>> inst = Test() # creiamo un'istanza di Test
96
>>> inst # mostriamo l'id dell'istanza
<__main__.Test object at 0x7fef42c03f28>
>>> inst.method() # verifichiamo che self corrisponde all'istanza
self is: <__main__.Test object at 0x7fef42c03f28>

Il motivo per cui non è necessario passare il self esplicitamente


è che l’espressione inst.method() è semplicemente zucchero
sintattico per Test.method(inst):
>>> Test.method # Test.method è una funzione definita nella
classe
<function Test.method at 0x7fef42c27840>
>>> inst.method # inst.method è un metodo legato all'istanza
<bound method Test.method of <__main__.Test object at
0x7fef42c03f28>>
>>> Test.method(inst) # possiamo chiamare Test.method e passare
inst
self is: <__main__.Test object at 0x7fef42c03f28>
>>> inst.method() # possiamo chiamare inst.method direttamente
self is: <__main__.Test object at 0x7fef42c03f28>

Entrambi i modi producono lo stesso risultato, ma normalmente


viene usato il secondo modo (inst.method()): quando
chiamiamo inst.method(), Python in realtà risale automaticamente
alla classe di inst ed esegue Test.method(inst). Per questo è
necessario che ogni metodo definito nella classe accetti self come
primo argomento. Notare anche che self non ha niente di
particolare o di diverso dagli altri argomenti (al contrario di
altri linguaggi come Java che usa la keyword this per riferirsi
all’istanza).
Dato che self si riferisce all’istanza, possiamo usarlo per
accedere ad altri attributi e metodi definiti all’interno dello
classe semplicemente facendo self.attribute o self.metodo().
Inizializzare istanze
Le classi supportano anche diversi metodi “speciali” che sono
identificati dalla presenza di due underscore prima e dopo del
nome. Questi metodi non vengono chiamati direttamente
facendo inst.__metodo__, ma vengono in genere chiamati
automaticamente in situazioni particolari
Uno di questi metodi speciali è __init__, chiamato automaticamente
ogni volta che un’istanza viene creata:
>>> # definiamo una classe Test
>>> class Test:
... # definiamo un __init__ che stampa un messaggio
... def __init__(self):
... print('New instance created!')
...
>>> Test() # quando creiamo un'istanza, __init__ viene chiamato
New instance created!
<__main__.Test object at 0x7fef42c1eeb8>
>>> Test() # quando creiamo un'istanza, __init__ viene chiamato
New instance created!
97
<__main__.Test object at 0x7fef3ff76390>

__init__ ha anche un’altra particolarità: gli argomenti passati


durante la creazione dell’istanza vengono ricevuti da __init__.
Questo ci permette di creare automaticamente istanze diverse in
base agli argomenti passati:
>>> # definiamo una classe Dog
>>> class Dog:
... # definiamo un __init__ che accetta un nome
... def __init__(self, name):
... # creiamo un attributo di istanza per il nome
... self.name = name
...
>>> # creiamo due istanze di Dog
>>> rex = Dog('Rex')
>>> fido = Dog('Fido')
>>> rex.name # verifichiamo che il nome della prima sia Rex
'Rex'
>>> fido.name # e che il nome della seconda sia Fido
'Fido'

È anche importante notare che __init__ non equivale ai costruttori


presente in altri linguaggi, dato che non crea l’istanza, ma la
inizializza solamente.
Attributi
In queste ultime due lezioni abbiamo visto che gli attributi sono
dei valori associati all’istanza (o alla classe) e abbiamo anche
visto alcuni esempi di dichiarazione e uso di attributi.
Gli attributi si possono raggruppare in due categorie:
 attributi di istanza;
 attributi di classe.
Come si può intuire dai nomi, gli attributi di istanza sono legati
a un’istanza specifica, mentre gli attributi di classe sono legati
alla classe.
Gli attributi di istanza sono generalmente più comuni e si
dichiarano facendo istanza.attributo = valore. Quando un attributo
di istanza viene dichiarato all’interno di un metodo (ad esempio
l’__init__), si usa self.attributo = valore, dato che il self si
riferisce all’istanza:
>>> # definiamo una classe Dog
>>> class Dog:
... # definiamo un __init__ che accetta un nome
... def __init__(self, name):
... # creiamo un attributo di istanza per il nome
... self.name = name
... # definiamo un metodo che accede al nome e lo stampa
... def print_name(self):
... print(self.name)
...
98
>>> # creiamo un'istanza di Dog
>>> dog = Dog('Rex')
>>> # accediamo all'attributo di istanza "name"
>>> dog.name
'Rex'
>>> # chiamiamo un metodo che stampa il nome
>>> dog.print_name()
Rex
>>> # cambiamo il valore dell'attributo dell'istanza
>>> dog.name = 'Fido'
>>> # verifichiamo che il nome è stato cambiato
>>> dog.name
'Fido'
>>> dog.print_name()
Fido

È anche possibile aggiungere o rimuovere attributi dalle istanze,


ma generalmente sconsigliato, dato che è preferibile avere gli
stessi attributi (anche se con valori diversi) su tutte le istanze
della stessa classe:
>>> # creiamo un'istanza di Dog
>>> rex = Dog('Rex')
>>> # verifichiamo gli attributi e metodi dell'istanza
>>> # usando dir() (i metodi speciali sono stati omessi)
>>> dir(rex)
[..., 'name', 'print_name']
>>> # aggiungiamo un nuovo attributo all'istanza
>>> rex.job = 'police officer'
>>> # verifichiamo che l'attributo è stato aggiunto
>>> dir(rex)
[..., 'job', 'name', 'print_name']
>>> # accediamo al nuovo attributo
>>> rex.job
'police officer'
>>> # rimuoviamo l'attributo usando "del"
>>> del rex.job
>>> # verifichiamo che l'attributo è stato rimosso
>>> dir(rex)
[..., 'name', 'print_name']

Gli attributi di classe sono valori legati alla classe, che sono
comuni e accessibili da tutte le istanze. Per dichiarare attributi
di classe, esistono due modi: usando classe.attributo = valore o
usando attributo = valore nel corpo della dichiarazione di una
classe:
>>> # definiamo una classe Dog
>>> class Dog:
... # definiamo un attributo di classe
... scientific_name = 'Canis lupus familiaris'
... # definiamo un __init__ che accetta un nome

99
... def __init__(self, name):
... # creiamo un attributo di istanza per il nome
... self.name = name
...
>>> # creiamo due istanze di Dog
>>> rex = Dog('Rex')
>>> fido = Dog('Fido')
>>> # verifichiamo che ogni istanza ha un nome diverso
>>> rex.name
'Rex'
>>> fido.name
'Fido'
>>> # accediamo all'attributo di classe da Dog
>>> Dog.scientific_name
'Canis lupus familiaris'
>>> # accediamo all'attributo di classe dalle istanze
>>> rex.scientific_name
'Canis lupus familiaris'
>>> fido.scientific_name
'Canis lupus familiaris'
>>> # modifichiamo il valore dell'attributo di classe
>>> Dog.scientific_name = 'Canis lupus lupus'
>>> # verifichiamo il cambiamento dall'istanza
>>> rex.scientific_name
'Canis lupus lupus'

In questo esempio vediamo che il nome scientifico ('Canis lupus


familiaris'), comune a tutte le istanze di Dog, viene dichiarato
durante la definizione della classe, ed è accessibile sia dalle
istanze che dalla classe stessa. Ogni istanza ha poi un nome
univoco (es. 'Rex' e 'Fido'), che viene definito nell’__init__ e
che è accessibile solamente dalle istanze.

Lezione 26di 40
Ereditarietà

L’ereditarietà ci permette di creare una nuova classe a partire da


una classe esistente e di estenderla o modificarla.
Ad esempio, possiamo creare una classe Person nel modo seguente:
>>> # definiamo una classe Person
>>> class Person:
... # definiamo un __init__ che assegna nome e cognome
all'istanza
... def __init__(self, name, surname):
... self.name = name
... self.surname = surname
... # definiamo un metodo "eat" che stampa un messaggio
100
... def eat(self, food):
... print(self.name, 'is eating', food)
... # definiamo un metodo "sleep" che stampa un messaggio
... def sleep(self):
... print(self.name, 'is sleeping')
...
>>> # creiamo un'istanza di Person specificando nome e cognome
>>> p = Person('Ezio', 'Melotti')
>>> # verifichiamo il valore degli attributi name e surname
>>> e.name
'Ezio'
>>> e.surname
'Melotti'
>>> # verifichiamo che i metodi funzionino
>>> p.eat('pizza')
Ezio is eating pizza
>>> p.sleep()
Ezio is sleeping

Se ora volessimo creare una versione più specializzata di Person,


possiamo definire una nuova sottoclasse Employee che eredita dalla
classe base Person. Per indicare che Employee eredita da Person,
basta aggiungere il nome della classe base (o delle classi, nel
caso dell’ereditarietà multipla) tra parentesi dopo il nome della
sottoclasse:
>>> # definiamo una classe Employee che eredita da Person
>>> class Employee(Person):
... # definiamo un nuovo __init__ che accetta
nome/cognome/lavoro
... def __init__(self, name, surname, job):
... # chiamiamo l'__init__ della classe base (o
superclasse)
... # che assegna nome e cognome all'istanza
... super().__init__(name, surname)
... # assegniamo il lavoro all'istanza
... self.job = job
... # definiamo un metodo aggiuntivo che stampa un messaggio
... def work(self):
... print(self.name, 'is working as a', self.job)
...
# creiamo un'istanza di Employee specificando nome/cognome/lavoro
>>> e = Employee('Ezio', 'Melotti', 'developer')
>>> # verifichiamo il valore degli attributi name e surname
>>> e.name
'Ezio'
>>> e.surname
'Melotti'
>>> # verifichiamo il valore del nuovo attributo "job"
>>> e.job
'developer'
>>> # verifichiamo che i metodi ereditati da Person funzionino
>>> e.eat('pizza')
101
Ezio is eating pizza
>>> e.sleep()
Ezio is sleeping
>>> # verifichiamo che il nuovo metodo funzioni
>>> e.work()
Ezio is working as a developer

Dall’esempio possiamo vedere che per definire una sottoclasse


basta usare la sintassi class SottoClasse(SuperClasse):
... o class SottoClasse(SuperClasse1, SuperClasse2, ...): ... nel
caso dell’eredità multipla.
La sottoclasse erediterà automaticamente tutti i metodi e gli
attributi definiti dalla classe base (o superclasse), come ad
esempio i metodi Person.eat e Person.sleep.
È anche possibile, nella sottoclasse, sovrascrivere (override in
inglese) e quindi ridefinire metodi definiti dalla superclasse.
Per esempio, la sottoclasse Employee sovrascrive
l’__init__ definito dalla classe Person, modificandolo in modo che
accetti un argomento aggiuntivo (job). Se stiamo sovrascrivendo un
metodo, è possibile usare la funzione built-in super() (che
restituisce un riferimento alla classe base) per accedere al
metodo corrispondente definito nella classe base. Infine, è
possibile definire metodi aggiuntivi (come Employee.work).
Il risultato finale è una classe Employee che ha tre attributi
(name, surname, e job), un __init__ modificato per accettare un
argomento aggiuntivo (job), due metodi ereditati
da Person (Person.eat e Person.sleep) e un nuovo metodo
(Employee.work).

Lezione 27di 40
Metodi speciali

Nelle lezioni precedenti abbiamo visto che esistono diversi metodi


“speciali”, indicati dalla presenza di due underscore prima e dopo
il nome (ad esempio, __metodo__). Questi metodi non vengono
chiamati direttamente dal programmatore, ma vengono invocati da
Python in situazioni particolari. Parlando delle classi, ad
esempio, abbiamo incontrato il metodo speciale __init__, invocato
automaticamente durante la creazione delle istanze. In questa
lezione vedremo altri metodi speciali e come possono essi possano
essere utili in vari contesti.
102
__str__ e __repr__
Oltre all’__init__, ci sono altri due metodi speciali che vengono
comunemente aggiunti quando si definisce una nuova
classe: __str__ e __repr__. Questi due metodi vengono invocati
automaticamente quando eseguiamo str(istanza) e repr(istanza), o
quando chiamiamo funzioni che eseguono queste operazioni (ad
esempio: print(istanza)).
Entrambi i metodi devono restituire una stringa: la differenza è
che __str__ restituisce un valore utile all’utente,
mentre __repr__ un valore utile allo sviluppatore:
>>> # definiamo una classe Person
>>> class Person:
... # definiamo un __init__ che assegna nome e cognome all'istanza
... def __init__(self, name, surname):
... self.name = name
... self.surname = surname
... # definiamo uno __str__ che restituisce nome e cognome
... def __str__(self):
... return '{} {}'.format(self.name, self.surname)
... # definiamo uno __repr__ che restituisce il tipo dell'istanza
... def __repr__(self):
... return '<Person object ({} {})>'.format(self.name,
self.surname)
...
>>> # creiamo un'istanza di Person
>>> p = Person('Ezio', 'Melotti')
>>> # l'interprete stampa automaticamente il repr() dell'oggetto
>>> # e il metodo p.__repr__() viene invocato
>>> p
'<Person object (Ezio Melotti)>'
>>> repr(p)
'<Person object (Ezio Melotti)>'
>>> # se usiamo str(), print(), o format(), p.__str__() viene
chiamato
>>> # automaticamente e il nome completo viene restituito
>>> str(p)
'Ezio Melotti'
>>> print(p)
Ezio Melotti
>>> 'Welcome {}!'.format(p)
'Welcome Ezio Melotti!'

Da questo esempio possiamo vedere che il __repr__ contiene


informazioni utili allo sviluppatore, come il il tipo dell’oggetto
e il valore degli attributi name e surname, mentre il valore
restituito da __str__ include solo il nome e il cognome, e può
essere usato direttamente in un’interfaccia utente o in messaggi
generati mediante la funzione print.
Il valore restituito da __repr__ può contenere più o meno
informazioni, ma in genere è bene che rispetti una delle seguenti
due forme:

103
 <Classe object ...>: il nome della classe seguita da
informazioni aggiuntive (ad esempio il valore di alcuni
attributi), il tutto racchiuso tra <...> (es. <Person object:
name='Ezio' surname='Melotti'>).
 Classe(arg1, arg2, ..., argN): l’espressione usata per creare
l’istanza, in grado di dirci il nome della classe e il valore
degli attributi (es. Person('Ezio', 'Melotti')).
__bool__ e __len__
Il metodo speciale __bool__ può essere usato per definire se un
oggetto è vero o falso, mentre il metodo __len__ può ritornare la
lunghezza (o il numero di elementi) di un oggetto.
Se __bool__ non è definito, Python può usare il risultato
di __len__ per determinare se un oggetto è vero o falso (una
lunghezza diversa da 0 è considerata vera). Se anche __len__ non è
definito, l’oggetto è considerato vero.
>>> # definiamo una classe Team
>>> class Team:
... # definiamo un __init__ che assegna i membri all'istanza
... def __init__(self, members):
... self.members = members
... # definiamo un __bool__ che restituisce False se il
... # team ha 0 membri, altrimenti True
... def __bool__(self):
... return len(self.members) > 0
... # definiamo un __len__ che restituisce il numero di membri
... def __len__(self):
... return len(self.members)
... # definiamo un __repr__ che restituisce il tipo dell'oggetto
... # e i nomi dei membri del team
... def __repr__(self):
... names = ', '.join([p.name for p in self.members])
... return '<Team object [{}]>'.format(names)
...
>>> # creiamo un'istanza di Team con 3 membri
>>> t1 = Team([Person('Guido', 'van Rossum'),
... Person('Alex', 'Martelli'),
... Person('Ezio', 'Melotti'),])
>>> # verifichiamo il repr dell'oggetto
>>> t1
<Team object [Guido, Alex, Ezio]>
>>> # verifichiamo che il team ha 3 membri
>>> t1.members
[<Person object (Guido van Rossum)>,
<Person object (Alex Martelli)>,
<Person object (Ezio Melotti)>]
>>> # verifichiamo che la lunghezza del team è 3
>>> len(t1)
3
>>> # verifichiamo che questo team è considerato "vero"
>>> bool(t1)
True
>>>
104
>>> # creiamo un'altra istanza di Team con 0 membri
>>> t2 = Team([])
>>> # verifichiamo il repr dell'oggetto
>>> t2
<Team object []>
>>> # verifichiamo che il team ha 0 membri
>>> t2.members
[]
>>> # verifichiamo che la lunghezza del team è 0
>>> len(t2)
0
>>> # verifichiamo che questo team è considerato "falso"
>>> bool(t2)
False
Copy
Nell’esempio, abbiamo definito una classe Team che include una
lista di membri. Quando usiamo len(istanza), viene automaticamente
invocato il metodo Team.__len__(), che ci restituisce il numero di
membri nel team. Quando usiamo bool(istanza) o if istanza: ...,
viene invece invocato il metodo Team.__bool__(), che ci
restituisce True se il team ha almeno un membro, altrimenti False.

Lezione 28di 40

Overloading degli operatori

Fare l’overloading degli operatori significa definire (o


ridefinire) il comportamento di un operatore durante l’interazione
con un’istanza di una classe che abbiamo creato in precedenza.
Questo ci permette di definire cosa succede quando, ad esempio,
utilizziamo una sintassi del tipo istanza1 + istanza2.
Nelle lezioni precedenti, abbiamo visto diversi tipi di operatori:
aritmetici (+, -, *, /, //, %), di confronto
(==, !=, <, <=, >, >=), binari (<<, >>, &, |, ^, ~), di
contenimento (in e not in), di indexing (oggetto[indice]), di
accesso a attributi (oggetto.attributo).
Per ognuno di questi operatori esiste un corrispondente metodo
speciale, che può essere definito per specificare il risultato
dell’operazione. Per diversi operatori esistono anche due tipi
aggiuntivi di metodi speciali, la versione speculare e quella in
place. Ad esempio, l’operatore + ha tre metodi speciali:
 __add__: quando eseguiamo istanza + valore, viene in realtà
eseguito il metodo istanza.__add__(valore);

105
 __radd__: quando eseguiamo valore + istanza, e il valore non
definisce un metodo __add__ compatibile con la nostra istanza,
viene eseguito il metodo istanza.__radd__(valore);
 __iadd__: quando eseguiamo istanza += valore, viene
eseguito istanza.__iadd__(valore), permettendoci di modificare
l’istanza in place.
Vediamo alcuni esempi di overloading degli operatori:
>>> # definiamo una classe Team
>>> class Team:
... # definiamo un __init__ che assegna i membri all'istanza
... def __init__(self, members):
... self.members = members
... # definiamo un __repr__ che restituisce il tipo
dell'oggetto
... # e i nomi dei membri del team
... def __repr__(self):
... names = ', '.join([p.name for p in self.members])
... return '<Team object [{}]>'.format(names)
... # definiamo un __contains__ che restituisce True se un
membro
... # fa parte del team, altrimenti False
... def __contains__(self, other):
... return other in self.members
... # definiamo un __add__ che restituisce un nuovo team
creato
... # dall'aggiunta di una nuova persona o dall'unione di 2
team
... def __add__(self, other):
... if isinstance(other, Person):
... return Team(self.members + [other])
... elif isinstance(other, Team):
... return Team(self.members + other.members)
... else:
... raise TypeError("Can't add Team with
{!r}.".format(other))
... # definiamo un __radd__ che è uguale ad __add__, visto che
... # l'addizione è un'operazione commutativa
... __radd__ = __add__
... # definiamo un __iadd__ che modifica il team aggiungendo
una
... # nuova persona o i membri di un altro team al team
corrente
... def __iadd__(self, other):
... if isinstance(other, Person):
... self.members.append(other)
... return self
... elif isinstance(other, Team):
... self.members.extend(other.members)
... return self
... else:

106
... raise TypeError("Can't add {!r} to the
team.".format(other))
...
>>>
>>> # creiamo 4 istanze di Person
>>> guido = Person('Guido', 'van Rossum')
>>> tim = Person('Tim', 'Peters')
>>> alex = Person('Alex', 'Martelli')
>>> ezio = Person('Ezio', 'Melotti')
>>>
>>> # creiamo 2 team da 2 persone per team
>>> t1 = Team([guido, tim])
>>> t2 = Team([alex, ezio])
>>>
>>> # verifichiamo i membri dei 2 team
>>> t1
<Team object [Guido, Tim]>
>>> t2
<Team object [Alex, Ezio]>
>>>
>>> # verifichiamo l'overloading dell'operatore in
>>> guido in t1
True
>>> ezio in t1
False
>>> ezio not in t1
True
>>>
>>> # verifichiamo l'overloading dell'operatore + (__add__)
>>> # sommando un'istanza di Team con una di Person
>>> t1 + ezio
<Team object [Guido, Tim, Ezio]>
>>> # verifichiamo che l'operazione ha restituito
>>> # un nuovo team, e che t1 non è cambiato
>>> t1
<Team object [Guido, Tim]>
>>>
>>> # verifichiamo l'overloading dell'operatore + (__radd__)
>>> # sommando un'istanza di Person con una di Team
>>> ezio + t1
<Team object [Guido, Tim, Ezio]>
>>>
>>> # verifichiamo l'overloading dell'operatore + (__add__)
>>> # sommando due istanze di Team
>>> t1 + t2
<Team object [Guido, Tim, Alex, Ezio]>
>>> t2 + t1
<Team object [Alex, Ezio, Guido, Tim]>
>>>
>>> # verifichiamo che t1 contiene 2 membri
>>> t1
<Team object [Guido, Tim]>
107
>>> # verifichiamo l'overloading dell'operatore += (__iadd__)
>>> # aggiungendo un'istanza di Person al Team t1
>>> t1 += ezio
>>> # verifichiamo che t1 è stato modificato
>>> t1
<Team object [Guido, Tim, Ezio]>
>>>
>>> # creiamo altre 2 istanze di Team
>>> t3 = Team([alex, tim])
>>> t4 = Team([guido, ezio])
>>> # verifichiamo che t3 contiene 2 membri
>>> t3
<Team object [Alex, Tim]>
>>> # verifichiamo l'overloading dell'operatore += (__iadd__)
>>> # aggiungendo un'istanza di Team al Team t3
>>> t3 += t4
>>> # verifichiamo che t3 è stato modificato
>>> t3
<Team object [Alex, Tim, Guido, Ezio]>
>>>
>>> # verifichiamo che aggiungere un tipo incompatibile
>>> # ci restituisce un TypeError
>>> t3 + 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 15, in __add__
TypeError: Can't add Team with 5.

In questo esempio abbiamo definito una classe Team che implementa


diversi metodi
speciali: __init__, __repr__, __contains__, __add__, __radd__, __i
add__. Questi metodi ci hanno permesso di definire il
comportamento degli operatori in, not in, +, += e di verificare i
diversi risultati che otteniamo combinandoli con istanze della
classe Team e della classe Person, definita in un esempio
precedente.
Riassunto dei metodi speciali
Le tabelle seguenti riassumono gli operatori più comunemente usati
e i loro metodi speciali.
Oltre al corrispondente metodo speciale (es. __add__),
gli operatori aritmetici hanno anche la
versione speculare (es. __radd__) e quella in
place (es. __iadd__):

Operator
e Descrizione Metodi speciali
+ addizione __add__, __radd__, __iadd__
– sottrazione __sub__, __rsub__, __isub__
* moltiplicazion __mul__, __rmul__, __imul__

108
e
/ divisione __truediv__, __rtruediv__, __itruediv__
divisione __floordiv__, __rfloordiv__, __ifloordiv_
// intera _
modulo (resto
della
% divisione) __mod__, __rmod__, __imod__
Ogni operatore di confronto ha solo un corrispondente metodo
speciale:

Operatore Descrizione Metodo speciali


== uguale a __eq__
!= diverso da __ne__
< minore di __lt__
minore o
<= uguale a __le__
> maggiore di __gt__
maggiore o
>= uguale a __ge__
Così come gli operatori aritmetici, anche gli operatori
binari hanno una versione speculare e una versione in place:

Operatore Descrizione Metodi speciali


esegue
uno shift a
sinistra
di n posizioni
x << n dei bit di x __lshift__, __rlshift__, __ilshift__
esegue
uno shift a
destra
di n posizioni
x >> n dei bit di x __rshift__, __rrshift__, __irshift__
esegue
un and tra i
bit di x e
x & y di y __and__, __rand__, __iand__
esegue
un or tra i
bit di x e
x | y di y __or__, __ror__, __ior__
esegue un or
esclusivo tra
i bit di x e
x ^ y di y __xor__, __rxor__, __ixor__

109
Esistono anche metodi speciali per determinare il comportamento di
un’oggetto durante l’accesso, l’assegnamento, e la rimozione di
elementi o attributi:

Operatore Descrizione Metodo speciali


accesso a un
object[item] elemento __getitem__
assegnamento
object[item] a un
= value elemento __setitem__
del rimozione di
object[item] un elemento __delitem__
accesso a un
object.attr attributo __getattr__
assegnamento
object.attr a un
= value attributo __setattr__
del rimozione di
object.attr un attributo __delattr__
Esistono infine altri metodi speciali meno comuni, che per brevità
non sono inclusi in questa guida, ma che si possono trovare
nella documentazione ufficiale sui metodi speciali.
È inoltre importante notare che esistono alcuni operatori su cui
non è possibile effettuare l’overloading, in particolare
l’operatore di assegnamento (=), e gli operatori booleani
(and, or, not).

Lezione 29di 40

GUI sui Python: i Framework da usare

In questa lezione vedremo una breve panoramica dei framework che


consentono di costruire interfacce grafiche in Python.
Nelle lezioni precedenti abbiamo visto come costruire applicazioni
in Python utilizzabili dalla riga di comando. L’utilizzo di un
framework per la costruzione di interfacce grafiche risulterà
necessario per fornire un aspetto professionale, ed aumentare
l’usabilità delle applicazioni.
I framework per costruire GUI (Graphical User Interface) in Python
possono essere suddivisi in due categorie:

110
 Cross-platform_ totalmente portabili, conferiscono (quasi)
sempre lo stesso aspetto a tutte le applicazioni,
indipendentemente dal sistema operativo che le esegue;
 Platform-specific: utilizzano elementi visuali forniti dal
particolare sistema operativo che esegue l’interfaccia.
Tra le caratteristiche principali che andrebbero valutate quando
si intende scegliere il framework più adatto alla nostra
applicazione Python, bisognerebbe valutare:
 Widget: è importante conoscere quali strumenti visuali mette a
disposizione il framework (es. bottoni, menu, canvas, ecc…) per
comprendere fin da subito se è ciò che fa per la nostra
applicazione;
 Licenza: il tipo di licenza offerta dal framework che intendiamo
utilizzare è fondamentale, specialmente nel caso di applicazioni
di natura commerciali;
 IDE per lo sviluppo: l’esistenza di uno strumento IDE risulta
fondamentale per abbattere i tempi di sviluppo e test. Quando
possibile, dovremo sempre scegliere un framework con una buona
IDE a supporto del processo di costruzione dell’interfaccia;
 Supporto da parte del produttore: molti dei framework esistenti
sul web sono caduti in disuso, e spesso la ragione è il mancato
supporto da parte del produttore, la mancanza di aggiornamenti o
la mancanza di un IDE.
Tra le decine e decine di frameworks per lo sviluppo di GUI in
Python, vedremo adesso quelli che sono maggiormente
utilizzati/supportati dai produttori. I framework con scarso
supporto o caduti in disuso non verranno trattati in questa sede.
Tra quelli osservati in questa breve panoramica, approfondiremo
(nelle lezioni successive) quelli più utilizzati e meglio
supportati.
Framework cross-platform
I framework cross-platform hanno la caratteristica di essere
portabili su diversi sistemi operativi, consentendo agli
sviluppatori di scrivere codice indipendente dalle librerie
grafiche del sistema in esecuzione. Nella maggior parte dei casi,
ciò implica che l’aspetto finale dell’applicazione rimarrà lo
stesso, indipendentemente dal sistema operativo in esecuzione. I
framework cross-platform che vedremo nella seguente tabella, in
alcuni casi, possono anche appoggiarsi su librerie grafiche
native: questo è un vantaggio in termini di efficienza
dell’applicazione grafica, che sfrutterà appieno le capacità del
sistema. D’altro canto, però, l’aspetto finale delle applicazioni
cambierà (seppur in modo limitato) a seconda del sistema operativo
su cui esse saranno eseguite.
Home
Nome page Descrizione IDE consigliato

Un wrapper per la libreria


GuiZero [1] Tkinter che consente la Eclipse+PyDev
creazione di semplici GUI. Il
111
paradigma di programmazione è
molto semplice e pensato per lo
sviluppo anche da parte dei
bambini. Supporta Windows, Mac,
Linux e Raspberry Pi.

Una libreria che fornisce oltre


20 diversi widgets. Utilizza le
OpenGL e gira su Linux,
Windows, OS X, iOS, Raspberry
Pi e Android. Totalmente
gratuita, ed utilizzabile
secondo i termini della licenza
MIT, anche per fini
Kivy [2] commerciali. Eclipse+PyDev

Una libreria compatibile con


Python 2 e Python 3, che
consente lo sviluppo di GUI su
Windows, Linux e Mac OS X.
Disponibile secondo la licenza
MIT. Le principali
caratteristiche riguardano:
Un insieme di forms, basati
su PyQt, OpenGL ed altre
librerie;
Uno strato software che
consente di integrare PyForms
all’interno di browsers e GUI
Desktop pre-esistenti;
Un paradigma che aiuta il
mantenimento, la pulizia, la
leggibilità ed il riutilizzo
PyForms [3] Non disponibile
del codice.
Un package per Python che
consente di utilizzare i
cosiddetti “GObject”, ossia le
utility fornite da GTK+,
GStreamer, WebKitGTK, GLib,
GIO, ecc… Il suo supporto è
esteso a Linux, Windows e MacOS
ed è compatibile con Python
v2/v2. La licenza di utilizo è
PyGObject [4] la LGPL v2.1+. Wing Python IDE

Prodotta dalla società


Riverbank, supporta Python
PyQt [5] v2/v3. Sostanzialmente è un Wing Python IDE;
wrapper che consente di Qt Desginer
112
utilizzare le famose librerie
Qt in Python. Il supporto è
esteso a Windows, OS X, Linux,
iOS ed Android. Implementa
oltre 1000 classi, per la nuova
versione PyQt5, che è
attualmente supportata dalla
compagnia produttrice delle
librerie stesse. Le licenze
disponibili sono di due tipo:
GNU GPL v3 e la licenza
commerciale della Riverbank.

Un wrapper per il componente


webview, compatibile con Python
v2/v3. Consente di visualizzare
contenuti HTML all’interno
della GUI stessa, facilitando
dunque l’inserimento di
contenuti web senza richiedere
l’utilizzo di un browser.
Particolarmente utile nello
sviluppo di applicazioni
lightweight, il suo utilizzo
può essere facilmente combinato
con web frameworks
quali Flask o Bottle. Il
wrapper utilizza componenti
nativi per la creazione delle
GUI, sia su Windows che su Mac
OS X, che per Linux. La licenza
Pywebview [6] disponibile è la BSD. Non disponibile

Il framework più “essenziale”


per la creazione di GUI in
Python. Supporta sia Python 2
che 3, ed è integrato
all’interno del linguaggio
stesso. Non è particolarmente
evoluto e consigliato solamente
per GUI molto semplici. Se
cercate la potenza di un web Komodo IDE;
framework abbandonatene Eclipse+PyDev;
Tkinter [7] l’utilizzo. Wing Python IDE;
PAGE.
Una libreria open-source,
capace di fornire primitive per
l’ informatica grafica,
elaborazione delle immagini,
Vtk [8] visione artificiale e Non disponibile
visualizzazione dei dati.
113
Fornisce una miriade di
widgets, inclusi quelli per la
navigazione 3D degli oggetti
mostrati. Compatibile con
Windows, Mac, Unix, fornito
come strumento open-source,
regolato dalla licenza BSD.

Una libreria per la creazione


di GUI, che agisce da wrapper
per i cosiddetti “wxWidgets”,
scritti in C++. WxPython è
totalmente open source, e
regolato dalla
licenza wxWindows Library
Licence. Al momento, le
piattaforme supportate sono:
Windows, Mac OS X e Linux.
Unica pecca, manca il supporto
WxPython [9] per Python 3. Wing Python IDE;
WxGlade.

Framework platform-specific
I framework platform-specific non sono molto diffusi, in quanto le
applicazioni risultanti sono utilizzabili solo una categoria di
sistemi operativi. Nella seguente tabella vedremo le
caratteristiche di due framework che stanno comunque ottenendo una
discreta diffusione nell’ambito dei sistemi Windows ed Android:
Home
Nome page Descrizione IDE consigliato

Un framework che consente di


utilizzare la sintassi di Python,
con gli strumenti grafici offerti
da .NET framework (incluso
silverlight lato browser).
Chiaramente funzionerà solo sui
sistemi Windows, ed è compatibile
solamente con Python 2.7.
Distribuito con licenza Apache
2.0. Interessante l’add-on Python
tools che consente lo sviluppo
rapido ed il testing tramite il
IronPython [10] famoso IDE Visual Studio. Python tools

Una libreria per Android Studio,


che consente di mixare codice
Python e Java in una singola
applicazione. Il punto di forza è
Chaquopy [11] il supporto completo alle Android Android Studio
Api. Compatibile con Python
114
v2/v3, fornito con licenza open-
source e commerciale.

Lezione 30di 40

Tkinter

In questa lezione esploreremo le peculiarità di TKinter,


una libreria essenziale per la creazione di GUI con Python,
e PAGE, uno strumento per la generazione automatica di interfacce
grafiche con Tkinter.
Tkinter nasce come wrapper per le librerie grafiche Tcl/Tk, che
sono, ad oggi, parte integrante di Python. Prima di iniziare a
osservare da vicino le caratteristiche di Tkinter, è bene tenere
sempre a mente i riferimenti alla documentazione ufficiale,
disponibile sia per Python 2.x che per Python 3.x.
Utilizzo
Per importare il modulo Tkinter, è necessario procedere con la
seguente import, dipendente dalla versione di Python installata:
from sys import version_info
if version_info.major == 2:
# Python 2.x
from Tkinter import *
from Tkinter.ttk import *

elif version_info.major == 3:
# Python 3.x
from tkinter import *
from tkinter.ttk import *

I moduli di Tkinter per Python 3.x sono retrocompatibili con


quelli della versione 2.x. Sarà dunque sufficiente utilizzare
la import di cui sopra per rendere le nostre interfacce
perfettamente compatibili tra le due versioni di Python. Il modulo
“ttk” è un’estensione di Tkinter, detta “libreria tematica”, di
cui parleremo a breve.
Procediamo adesso con la scrittura del solito file “Hello world” e
verifichiamo che Tkinter funzioni correttamente. Creiamo un file
nominato hello.py, e copiamo-incolliamo il seguente codice:
#rimpiazzare questo commento con la import su indicata
115
ro = Tk()
w = Label(ro, text="Hello
"Hello world")
w.pack()
ro.mainloop()

Eseguiamo il comando python hello.py.


. Se tutto funziona
correttamente, dovremmo visualizzare la seguente finestra:
Figura 1. Hello world con Tkinter (click per ingrandire)

Widget
I widget di Tkinter sono numerosi ed elencati nella seguente
tabella, che li raggruppa in base alla categoria:
Categoria Nome Descrizione

Il contenitore di tutti widget grafici


istanziati dalla nostra applicazione.
Tk Istanzia una finestra generica

Rappresenta una finestra generica,


separata da Tk, ma comunque dipendente da
esso. Tale componente è utilizzabile per
Toplevel creare applicazioni multi
multi-finestra
finestra

Consente di raggruppare gli oggetti delle


finestre. Non ha un corrispettivo
“fisico”, e va interpretato come un
componente “logico” della finestra.
Ciascuna finestra potrà essere composta
Frame da diversi frame

Button Un semplice bottone cliccabile

Bottone per l’inserimento di una spunta


Checkbutton (checkmark)

Una casella di testo a linea singola


Entry editabile

Label Un semplice testo non editabile

Una lista di opzioni testuali, tutte


Listbox contemporaneamente visibili

Base Optionmenu
Simile al precedente, ma trattasi di menu
116
di tipo “popup”

Photoimage Utilizzato per mostrare immagini

Bottone di tipo “radio” a selezione


Radiobutton singola

Scale Implementa uno slider

Crea un’area di disegno. Le primitive che


mette a disposizione consentono di
disegnare archi, immagini bitmap, linee,
ovali, poligoni, rettangoli, testi. Tutte
le primitive sono personalizzabili in
Canvas termini di colore e dimensioni

Testo multilinea, consente di gestire il


font o il colore. Può essere utilizzato
ad esempio per implementare un semplice
Decorati Text editor di testo

Menu a comparsa che consente di inserire


etichette testuali ed associarle a
funzioni da eseguire quando l’etichetta
Menu viene cliccata

La porzione di un Menu che viene sempre


mostrata sull’interfaccia grafica.
Cliccare sulla sua etichetta testuale
consentirà di visualizzare il Menu ad
Menubutton essa associato

Barra utilizzata congiuntamente ad


una Listbox, Canvas oppure Text. Consente
di scorrere verticalmente/orizzontalmente
Compositi Scrollbar il componente ad essa associato

Con l’introduzione delle librerie tematiche Tk/Tcl 8.5, sono stati


aggiunti alcuni componenti tematici che aumentano le potenzialità
di Tkinter. Bisogna premettere che alcuni dei componenti della
libreria “tematica” sovrascrivono i componenti della libreria
classica, migliorandone l’aspetto. Per potere utilizzare la
libreria tematica e sovrascrivere i vecchi componenti della
libreria Tkinter, basterà utilizzare l’import precedentemente
indicato. Qualora desiderassimo utilizzare i vecchi componenti di
Tkinter, sarà sufficiente commentare le due linee contenenti
le import ai componenti “ttk”:
#from Tkinter.ttk import *
#from tkinter.ttk import *

117
I componenti di ttk sono in tutto 17, di cui 11 già esistenti in
Tkinter. Rispetto ai componenti della vecchia versione, avremo
quindi a disposizione 6 nuovi componenti, qui di seguito elencati:

Categoria Nome Descrizione

Combinazione di un Entry e un menu drop-


Combobox down

Strumento utile per gestire applicazioni


multifinestra. L’aspetto è quello di un
pannello che consente di selezionare la
Notebook finestra tra diverse “schede”

Una barra di completamento per mostrare


Progressbar l’avanzamento di un task

Un separatore da utilizzare per


Separator visualizzare i bordi dei widget

Consente di scalare le dimensioni della


GUI. Usualmente viene mostrato in basso a
Sizegrip destra nella finestra principale

Visualizza una gerarchia di oggetti, dove


ciascuno avrà associato una label testuale,
un’immagine opzionale ed una lista di
Tematici Treeview attributi

Layout manager
Al fine di posizionare i widget all’interno delle finestre della
GUI, dovremo utilizzare un layout manager. Tkinter ne mette a
disposizione tre:
 pack: consente di elencare in sequenza gli oggetti da disporre
nella nostra GUI. L’ordine di visualizzazione è identico
all’ordine con cui vengono registrati tramite
l’istruzione pack();
 grid: consente di posizionare gli oggetti all’interno di una
griglia 2-D. Gli oggetti vengono posizionati tramite l’utilizzo
dell’istruzione grid(), indicando numero di riga e di colonna;
 place: consente di indicare la posizione assoluta/relativa degli
oggetti all’interno della GUI. Utilizza l’istruzione place().
L’unica avvertenza da tenere a mente è che i layout non vanno MAI
mescolati nella stessa applicazione. Il più semplice e rapido da
utilizzare è il layout “grid”, mentre il più preciso è il layout
“place”.

118
PAGE: un IDE per la creazione di interfacce grafiche con Tk/Tcl
PAGE (Python Automatic GUI Generator)
nerator) è una IDE Drag and Drop per
la semplificazione del
l processo di creazione delle interfacce
grafiche in Python. Compatibile con Python 2.7 e versioni
successive, fornisce il supporto per i widget classici e tematici.
È disponibile tramite licenza GNU.
Per la sua installazione sarà sufficiente scaricarlo
da Sourceforge.
Utilizzando Ubuntu 16.04, scarichiamo l’archivio “.tgz” ed
estraiamone il contenuto in una cartella denominata “page”.
Dopodichè aprendo il terminale, dalla directory scompattat
scompattata
digitiamo il comando per la configurazione ./configure.
./configure
A questo punto, digitando il comando ./page,
, si aprirà l’IDE:
Figura 2. Interfaccia grafica di PAGE (click per ingrandire)

Da questo momento in poi potremo riempire il nostro Toplevel con


tutti i widget che desideriamo, utilizzando la widget toolbar, e
cambiando le impostazioni dall’
dall’attribute editor.
Potremo testare velocemente l’ interfaccia, cliccando sul
pulsante Gen_Python ->
> Generate support module,
, e salvando il
modulo di supporto in formato “tcl”. Dopodichè clicchiamo
su Gen_python -> Generate Python GUI,
, ed eseguiamo l’interfaccia
con Run:

119
Figura 3. Esecuzione dell’interfaccia generata da PAGE (click per
ingrandire)

Il codice Python, generato automaticamente, verrà salvato


all’interno della cartella dalla quale abbiamo eseguito PAGE.

Lezione 31di 40
PyQt

Questa lezione esplorerà le caratteristiche di PyQt,


, un insieme di
classi (oltre 1000) che consentono l’accesso alle librerie
grafiche Qt.
. Parleremo inoltre di Qt Designer,
, un potente editor
che permette di creare rapidamente interfacce desktop tramite
strumenti Drag and Drop
Drop.
Nel seguito della lezione ci riferiremo all’ultima r
release di
PyQt, ossia PyQt5 (v 5.6), facendo quindi riferimento alle
librerie grafiche Qt v5. Va inoltre detto che la versione più
vecchia, PyQt4, benchè assolutamente funzionante, sembra essere
ormai sorpassata in quanto le relative librerie Qt4 non sono più
supportate dal sito del produttore. La buona notizia è che PyQt5 è
compatibile con Python 2.6+ e 3.x e funziona su tutti i sistemi

120
capaci di eseguire le Qt, inclusi Windows, OS
X, Linux, iOS e Android
Android.. La documentazione ufficiale di PyQt è
disponibile sul sito della RiverBank.

Installazione e primo utilizzo


Nel seguito, daremo per scontato che si disponga già di
un’installazione di Python 3 e del relativo gestore dei pacchetti
python pip. In caso di dubbi sulla procedura di installazione di
Python 3, rimandiamo alla lezione introduttiva di questa guida9
sull’installazione
installazione di Python
Python.
Per i nostri scopi, faremo inoltre riferiment
riferimentoo alla versione
gratuita (licenza GPL) di PyQt5. Maggiori informazioni sulla
versione commerciale sono reperibili a questo link.
Supponendo
nendo di operare su Ubuntu,
, per installare PyQt5 non dovremo
far altro che digitare il comando seguente:

pip3 install pyqt5

Procediamo dunque con la creazione di un “hello world”, per


verificare che PyQt sia stato correttamente installato. Creiamo un
file denominato hello.py e copiamo-incolliamo
incolliamo il seguente testo al
suo interno:
from PyQt5.QWidgets import *
if __name__==’__main__’
’__main__’:
hello=QApplication
QApplication([])
finestra=QMainWindow
QMainWindow()
label=QLabel(‘Hello
‘Hello, World!’)
finestra.setCentralWidget
setCentralWidget(label)
finestra.show()
hello.exec_()

Eseguiamo quindi lo script, digitando:


python3 hello.py

Se tutto sarà andato a buon fine, dovremmo visualizzare la


seguente finestra:
Figura 1. Hello World con PyQt (click per ingrandire)

Moduli
Le classi messe a disposizione da PyQt5 sono oltre 1000,
raggruppate in circa 50 moduli, che rendono questa libreria molto
più versatile e potente di Tkinter.
. Nella seguente tabella

121
riassumeremo i principali moduli (con le relative funzionalità)
per iniziare a sviluppare le prime interfacce:

Modulo Descrizione Documentazione


Contiene le classi base di
PyQt, quali le astrazioni per
le animazioni, macchine a
stati, thread, files, memoria
condivisa, espressioni
QtCore regolari [1]
Fornisce un insieme di classi
per la gestione delle
finestre, immagini, font e
testi. Da usare
congiuntamente al modulo
QtGui “QtWidgets” [2]
Una vasta gamma di widget
(form, bottoni, label,
treeview, tabelle, checkbox e
quant’altro) adatti alla
creazione di interfacce
QtWidgets grafiche desktop [3]
Fornisce le classi per il
supporto agli acquisti da
effettuare via app. In
particolare, supporta l’App
Store di Mac OS ed IOS, e
QtPurchasing Google Play di Android [4]
Un modulo utilissimo per la
visualizzazione di dati in 3D
(es. superfici, scatter-plot,
QtDataVisualization e quant’altro) [5]

Qt Designer: un IDE per la creazione rapida di interfacce desktop


Qt Designer è un tool WYSIWYG, molto simile a PAGE per Tkinter,
che consente di creare rapidamente interfacce desktop utilizzando
il modulo QtWidgets. Viene fornito congiuntamente alle librerie
Qt; per tal motivo, per utilizzarlo è necessario scaricare
le librerie Qt5, avendo cura di scegliere il tipo di installazione
che si confà maggiormente alle nostre necessità:

122
Figura 2. Tipologie di licenze per le librerie Qt (click per
ingrandire)

Seguiamo quindi le istruzioni indicate per l’installazione; il


processo è automatico e non dovrebbero esserci grossi problemi.
Una volta installate le Qt, cerchiamo l’eseguibile di Qt Designer
ed avviamolo. Dovremmo visualizzare una interfaccia simile a
quella mostrata in figura:
Figura 3. Interfaccia grafica di Qt Designer (click per
ingrandire)

123
Il sistema è veramente semplice da utilizzare. Sulla sinistra,
dalla Widget box,
, potremo scegliere il tipo di layout con il quale
posizionare gli oggetti sull’interfaccia. Inoltre, avremo a
disposizione la lista dei widget posizionabili (non interamente
visibili in figura). Sulla destra, avremo a disposizione
il property editor,
, che consentirà di personalizzare i widget
inseriti.
Esempio: realizzare un’interfaccia di login
Lanciamo QtDesigner, e clicchiamo su File -> New ->
> Main Window:
Window
Figura 4. Finestra creata con Qt Designer (click per ingrandire)

Avremo creato il contenitore per i nostri widget, che su PyQt5 è


un’istanza della classe QMainWindow. Adesso, dalla Widget
box sulla sinistra, aggiungiamo un widget contenitore (
(QWidget),
due label (QLabel),
), due caselle di testo (
(QTextEdit)
) ed un bottone
(QPushButton):
Figura 5. Login form (click per ingrandire)

In pochi istanti avremo creato una semplice interfaccia di login


per la nostra applicazione. Grazie ad un sapiente utilizzo dei
contenitori (QFrame e Qwidget
Qwidget)) potremo successivamente creare
semplici applicazioni multi
multi-vista
vista sulla stessa finestra.
124
L’interfaccia di Qt Designer, consente inoltre di inserire delle
azioni da eseguire in corrispondenza di un dato evento
(denominato segnale nelle librerie Qt). Per fare ciò, apriamo il
tab View ->
> Signals/Slot editor
editor.. Ciascun segnale sarà composto da
un sender, un segnale (metodo del sender), un receiver ed uno slot
(metodo del receiver). Noi abbiamo creato un’azione ((QAction)
denominata “handle_login”,
andle_login”, tramite l’
l’Action editor,
, accessibile
tramite View ->
> Action Editor
Editor,, e successivamente collegato il
bottone e l’azione dall’editor dei segnali come segue:
Figura 6. Aggiunta di un segnale/slot tramite editor (click per
ingrandire)

Questo significa che, ogni qual volta il bottone sarà cliccato,


verrà richiamata la funzione trigger() dell’oggetto
“handle_login”. Chiaramente, la logica di business dovremo
implementarla in un secondo momento, quando avremo a disposizione
il codice Python dell’interfaccia
interfaccia grafica (sezione seguente).
Conversione in Python e implementazione della logica di business
Per salvare l’interfaccia generata da Qt Designer, andiamo sul
tab File – > Save as ->
> test.ui
test.ui.. Notiamo che l’interfaccia
generata è stata salvata in un formato XML (estensione .ui), non
direttamente modificabile in Python. Fortunatamente, gli
sviluppatori di PyQt hanno messo a disposizione un potente
strumento, denominato pyuic5
pyuic5, che consente la traduzione dal
formato .ui in uno script (
(.py). Per convertire
rtire l’interfaccia così
generata, posizioniamoci all’interno della cartella contenente
l’interfaccia, e digitiamo da shell:
pyuic5 -x test.ui -o
o test.py

Se la conversione è avvenuta correttamente, dovremmo visualizzare


il nuovo file test.py,
, rappresentativo dell’interfaccia in codice
Python. Eseguiamo il file per la verifica del corretto
funzionamento:
python3 test.py

Se tutta la procedura avrà funzionato correttamente, l’interfaccia


sarà mostrata come segue:

125
Figura 7. Aspetto dell’interfaccia eseguita con Python (click per

ingrandire)
A questo punto saremo liberi di utilizzare il file Python appena
generato ed implementare la logica di controllo della nostra
interfaccia.

Lezione 32di 40

Kivy

Scarica gli allegati alla lezione ALLEGATI:

(main.py)
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder

class ScreenManagement(ScreenManager):
pass

class LoginPage(Screen):
def verify(self):
user=self.ids["login"].text
pwd=self.ids["password"].text
if user== "user" and pwd== "passwd":
print('login successful')

class LoginApp(App):
def builder(self):
return kv_file

kv_file = Builder.load_file('login.kv')

if __name__ == '__main__':
LoginApp().run()
126
(Login.kv)
ScreenManagement:
LoginPage:

<LoginPage>:
BoxLayout:
orientation: 'vertical'
padding: [10,50,10,50]
spacing: 20

Label:
text: 'Login'
font_size: 18
halign: 'left'

TextInput:
id: login
multiline:True
font_size: 28
readonly: False
text: 'user'
on_text: root.verify()

Label:
text: 'Password'
halign: 'left'
font_size: 18

TextInput:
id: password
multiline:False
password:True
font_size: 28
text: 'passwd'

Button:
text: 'Connect'
font_size: 24
127
on_press: root.verify()

In questa lezione parleremo di Kivy,


, una libreria Python per lo
sviluppo di interfacce grafiche portabili tra diverse piattaforme.
Kivy supporta infatti i sistemi operativi Linux, Windows,
Windows OS
X, Android ed iOS,
, risultando inoltre compatibile con Raspberry
Pi.
La libreria Kivy è totalmente gratuita e disponibile secondo la
licenza MIT, dunque utilizzabile anche in prodotti commerciali.
Inoltre, le release sono stabili e completamente supportate
dal team di sviluppo.
. Le librerie grafiche utilizzano le OpenGL,
cercando di sfruttare a fondo le capacità della GPU. I widget a
disposizione sono oltre 20.
Installazione
stallazione e primo utilizzo
All’interno della pagina dei downloads di Kivy, troveremo le
istruzioni di installazione per tutte le piattaforme sopra citate.
Inoltre, il codice sorgente è disponibile su GitHub.. Nel seguito
assumereremo che siano già installati Python 2 o Python 3 e pip,
aggiornati alle ultime versioni. In tutti i casi descritti qui di
seguito, alla fine della procedura di installa
installazione,
zione, scriveremo il
seguente Hello World, e ne testeremo l’esecuzione:
from kivy.app import App
from kivy.uix.button import Button

class PrimaApp(App):
def build(self):
):
return Button
Button(text='Hello World')
PrimaApp().run()

Figura 1. Finestra Hello World Kivy (click per ingrandire)

Installazione su Windows
Per installare Kivy su Windows, apriamo il terminale e digitiamo:
python -m pip install --
--upgrade pip wheel setuptools

Installiamo quindi le dipendenze:


128
python -m pip install pypiwin32 kivy.deps.glew pygments
kivy.deps.sdl2 docutils kivy.deps.gstreamer

Installiamo infine Kivy:


python -m pip install kivy

A questo punto eseguiamo l’hello world e verifichiamo che tutto


sia andato a buon fine.
Installazione su Mac OS X
Nel caso di Mac OS X,
, potremo procedere direttamente con il
download dell’installer automatico (in formato .dmg).
). Dovremo aver
cura di scegliere la versione adatta, in base alla versione di
Python:
Figura 2. Opzioni di installazione su Mac OS X (fonte: Kivy
donwloads) (click per ingrandire)

Installazione su Linux
Per installare Kivy su Linux, supponendo di avere a
disposizione apt,
, aggiungiamo il seguente repository al nostro
sistema:
sudo add-apt-repository
repository ppa:kivy-team/kivy

Procediamo dunque con l’installazione. Se usiamo Python 2:


sudo apt-get install python
python-kivy

oppure, per Python 3:


sudo apt-get install python3
python3-kivy

All’interno della pagina web per l’installazione su linux


troveremo informazioni dettagliate anche per Debian, Linux Mint,
OpenSuse, Gentoo e Fedora.
Installazione su Android
Per
r utilizzare Kivy su Android, dovremo installare una app
detta Kivy Launcher,
, che agirà da wrapper per le applicazioni che
fanno utilizzo di Kivy su Android.
A questo
esto punto, all’interno della scheda SD del nostro cellulare,
creiamo una directory rappresentativa della app che intendiamo
eseguire (esempio, “helloworld”):
/sdcard/kivy/helloworld
129
La directory helloworld conterrà i seguenti file:
 main.py: il cuore della app;
 android.txt: proprietà della app;
Il file android.txt ha la seguente struttura:
title=helloworld
author=htmldotit
orientation=<portrait|landscape>

Nel caso in cui intendiamo rilasciare l’app, il procedimento è un


po’ più complesso e va fuori gli scopi di questa lezione
introduttiva. Sommariamente, per generare l’APK dovremo
utilizzare Buildozer, un tool che automatizza il processo di
creazione degli APK per Android. Funziona solo su Linux, ed è in
alpha-release.
Installazione su Raspberry
Installare Kivy su Raspberry (Raspbian Jessie/Stretch) è possibile
in due modi:
1. installare l’iso KivyPie fornita a questo link (avendo cura di
installare startx, che non viene fornito all’interno
dell’immagine);
2. installazione manuale: in tal caso, dovremo seguire le
istruzioni fornite a questo link.
Architettura di Kivy
Kivy ha un’architettura piuttosto complessa, ed offre una miriade
di funzionalità. Diversamente da altre librerie grafiche
(es. Tkinter) si dimostra adatta anche allo sviluppo di giochi 2D.
La seguente figura mostra l’architettura completa:

130
Figura 3. Architettura di Kivy (fonte: Kivy architecture)
architecture (click
per ingrandire)

Il livello più basso deve essere pensato come un “ammasso” non ben
organizzato di tutte le funzionalità che Kivy intende fornire.
Tale livello è strettamente dipendente dal sistema operativo in
uso e per tale motivo viene reso trasparente all’utente finale
tramite il
l successivo livello di astrazione.
Il livello intermedio, scritto in C, è ancora dipendente dal
sistema operativo e agisce da “collante” tra il livello più basso
e i moduli Python del livello più alto. Fondamentalmente non fa
altro che riorganizzare le fu
funzionalità
nzionalità dei moduli di basso
livello in un insieme ordinato e le espone secondo un’interfaccia
C standard.
Il livello più alto, scritto interamente in Python, è l’insieme di
librerie che troveremo identiche su ogni piattaforma. In
particolare, troveremo a disposizione i seguenti moduli:
 Clock:
: un modulo che mette a disposizione timers per la
sincronizzazione;
 Cache:
: un modulo progettato per incrementare le prestazioni
delle nostre app, che consente di velocizzare l’accesso a dati
acceduti frequentemente;

131
 Gesture Detection: un riconoscitore di gesti da utilizzare su
dispotivi touch. Può essere utilizzato anche per riconoscere
gesti personalizzati;
 Kivy Language: un linguaggio per la descrizione delle interfacce
grafiche che facilita il posizionamento dei widget nella nostra
app;
 Properties: proprietà da utilizzare per ottimizzare la veste
grafica della nostra app.
UIX
Tale modulo contiene le classi per la creazione di applicazioni
desktop/mobile, che fondamentalmente rappresenta i contenuti della
libreria Tkinter, mettendo a disposizione elementi come i bottoni,
file browser, pannelli e quant’altro. In Kivy, le funzionalità di
“contenitore” vengono svolte dai moduli di layout. I layout
disponibili sono cinque: box, grid, stack, anchor e float.
I layout box, grid e stack sono principalmenti utilizzati per
interfacce grafiche semplici composte da bottoni e canvas. I
layout anchor e float consentono un posizionamento più preciso e
sono utilizzati tipicamente nelle applicazioni desktop. Impostare
il layout di un bottone, ad esempio, consta di tre semplici
istruzioni:
layout = BoxLayout(padding=5)
button = Button(text=’Bottone di prova’)
layout.add_widget(button)

Sviluppo di una semplice applicazione con Kivy


Vedremo adesso come procedere allo sviluppo di una tipica
schermata di login, contenente alcuni utili widget. Altri esempi
dimostrativi sono inoltre disponibili sulla pagina ufficiale.
Il nostro progetto sarà composto da due file:
 main.py: implementa le funzionalità della finestra di login;
 login.kv: il file di descrizione dell’interfaccia grafica
(utilizza linguaggio Kivy).
Il main è un file Python composto da uno screenmanager:
class ScreenManagement(ScreenManager):
pass

Esso conterrà inoltre una classe per effettuare i controlli sui


dati inseriti sul form di login:
class LoginPage(Screen):
def verify(self):
#verifica user e password

La app vera e propria caricherà il layout file (login.kv):


class LoginApp(App):
def builder(self):
return kv_file
132
kv_file = Builder.load_file
load_file('login.kv')

Il file di layout segue invece un andamento ad “albero”. Il file


specifica il contenuto dello screenmanager, ossia la login page,
ed a sua volta il contenuto della login page, cioè: due label, due
caselle di input ed un bottone per la login.
ScreenManagement:
LoginPage:
<LoginPage>:
BoxLayout:
orientation: 'vertical'
padding: [10,50,10,50]
spacing: 20
Label:
# appeareance
TextInput:
# appeareance
Label:
# appeareance
TextInput:
# appeareance
Button:
# appeareance

Se tutto andrà bene, dovrebbe aprirsi la seguente finestra di


login:
Figura 4. App di login (click per ingrandire)

133
Il codice sorgente di questo esempio è allegato a questa lezione,
ed è disponibile qui.(Vai sul sito di HTML.it)
(main.py)

from kivy.app import App


from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class ScreenManagement(ScreenManager):
pass
class LoginPage(Screen):
def verify(self):
user=self.ids["login"].text
pwd=self.ids["password"].text
if user== "user" and pwd== "passwd":
print('login successful')
class LoginApp(App):
def builder(self):
return kv_file

kv_file = Builder.load_file('login.kv')

if __name__ == '__main__':
LoginApp().run()

(Login.kv)
ScreenManagement:
LoginPage:
<LoginPage>:
BoxLayout:
orientation: 'vertical'
padding: [10,50,10,50]
spacing: 20
134
Label:
text: 'Login'
font_size: 18
halign: 'left'
TextInput:
id: login
multiline:True
font_size: 28
readonly: False
text: 'user'
on_text: root.verify()
Label:
text: 'Password'
halign: 'left'
font_size: 18
TextInput:
id: password
multiline:False
password:True
font_size: 28
text: 'passwd'

Button:
text: 'Connect'
font_size: 24
on_press: root.verify()

135
Lezione 33di 40
Python e Java

Nota: lezione in aggiornamento


Se qualcuno fosse interessato all’utilizzo delle librerie di java
all’interno di python, può utilizzare “Jython“.
Jython è un interprete python scritto completamente in java. Per
poterlo utilizzare si deve scaricare la classe java per
l’installazione presso il sito www.jython.org.
Essendo scritto in java, jython permette di leggere tutte le
librerie di java. è quindi possibile utilizzare la vasta raccolta
di oggetti disponibili per questa famosa piattaforma di sviluppo.
Dopo aver installato il programma, facendo partire l’interprete si
vede il nostro amico prompt:

C:jython>jython
Jython 2.0 on java1.2 (JIT: symcjit)
Type “copyright”, “credits” or “license” for more information.
>>>

Ora ci si può divertire provando nuovamente tutti gli esempi del


presente manuale. Jython contiene, infatti, tutte le librerie
standard di python. Inoltre è possibile richiamare direttamente le
librerie standard di java.
Vi mostro un piccolo esempio:

>>> import java


>>> java.lang.System.out.print(“Ciao a tutti\n”)
Ciao a tutti

In questo esempio ho importato la libreria di java e ho utilizzato


il metodo standard “print” nella gerarchia “java.lang.System.out”.
Questo codice fara’ sicuramente la felicita’ degli amanti java !
L’unico problema di questa implementazione dell’interprete java, è
rappresentato dalla sua lentezza. Si può gia’ notare, dopo pochi
esempi, che il tempo di esecuzione del codice python è
notevolmente peggiorato. Il motivo è chiaramente dovuto alla
lentezza dell’interprete java rispetto ad un programma compilato
nativo fatto in C.
Il mio consiglio è di utilizzare jython solo se serve
necessariamente utilizzare le librerie di java. Altrimenti è
consigliabile installare un interprete python per la propria
piattaforma di utilizzo. In questa scelta si deve tenere conto che
molte funzionalita’ di java sono gia’ presente nei moduli standard
di python.
136
Lezione 34di 40

Python e i database

Nota: lezione in aggiornamento


Ogni linguaggio di programmazione, degno di tale nome, deve avere
degli strumenti per accedere ai maggiori sistemi di gestione di
base dati relazionali. Anche python dispone di strumenti atti a
risolvere questa problematica.
A tale scopo è stata sviluppata una raccolta di moduli, denominata
“DB-API“.
Tale libreria ha lo scopo di creare un interfaccia unica di
accesso ai database, indipendentemente dal tipo di sistema
utilizzato. Per fare questo sono stati sviluppati diversi strati:
 Uno strato unico di accesso ai dati, composto da un insieme di
funzioni standard.
 Diversi drivers specifici per ogni tipo di database. A tal
proposito, esistono drivers per mySQL, Informix, DB2 e anche per
ODBC.
Per accedere, ad esempio, a qualsiasi fonte dati ODBC, è
necessario caricare l’apposito modulo (chiamato “odbc”). Nel caso
di odbc, l’operazione è estremamente semplice. Infatti, una volta
installate le win32 extensions si ha gia’ il modulo odbc
disponibile.
Vi illustro di seguito un esempio con il quale utilizzo il
linguaggio SQL per fare delle interrogazioni ad un database.
Facciamo le seguenti ipotesi di lavoro, riferite chiaramente
all’ambiente Microsoft Windows:
 Ho creato un database in Microsoft Access denominato
“db_articoli.mdb”.
 Ho creato un DSN (Data Source Name) con ODBC Manager, denominato
“articoli” (vedi il disegno). Esso associa il nome logico
“articoli” al file fisico “db_articoli.mdb”.
 Il database contiene una tabella, denominata “elenco_articoli”,
con due campi: codice e descrizione.

137
Voglio costruire un programma che mostra tutti i record della
tabella “elenco_articoli”. Ecco il semplice listato che risolve
questo problema:

import odbc
try:
s = odbc.odbc(‘articoli’) # mi collego al DSN
cur = s.cursor()
cur.execute(‘select * from elenco_articoli’)
rec = cur.fetchall()

print ‘Codice — Descrizione n’


for i in rec:
print i[0] + ‘ — ‘ + i[1] + ‘n’
except:
print ‘errore’
Il risultato del programma potrebbe essere il seguente:

Codice — Descrizione

233412 — matita
567543 — quaderno
533232 — gomma
Analizzando il codice si possono fare le seguenti osservazioni:
 la variabile “s” rappresenta l’oggetto database. La variabile è
stata inizializzata utilizzando il modulo odbc.

138
 la variabile “cur” rappresenta un cursore sul database. Quindi
attraverso questo cursore è possibile navigare nella struttura
del database.
 attraverso il metodo “execute” è possibile eseguire una
richiesta SQL.
 Il metodo “fetchall()” restituisce una lista contenente tutti i
record risultanti dalla query SQL. Ogni elemento della lista
“rec” è a sua volta una lista con i valori dei campi. In questo
caso i campi sono due:

Codice, nella posizione 0;


o
o Descrizione, nella posizione 1;
 Con un semplice ciclo posso stampare tutti i record della
tabella.
Tutte le funzioni utilizzate fanno parte dello strato standard
definito dalla libreria DB-API. Questo significa che il codice non
cambia se si utilizza un altro Database al posto di Microsoft
Access. L’unico cambiamento riguarda l’utilizzo del modulo
apposito.

Lezione 35di 40

Python e cgi
Nota: lezione in aggiornamento

Programmazione CGI
Python ha avuto un discreto successo nello sviluppo di
applicazioni web. In particolare viene utilizzato come linguaggio
di script richiamato da un server web attraverso la metodologia
CGI (common gateway interface). Il meccanismo CGI funziona nel
seguente modo:
è necessario avere installato sul proprio computer i seguenti
programmi:
 Un server web, come Apache o Internet Information Server. Con
questo tipo di programma è possibile costruire un sito internet
sul vostro computer. Il server web, infatti, risponde alle
chiamate in protocollo HTTP e fornisce come risposta dei files
HTML contenuti nel vostro Hard Disk.
 L’interprete python.
Potreste decidere di richiamare un programma python attraverso il
web accedendo al sito internet creato dal server web.
Per fare questo è necessario installare un modulo (CGI) che
139
permetta di invocare l’interprete python quando l’url presentata
al server web ne richieda l’esecuzione.
I moduli CGI si trovano in rete, ne esistono per tutti i
principali web server. Ad esempio per Apachee esiste
il mod_python, sia per linux che per Microsoft Windows.
Una volta installato mod_python, diventa semplice richiamare un
programma python.
Ad esempio: supponiamo che sulla nostra macchina sia stato
installato un web server che risponde alle richieste http con il
dominio “http://www.mio.it”.
Per invocare il programma è sufficiente digitare sul web la
seguente url: “http://www.mio.it/mio_programma.py”. In questo caso
è stato richiesto il programma python “mio_programma.py” contenuto
nella root directory del server web.
Vediamo ora un semplice programma CGI in python:

def main():
print ‘Content-type: text/html’
print
print ‘<HTML><HEAD><TITLE> Ciao,
mondo!</TITLE><BODY>” print ‘Ciao, mondo!’
print ‘</BODY></HTML>’

if (__name__ == ‘__main__’):
main()
Come si può osservare dal codice, il programma non fa altro che
scrivere in output un file html. Infatti il CGI cattura tutto
l’output del programma python e lo manda come risposta sul web.
L’utente che ha richiesto l’esecuzione del programma otterra’ in
risposta il seguente documento html:

<HTML><HEAD><TITLE> Ciao, mondo!</TITLE><BODY>


Ciao, mondo!
</BODY></HTML>

Questo meccanismo apre le porte alla creazione di siti web


dinamici. Ad esempio è possibile creare dinamicamente delle pagine
web che contengono dati contenuti dentro ad un database.
Per fare cio’ è sufficiente interfacciarsi ad un DBMS (come
abbiamo visto in precedenza) e costruirsi la pagina web da
stampare in output con la funzione “print”. Per aiutarci a
costruire in modo più rapido delle pagine web esiste una libreria
ad oggetti che permette di utilizzare delle classi predefinite.
Chi è interessato può trovare informazioni presso HtmlGen

Programmazione Web lato server


I programmi CGI hanno un difetto: non hanno un grande performance.
Infatti, tutte le volte che viene richiesta l’esecuzione di un
programma CGI, viene creato un nuovo processo per eseguire una
istanza dell’interprete python. Per risolvere questo problema
140
diversi costruttori hanno creato dei piccoli trucchi. Ad esempio:
in ambito Microsoft si possono creare dei programmi CGI sottoforma
di DLL, in modo che possano essere caricati in memoria una sola
volta e condivisi da tutte le richieste.
Tuttavia, per chi ha esigenze di prestazioni molto elevate, esiste
un unica vera soluzione: avere il server web nello stesso
programma CGI. Meglio ancora: avere il web server scritto nel
linguaggio utilizzato, nel nostro caso python.
In questa situazione diventa tutto più semplice; infatti ogni
chiamata CGI risulta essere equivalente ad una chiamata di una
funzione all’interno dello stesso programma, quindi all’interno
dello stesso processo. Solo in questo modo si possono raggiungere
le massime prestazioni.
Questo approccio è il medesimo usato da Java, quando esso è
utilizzato come linguaggio di sviluppo lato server (le famose
Servlet).
è stato sviluppato in python un intero web server, chiamato
“Zope“. Esso permette la pubblicazione di pagine html statiche, ma
permette anche il richiamo diretto di programmi python. Zope
dispone di una libreria di oggetti gia’ pronti per lo sviluppo di
applicazioni sul web. Inoltre esso dispone di una estensione
dell’html, denominata “DTML“, molto simile al meccanismo ASP di
Microsoft (Active Server Page), la quale permette anche ad un
programmatore inesperto di essere in grado di costruire dei siti
web dinamici senza grossi sforzi.
Chi fosse interessato alla piattaforma Zope puo andare sul sito
ufficiale www.zope.org. In questo sito si può scaricare il
software per la piattaforma linux e windows, consultare una
ricchissima guida ed eseguire il tutorial.

Lezione 36di 40
Multithreading

Quando un’applicazione richiede l’esecuzione di task molto lunghi,


nonché di operazioni potenzialmente parallelizzabili, è
generalmente utilizzata la programmazione concorrente, a cui ci
spesso si riferisce spesso con il termine “multithreading“.
Un’applicazione che sfrutta questo paradigma si dice multithread.
I thread non sono altro che sotto-processi eseguiti generalmente
in parallelo, e generati da un processo padre. Quando uno script
Python viene eseguito, abbiamo infatti la possibilità di creare
uno o più thread, che possono collaborare per il raggiungimento di

141
uno scopo comune, eventualmente condividendo le stesse risorse
computazionali, nonché gli stessi dati.
I processori dei computer più recenti sono generalmente multi-
core, offrendo quindi la possibilità di eseguire più operazioni
parallele, sfruttando al meglio le risorse computazionali del
calcolatore. Sebbene ciò sia vero, la programmazione concorrente
nasconde spesso alcune difficoltà non banali, che vanno gestite
opportunamente per evitare errori come deadlock o problemi
di sincronizzazione.
In questa lezione vedremo le principali opzioni offerte da Python
per programmare con i thread. A tale scopo, la lezione si
concentrerà sull’uso del modulo threading, mentre il
modulo _thread (successore di quello che su Python 2.x era il
modulo thread) non sarà trattato in quanto considerato deprecato
dalla community di Python.
Creazione ed avvio di un thread
La creazione di un thread con Python 3 necessita della definizione
di una classe, che erediti dalla classe Thread. Quest’ultima è
inclusa nel modulo threading, che va quindi importato. La classe
che definiremo (rappresentante dunque il nostro thread) dovrà
rispettare una precisa struttura: dovremo innanzitutto definire il
metodo __init__, ma soprattutto dovremo sovrascrivere il
metodo run.
Per capire meglio come procedere, vediamo un semplice esempio
pratico:
from threading import Thread
import time
class IlMioThread (Thread):
def __init__(self, nome, durata):
Thread.__init__(self)
self.nome = nome
self.durata = durata
def run(self):
print ("Thread '" + self.name + "' avviato")
time.sleep(self.durata)
print ("Thread '" + self.name + "' terminato")

Abbiamo così definito un classe IlMioThread, che possiamo


utilizzare per creare tutti i thread che vogliamo. Ogni thread di
questo tipo sarà caratterizzato dalle operazioni definite nel
metodo run, che in questo semplice esempio si limita a stampare
una stringa all’inizio ed alla fine della sue esecuzione. Nel
metodo __init__, inoltre, abbiamo specificato due parametri di
inizializzazione (che poi sono utilizzati nel metodo run): tali
parametri saranno specificati in fase di creazione del thread.
Vediamo ora come creare uno o più thread, a partire dalla
precedente definizione della classe IlMioThread:
from random import randint
# Creazione dei thread
thread1 = IlMioThread("Thread#1", randint(1,100))

142
thread2 = IlMioThread("Thread#2", randint(1,100))
thread3 = IlMioThread("Thread#3", randint(1,100))
# Avvio dei thread
thread1.start()
thread2.start()
thread3.start()
# Join
thread1.join()
thread2.join()
thread3.join()
# Fine dello script
print("Fine")

In questo esempio, abbiamo creato tre thread, ognuno con le sue


proprietà nome e durata (in accordo alla definizione del
metodo __init__). Li abbiamo poi avviati mediante il metodo start,
il quale si limita ad eseguire il contenuto del
metodo run precedentemente definito. Si noti che il
metodo start non è bloccante: quando esso viene eseguito, il
controllo passa subito alla riga successiva, mentre il thread
viene avviato in background. Per attendere che un thread termini,
è necessario eseguire una operazione di join, come fatto nel
codice precedente.
Sincronizzazione
Il modulo threading di Python include anche un semplice meccanismo
di lock, che permette di implementare la sincronizzazione tra i
thread. Un lock non è altro che un oggetto (tipicamente
accessibile da più thread) di cui un thread deve “entrare in
possesso” prima di poter procedere all’esecuzione di una sezione
protetta di un programma. Tali lock sono creati eseguendo il
metodo Lock(), definito nel summenzionato modulo threading.
Una volta ottenuto il lock, possiamo utilizzare due metodi che ci
permettono di sincronizzare l’esecuzione di due (o più) thread: il
metodo acquire per acquisire il controllo del lock, ed il
metodo release per rilasciarlo. Il metodo acquire accetta un
parametro opzionale che, se non specificato o impostato a True,
forza il thread a sospendere la sua esecuzione finché il lock
verrà rilasciato e potrà quindi essere acquisito. Se, invece, il
metodo acquire viene eseguito con argomento pari a False, esso
ritorna immediatamente un risultato booleano, che vale True se il
lock è stato acquisito, oppure False in caso contrario.
L’esempio seguente mostra come utilizzare il meccanismo dei lock
su Python:
import threading
import time.
from random import randint
# Definizione del lock
threadLock = threading.Lock()
class IlMioThread (threading.Thread):
def __init__(self, nome, durata):

143
threading.Thread.__init__(self)
self.nome = nome
self.durata = durata
def run(self):
print ("Thread '" + self.name + "' avviato")
# Acquisizione del lock
threadLock.acquire()
time.sleep(self.durata)
print ("Thread '" + self.name + "' terminato")
# Rilascio del lock
threadLock.release()
# Creazione dei thread
thread1 = IlMioThread("Thread#1", randint(1,100))
thread2 = IlMioThread("Thread#2", randint(1,100))
thread3 = IlMioThread("Thread#3", randint(1,100))
# Avvio dei thread
thread1.start()
thread2.start()
thread3.start()
# Join
thread1.join()
thread2.join()
thread3.join()
# Fine dello script
print("Fine")

Abbiamo modificato il codice precedente tramite l’uso dei lock, in


modo che essi vengano eseguito in sequenza: il primo thread,
infatti, acquisirà il lock e verrà eseguito mentre gli altri due
thread rimarranno in attesa (poiché il metodo acquire è eseguito
senza parametri). Al termine dell’esecuzione del primo thread, il
secondo otterrà il lock e l’ultimo thread dovrà rimanere ancora in
attesa, fino al termine dell’esecuzione del secondo.
Conclusioni
Come già anticipato, l’uso dei thread permette di realizzare
applicazioni concorrenti anche molto complesse, che possono quindi
essere difficili da gestire. La gestione corretta della
sincronizzazione è fondamentale per il corretto funzionamento di
un’applicazione, e può essere gestita sfruttando anche altre
funzionalità messe a disposizione da Python. Per approfondire
meglio, suggeriamo di fare riferimento alla documentazione
ufficiale.

144
Lezione 37di 40

File JSON: leggere e scrivere

Nell’ultimo decennio, si è affermato sempre di più JSON (acronimo


che sta per JavaScript Object Notation), formato di file di testo
usato per la strutturazione dei dati, e basato sulla sintassi per
la definizione degli oggetti su Javascript.
Il formato JSON è spesso usato per lo scambio di dati tra moduli
di applicazioni web, nonché per la gestione di file di
configurazione o, più semplicemente, per archiviare dati in
formato testuale. Data la grande diffusione di questo formato, è
bene capire come leggere e manipolare dati di questo tipo anche
tramite Python. In questa lezione ci occuperemo proprio di questo,
supponendo che il lettore conosca già questo formato di dati; se
così non fosse, rimandiamo ad un apposito approfondimento di
HTML.it su questo specifico argomento.
Lettura di file JSON
Trattandosi di un formato basato su file di testo, teoricamente
potremmo accedere e leggere qualunqe file JSON tramite la
funzione open, che abbiamo già trattato discutendo della gestione
dei file. Nelle ultime versioni di Python, però, è incluso tra gli
altri un modulo specifico per l’interfacciamento con i file in
formato JSON: il modulo json, appunto.
La lettura di un file JSON avviene sempre tramite open, ma il
risultato di questa operazione viene poi passato ad un’altra
funzione, load, proprio del modulo json. Vediamo un semplice
esempio qui di seguito:
import json
data = json.load(open("dati.json"))

La funzione load non fa altro che leggere automaticamente il


contenuto del file, trasformandolo in una rappresentazione molto
più “vicina” a Python: quella dei dizionari. Supponiamo, ad
esempio, di avere a disposizione un file dati.json, contenente il
testo seguente:
{
"mappe": [
{
"id": "01",
"nome": "test"
},
{
"id": "02",
"nome": "prova"
}
],
"dispositivo": {
"power": "on",
"parametri": {
145
"p1": "0.0235849594",
"p2": "0.4877774151",
"p3": "2.0000022549"
}
}
}

L’intero oggetto JSON sarà trasformato in un dizionario con 2


proprietà, mappe e dispositivo, entrambe accessibili tramite la
sintassi basata sulle parentesi quadre. Discorso analogo si
applica agli array JSON (ad esempio quello identificato dalla
parola mappe), che sono trasformati in liste. Di seguito vediamo
alcuni esempi pratici e chiarificatori:
import json
data = json.load(open("dati.json"))
data["mappe"] #ritorna una lista contenente
due dizionari
data["mappe"][0]["id"] #ritorna la stringa "01"
data["dispositivo"]["parametri"] #ritorna un dizionario
contenente le chiavi p1, p2 e p3

Scrittura di file JSON


Anche per la scrittura di un file JSON è preferibile ricorrere
alle funzionalità di comodo implementate nel modulo json,
piuttosto che lavorare a basso livello con la scrittura dei file
tradizionali.
Come già accennato poc’anzi, un oggetto JSON può essere facilmente
rappresentato da un dizionario in Python. Per questo motivo, per
scrivere un file JSON è necessario innanzitutto organizzare i dati
all’interno di un dizionario, seguendo la struttura dell’output
JSON desiderato. Una volta ottenuto questo dizionario, che
supponiamo per il momento memorizzato in una variabile data, è
sufficiente utilizzare la funzione dump:
import json
data = { ... }
with open("output.json", "w") as outfile:
json.dump(data, outfile)

Anche in questo caso, quindi, il procedimento è molto semplice e


lineare. Per semplicità, vediamo un esempio concreto. Supponiamo
di volere scrivere su un file output.json il JSON d’esempio visto
in precedenza. Ecco come potremmo organizzare il nostro codice al
fine di creare l’oggetto data dell’esempio precedente:
data = {'mappe': [{'id': '01', 'nome': 'test'},
{'id': '02', 'nome': 'prova'}],
'dispositivo': {'power': 'on',
'parametri': {'p1': '0.0235849594',
'p2': '0.4877774151',
'p3': '2.0000022549'}}}

146
Si noti che, così facendo, non abbiamo specificato nessuna
informazione relativa all’indentazione da utilizzare nel file
generato come output. Possiamo aggiungere questa ulteriore
possibilità sfruttando il parametro opzionale indent, che
specifica il numero di spazi di usare per l’indentazione:
with open("output.json", "w") as outfile:
json.dump(data, outfile, indent=4) #indentazione con 4
spazi

Per ulteriori dettagli, si consiglia di consultare


la documentazione ufficiale relativa al modulo json.

Lezione 38di 40
File XML: leggere e scrivere

Oltre a JSON, di cui si è discusso nella lezione precedente, uno


dei formati più utilizzati per la strutturazione e l’interscambio
dei dati è XML (acronimo che sta per eXtensible Markup Language).
Diffuso già da diversi anni, XML può essere utilizzato per diversi
scopi, e strutturato in modi più o meno standard, definendo
convenzioni che hanno portato all’affermazione di formati
quali RSS, SVG ed RDF/XML.
In questo articolo vedremo come utilizzare Python per effettuare
il parsing di un file XML, nonché per scrivere file XML. A tale
fine, utilizzeremo il modulo xml.etree.ElementTree, incluso in
tutte le installazione di Python più recenti. Come nel resto della
guida, utilizzeremo Python 3 come versione di riferimento del
linguaggio. Dal momento che non ci soffermeremo sul formato XML,
chi volesso approfondirne le caratteristiche può fare riferimento
alla guida a XML di HTML.it.
Leggere file XML
Come già detto quando abbiamo affrontato la gestione dei file
JSON, anche con i file XML è teoricamente possibile una gestione
basata sulle API per l’interazione con i normali file di testo.
Sebbene abbiamo già trattato la gestione dei file in precedenza, è
bene sottolineare che ciò è poco sensato, dal momento che Python
ci offre una soluzione molto più performante, finalizzata proprio
all’interfacciamento con i file XML: il
modulo xml.etree.ElementTree.
La prima cosa da fare è quindi importare tale modulo:
import xml.etree.ElementTree as ET

A questo punto, avremo due opzioni per la lettura dell’XML:


possiamo leggere il contenuto di un file XML, oppure effettuare il
147
parsing di una stringa contenente XML valido. Nel primo caso,
procediamo come segue:
tree = ET.parse('test.xml')
root = tree.getroot()

In alternativa, se vogliamo leggere da una stringa, utilizzeremo


il metodo fromstring():
root = ET.fromstring('Test')

In entrambi i casi, la variabile root rappresenterà l’elemento


radice del file XML. Come ogni elemento, esso include una
proprietà denominata attrib, contenente un dizionario
(eventualmente vuoto) di attributi. Ogni attributo è quindi una
entry del dizionario, in cui il nome dell’attributo è la chiave,
ed il suo contenuto è il valore. Per semplicità, consideriamo il
seguente file XML:
<root test="prova">
<elemento>Test</elemento>
</root>

In questo caso, l’elemento root conterrà un dizionario con un solo


attributo. Per accedere al valore di tale attributo, potremo
procedere così:
import xml.etree.ElementTree as ET
tree = ET.parse('test.xml')
root = tree.getroot()
root.attrib['test']

Va detto, inoltre, che nel caso in cui sia specificato


un namespace, questo andrà anteposto alla chiave di ogni
attributo. Facciamo un esempio:
<h:table xmlns:h="http://www.w3.org/TR/html4/" h:test="prova">
<h:tr class="myclass">
<h:td>Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>

Supponiamo di volere accedere all’attributo test (identificato dal


namespace h:, ovvero http://www.w3.org/TR/html4/). In questo caso,
la chiave da utilizzare su Python per accedere al valore
dell’attributo non sarà semplicemente test,
bensì {http://www.w3.org/TR/html4/}test:
root.attrib['{http://www.w3.org/TR/html4/}test']

Nel seguito, per semplicità, ci riferiremo sempre ad esempi senza


namespace, sebbene questa considerazione può essere utile per
gestire file XML più complessi.
Considerando ora il file XML seguente, immaginiamo ora di
volere accedere agli elementi figli di root:
148
<root>
<elemento>Test1</elemento>
<elemento>Test2</elemento>
<elemento>Test3</elemento>
</root>

Tutti i nodi figli di root possono essere esplorati con un


semplice ciclo for. L’esempio seguente chiarisce meglio questo
concetto:
for child in root:
print(child.tag) #la proprieta' tag fornisce il nome
dell'elemento, incluso l'eventuale namespace
print(child.text) #la proprieta' text fornisce il contenuto
testuale dell'elemento
print(child.attrib) #ovviamente, possiamo ottenere gli
attributi di ogni elemento figlio

Infine, possiamo scegliere di utilizzare la sintassi delle


parentesi quadre, accedendo agli elementi figli tramite l’indice
di posizione. Ad esempio, il primo figlio di root sarà accessibile
tramite la sintassi root[0], il secondo figlio tramite root[1] e
così via.
Oltre alle funzionalità di base appena viste, ce ne sono alcune
più avanzate (ad esempio implementando ricerche tramite XPath) che
possono essere approfondite facendo riferimento
alla documentazione ufficiale.
Modificare un file XML
Una volta caricato un file XML, possiamo modificarne il contenuto
sfruttando un comodo metodo messo a disposizione da Python: il
metodo write della classe ElementTree. Supponiamo di avere
caricato il contenuto di un file XML, e di modificarlo come segue:
import xml.etree.ElementTree as ET
#Parsing da file
tree = ET.parse('test.xml')
root = tree.getroot()
root[0].text = 'Prova' #modifica del testo di un
elemento
root[1].attrib['nome'] = 'tizio' #inserimento (o modifica) di un
attributo
root.remove(root[2]) #rimozione di un elemento

A questo punto, usiamo il metodo write dell’oggetto tree per


scrivere il file XML così modificato su un nuovo file:
tree.write('output.xml')

Scrivere file XML


Oltre a modificare un file XML, possiamo anche crearne uno da
zero. Immaginiamo, ad esempio, di volere creare un file XML come
il seguente:

149
<rubrica>
<persona id="1">
<nome>Vito</nome>
<cognome>Gentile</cognome>
</persona>
</rubrica>

La funzione SubElement() permette di creare nuovo elementi a


partire da un elemento dato. Ecco quindi come creare un nuovo
albero XML come il precedente:
root = ET.Element('rubrica')
persona = ET.SubElement(root, 'persona')
persona.attrib['id'] = '1'
nome = ET.SubElement(persona, 'nome')
nome.text = 'Vito'
cognome = ET.SubElement(persona, 'cognome')
cognome.text = 'Gentile'
tree = ET.ElementTree(root)
tree.write('output.xml')

Abbiamo innanzitutto definito un nuovo elemento radice, creando


una istanza della classe Element. Quindi, la funzione SubElement è
utilizzata per creare gli elementi figli: il primo argomento
rappresenta l’elemento genitore, mentre il secondo argomento è il
nome del nuovo elemento (che è rappresentato dal valore di ritorno
della funzione SubElement). Abbiamo poi utilizzato le stesse
istruzioni viste in precedenza per creare gli attributi e per
valorizzare il contenuto testuale degli elementi.
Infine, è stato sufficiente creare una istanza della
classe ElementTree, mediante la quale utilizzare (come già visto)
il metodo write, che ci permette di generare il file XML appena
definito.
Per approfondire i concetti visti in questa lezione, rimandiamo
alla documentazione ufficiale.

150
Lezione 39di 40
Future statement

Sebbene il focus di questa guida sia stato centrato su Python 3,


ci sono ancora molti sviluppatori che, per necessità o comodità,
continuano ad utilizzare le versioni 2.x di questo linguaggio di
programmazione. Purtroppo, le versioni dei due linguaggi
presentano alcune differenze che, sebbene apparentemente semplici,
comportano la necessità di scrivere codice specificamente pensato
per l’una o l’altra versione.
Esiste però la possibilità, per gli sviluppatori che utilizzano
Python 2, di sfruttare alcune utili funzionalità introdotte con
Python 3. Tale possibilità si basa sull’uso dei future statement,
ovvero del modulo fittizio __future__. In questa lezione vedremo
come utilizzarlo, facendo riferimento alla versione 2.7 di Python.
Utilizzare print come funzione
Un primo esempio che ci permette di capire l’utilità del
modulo __future__ consiste nell’utilizzare la funzione print, che
su Python 2.x è invece definita come statement:
print "Ciao!" #sintassi supportata solo su Python 2.x

Volendo “avvicinare” un po’ di più la sintassi a quella di Python


3, possiamo sfruttare il modulo __future__ come segue:
from __future__ import print_function
print("Ciao!") #sintassi di Python 3, riportata anche su Python
2.x

Come si vede, è basato una semplice import per cambiare (di fatto)
la sintassi di Python 2.x. È importante notare che questa sintassi
non ha soltanto definito una nuova funzione print, ma ha
anche eliminato lo statement print. Ad esempio, il codice seguente
causerebbe un errore di sintassi:
from __future__ import print_function
print "Ciao!" #errore di sintassi

Sebbene la sintassi utilizzata coincida con quella che permette di


includere un modulo, quello che abbiamo fatto è, in realtà,
l’utilizzo di un future statement. Si tratta di una direttiva del
compilatore che forza l’interprete Python ad interpretare le righe
che includono (in questo caso) la funzione print in modo da
supportare le versioni future del linguaggio. Si noti che, proprio
per questo motivo, i future statemente devono essere
necessariamente posti all’inizio di ogni file in cui vengono
utilizzati.
Divisioni
Un’altra semplice funzionalità che può tornare molto utile agli
sviluppatori di Python 2.x è la gestione delle divisioni tra
interi. Su Python 2.x, infatti, l’operatore / funziona in modo
analogo a linguaggi come C, C++ e Java, in cui il tipo degli

151
operandi può cambiare radicalmente il risultato della divisione.
Facciamo un esempio:
3 / 2 #su Python 2.x, il risultato è 1. Su Python 3, il
risultato è 1.5
3.0 / 2 #sia su Python 2.x che su Python 3, il risultato è 1.5
3 // 2 #sia su Python 2.x che su Python 3, il risultato è 1

L’esempio precedente mostra come Python 3 differenzi il risultato


dell’operazione grazie all’introduzione dell’operatore //, che
effettua esplicitamente l’arrotondamento per difetto sul risultato
della divisione. Su Python 2.x, invece, non c’è differenza
tra / e //. Per importare questa funzionalità su Python 3,
possiamo sfruttare ancora una volta un future statement:
from __future__ import division
3 / 2 #risultato: 1.5
3 // 2 #risultato: 1

Altre funzionalità
Oltre alle due funzionalità relative alla funzione print ed alle
divisioni tra interi, i future statements permettono di importare
molte altre caratteristiche della versione 3 del linguaggio Python
anche su Python 2.x. Una di queste è il costrutto with, già visto
nella lezione sui file, che può essere importato come segue:
from __future__ import with_statement
with open('text.txt', 'r') as f: #sintassi di Python 3 utilizzata
su Python 2.x
print f.read() #sintassi standard di Python 2.x

Altre funzionalità che possono essere incorporate tramite i future


statement sono identificate dalle seguenti stringhe (che devono
seguire la solita sintassi from __future__ import):
 nested_scopes
 generators
 absolute_import
 unicode_literals
Per maggiori dettagli, è comunque possibile consultare
la documentazione ufficiale.
Infine, è bene segnalare la possibilità di installare il modulo
aggiuntivo python-future (che può essere ottenuto mediante pip ),
che implementa alcune ulteriori funzionalità, al fine di
facilitare il porting del codice da Python 2.x a Python 3, nonchè
di garantire la retrocompatibilità del codice Python 3 su Python
2.x. Ulteriori informazioni possono essere reperite sul sito
ufficiale del progetto.

152
Lezione 40di 40

pip: il package manager per Python

Nella lezione sui moduli abbiamo accennato brevemente alla


possibilità di installare moduli e package aggiuntivi che non
fanno parte della libreria standard. In questa appendice, vedremo
come usare il package manager pip per installare moduli e package
dal Python Package Index.
Il Python Package Index e pip
Il Python Package Index (PyPI) è un repository che contiene decine
di migliaia di package scritti in Python. Chiunque può scaricare
package esistenti o condividere nuovi package su PyPI. PyPI è
anche conosciuto con il nome di Cheese Shop, dallo sketch del
cheese shop di Monty Python.
È possibile accedere ai package del Python Package Index sia
tramite un browser (all’indirizzo https://pypi.org/), sia tramite
un tool chiamato pip.
pip è un tool che ci permette di cercare, scaricare ed installare
package Python che si trovano sul Python Package Index. Il nome è
un acronimo ricorsivo, che significa Pip Installs Packages. pip ci
consente inoltre di gestire i package che abbiamo già scaricato,
permettendonci di aggiornarli o rimuoverli.
Installare pip
pip è già incluso nelle versioni di Python >= 3.4 e >= 2.7.9. Per
verificare se questo tool è già installato, possiamo eseguire il
comando python3 -m pip -V o semplicemente pip3 -V dal terminale.
Se pip è già installato, questo comando ci mostrerà la versione
installata.
Se, invece, non abbiamo installato pip, possiamo rimediare
semplicemente scaricando ed eseguendo con Python lo script get-
pip.py.

Aggiornare pip
È anche consigliato verificare che la versione di pip sia sempre
aggiornata. Per aggiornarlo, possiamo usare il comando python3 -m
pip install -U pip, o più semplicemente pip install -U pip.
Usare pip
pip supporta una serie di comandi che ci permettono, tra le altre
cose,
di cercare, scaricare, installare, aggiornare e rimuovere package.
Vediamo in dettaglio il funzionamento dei comandi più comuni.

153
Cercare package
Per cercare package nel Python Package Index, possiamo usare il
comando python3 -m pip search KEYWORD. Ad esempio, se volessimo
cercare il package BeautifulSoup, possiamo eseguire il comando
seguente, osservando il relativo output:
$ python3 -m pip search beautifulsoup
beautifulscraper (1.1.0)
- Python web-scraping library that wraps urllib2 and
BeautifulSoup.
scrapy-beautifulsoup (0.0.2)
- Simple Scrapy middleware to process non-well-formed HTML with
BeautifulSoup
ipython-beautifulsoup (0.3)
- Custom rendering of beautifulsoup objects in IPython notebook
and qtconsole
django-beautifulsoup-test (1.1.3)
- TestCase class for using BeautifulSoup with Django tests
BeautifulSoup (3.2.1)
- HTML/XML parser for quick-turnaround applications like screen-
scraping.
beautifulsoup4-slurp (0.0.2)
- Slurp packages Beautifulsoup4 into command line.
beautifulsoup4 (4.6.0)
- Screen-scraping library
INSTALLED: 4.6.0 (latest)
beautifulsoupselect (0.2)
- Simple wrapper to integrate BeautifulSoup and soupselect.py in
a single package
collective.soupstrainer (2.0)
- Clean up HTML using BeautifulSoup and filter rules.
Detextile (0.0.3)
- Convert HTML to Textile syntax using BeautifulSoup.
spider-egg (0.1.0)
- a template for python crawler with requests and beautifulsoup
ElementSoup (rev452)
- ElementTree wrapper for BeautifulSoup HTML parser
...

Si noti che, oltre ai risultati della ricerca, l’ouput ci indica


anche quali package sono installati e se ne esiste una versione
più recente. In questo caso l’ultima versione di beautifulsoup è
già installata.
Installare package
Per installare un package, basta eseguire python3 -m pip install
PACKAGE. Ad esempio, se volessimo installare il package requests,
possiamo eseguire:
$ python3 -m pip install requests
Collecting requests
Downloading requests-2.18.4-py2.py3-none-any.whl (88kB)
100% |████████████████████████████████| 92kB 1.3MB/s
Collecting urllib3<1.23,>=1.21.1 (from requests)
154
Downloading urllib3-1.22-py2.py3-none-any.whl (132kB)
100% |████████████████████████████████| 133kB 2.4MB/s
Collecting certifi>=2017.4.17 (from requests)
Downloading certifi-2017.7.27.1-py2.py3-none-any.whl (349kB)
100% |████████████████████████████████| 358kB 869kB/s
Collecting idna<2.7,>=2.5 (from requests)
Downloading idna-2.6-py2.py3-none-any.whl (56kB)
100% |████████████████████████████████| 61kB 3.7MB/s
Collecting chardet<3.1.0,>=3.0.2 (from requests)
Using cached chardet-3.0.4-py2.py3-none-any.whl
Installing collected packages: urllib3, certifi, idna, chardet,
requests
Successfully installed certifi-2017.7.27.1 chardet-3.0.4 idna-2.6
requests-2.18.4 urllib3-1.22

Come possiamo vedere, pip non solo scarica e installa


automaticamente il package, ma si assicura che tutte le dipendenze
richieste dal package siano soddisfatte.
Se specifichiamo solo il nome del package, pip installerà
automaticamente la versione più recente di esso. Se invece
vogliamo installare una versione specifica, possiamo usare il
comando python3 -m pip install 'package==X.Y.Z'. Ad esempio, il
seguente comando installerà requests versione 2.16.5:
$ python3 -m pip install 'requests==2.16.5'
Collecting requests==2.16.5
Downloading requests-2.16.5-py2.py3-none-any.whl (87kB)
100% |████████████████████████████████| 92kB 911kB/s
Collecting urllib3<1.22,>=1.21.1 (from requests==2.16.5)
Downloading urllib3-1.21.1-py2.py3-none-any.whl (131kB)
100% |████████████████████████████████| 133kB 1.4MB/s
Collecting idna<2.6,>=2.5 (from requests==2.16.5)
Downloading idna-2.5-py2.py3-none-any.whl (55kB)
100% |████████████████████████████████| 61kB 2.9MB/s
Collecting certifi>=2017.4.17 (from requests==2.16.5)
Using cached certifi-2017.7.27.1-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests==2.16.5)
Using cached chardet-3.0.4-py2.py3-none-any.whl
Installing collected packages: urllib3, idna, certifi, chardet,
requests
Successfully installed certifi-2017.7.27.1 chardet-3.0.4 idna-2.6
requests-2.16.5 urllib3-1.22

È inoltre possibile usare gli operatori <, <=, >, >= per
specificare versioni massime e minime. Queste espressioni si
possono anche combinare usando la virgola (,), permettendoci di
specificare sia una versione minima che una massima. Ad esempio,
il seguente comando installerà la versione più recente
di requests 2.16.x, ma non installerà la versione 2.17 o
successive, anche se sono disponibili:
$ python3 -m pip install 'requests>=2.16,<2.17'
Collecting requests<2.17,>=2.16
Using cached requests-2.16.5-py2.py3-none-any.whl
155
Collecting chardet<3.1.0,>=3.0.2 (from requests<2.17,>=2.16)
Using cached chardet-3.0.4-py2.py3-none-any.whl
Collecting urllib3<1.22,>=1.21.1 (from requests<2.17,>=2.16)
Using cached urllib3-1.21.1-py2.py3-none-any.whl
Collecting idna<2.6,>=2.5 (from requests<2.17,>=2.16)
Using cached idna-2.5-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests<2.17,>=2.16)
Using cached certifi-2017.7.27.1-py2.py3-none-any.whl
Installing collected packages: chardet, urllib3, idna, certifi,
requests
Successfully installed certifi-2017.7.27.1 chardet-3.0.4 idna-2.6
requests-2.16.5 urllib3-1.22

Come possiamo vedere dall’output, pip ha installato la versione


2.16.5, cioè la versione più recente di requests 2.16.x.
Nel caso di applicazioni complesse che hanno diverse dipendenze,
non è necessario installare manualmente tutti i package necessari
a uno a uno. pip ci dà la possibilità di elencare le dipendenze in
un file requirements.txt, e di eseguire il comando python3 -m pip
install -r requirements.txt per installarle tutte simultaneamente.
Esistono infine diverse opzioni aggiuntive, che ci permettono
anche di installare da repository diversi dal PyPI o direttamente
da version control system. La descrizione di questa e altre
opzioni avanzate può essere visualizzata eseguendo il
comando python3 -m pip install --help (o python3 -m pip install -
h).
Aggiornare package
Per aggiornare un package possiamo usare il comando install in
combinazione con l’opzione -U o --upgrade. Ad esempio, per
aggiornare il package requests alla versione più recente, possiamo
usare:
$ python3 -m pip install -U requests
Collecting requests
Using cached requests-2.18.4-py2.py3-none-any.whl
Collecting idna<2.7,>=2.5 (from requests)
Using cached idna-2.6-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests)
Using cached chardet-3.0.4-py2.py3-none-any.whl
Collecting urllib3<1.23,>=1.21.1 (from requests)
Using cached urllib3-1.22-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests)
Using cached certifi-2017.7.27.1-py2.py3-none-any.whl
Installing collected packages: idna, chardet, urllib3, certifi,
requests
Successfully installed certifi-2017.7.27.1 chardet-3.0.4 idna-2.6
requests-2.18.4 urllib3-1.22
Copy
Elencare package
Per vedere una lista di package installati, possiamo utilizzare il
comando python3 -m pip list:
156
$ python3 -m pip list
apt-xapian-index (0.47)
apturl (0.5.2)
asn1crypto (0.22.0)
beautifulsoup4 (4.6.0)
certifi (2017.7.27.1)
chardet (3.0.4)
command-not-found (0.3)
cryptography (1.9)
cupshelpers (1.0)
distro-info (0.17)
html5lib (0.999999999)
httplib2 (0.9.2)
...

Aggiungendo l’opzione --outdated possiamo ottenere una lista di


package che non sono correntemente aggiornati all’ultima versione.
È anche possibile visualizzare informazioni specifiche per un
singolo package, usando il comando python3 -m pip show PACKAGE. Ad
esempio, per vedere le informazioni sul package requests, possiamo
eseguire:
$ python3 -m pip show requests
Name: requests
Version: 2.18.4
Summary: Python HTTP for Humans.
Home-page: http://python-requests.org
Author: Kenneth Reitz
Author-email: me@kennethreitz.org
License: Apache 2.0
Location: .../lib/python3.6/site-packages
Requires: urllib3, certifi, chardet, idna

Disinstallare package
Per rimuovere un package che abbiamo installato in precedenza,
basta eseguire python3 -m pip uninstall PACKAGE. Ad esempio, se
volessimo rimuovere il package requests, possiamo eseguire:
$ python3 -m pip uninstall requests
Uninstalling requests-2.18.4:
.../lib/python3.6/site-packages/requests-2.18.4.dist-
info/DESCRIPTION.rst
.../lib/python3.6/site-packages/requests-2.18.4.dist-
info/INSTALLER
.../lib/python3.6/site-packages/requests-2.18.4.dist-
info/METADATA
.../lib/python3.6/site-packages/requests-2.18.4.dist-info/RECORD
.../lib/python3.6/site-packages/requests-2.18.4.dist-info/WHEEL
.../lib/python3.6/site-packages/requests-2.18.4.dist-
info/metadata.json
.../lib/python3.6/site-packages/requests-2.18.4.dist-
info/top_level.txt
157
.../lib/python3.6/site-packages/requests/__init__.py
...
Proceed (y/n)? y
Successfully uninstalled requests-2.18.4

Così come per il comando install, il comando uninstall ci permette


di specificare un elenco di package da disinstallare mediante
l’uso di un file requirements.txt e del comando python3 -m pip
uninstall -r requirements.txt.
Controllare package
Infine, il comando python3 -m pip check ci permette di verificare
se tutte le dipendeze dei package correntemente installati siano
state soddisfatte o meno:
$ python3 -m pip check
No broken requirements found.

pip include altri comandi e opzioni, che possono essere esplorati


usando i comandi python3 -m pip --help e python3 -m pip COMMAND --
help. È anche possibile consultare online la documentazione
ufficiale di pip.

158
INDICE Pag.

Primi passi con il linguaggio

1 Introduzione e un po' di storia …………………………………………………………………………………..... 3


2 Perché usare Python ………………………………………………………………………………………….... 5
3 Installare Python …………………………………………………………………………………………………...... 8
4 L'interprete Python e l'IDLE ……………………………………………………………………………………………….. ..11
5 Le funzioni print e input ………………………………………………………………………………………………….....15
6 Indentazione …………………………………………………………………………………………………………………17

Variabili e tipi di dati

7 Variabili e commenti in Python ……………………………………………………………………………………………. 19

8 Numeri e operatori logici in Python ………………………………………………………………………………………… 23

9 Stringhe in Python ……………………………………………………………………………………………………………. 25

10 Tuple ………………………………………………………………………………………………………………………….. 30

11 Liste in Python ……………………………………………………………………………………………………………….. 32

12 Dizionari …………………………………………………………………………………………………………………….. 35

13 Set e frozenset ……………………………………………………………………………………………………………… 39

14 Tipi built-in: riepilogo ………………………………………………………………………………………………………… 43

La programmazione con Python

15 Istruzioni Condizionali ………………………………………………………………………………………………………. 47

16 Cicli: for e while ……………………………………………………………………………………………………………… 51

17 List/Set/Dict Comprehension ………………………………………………………………………………………………. 56

18 Funzioni in Python …………………………………………………………………………………………………………... 60

19 Gestione delle eccezioni …………………………………………………………………………………………………… 68

20 Gestire i file …………………………………………………………………………………………………………………. 74

21 Moduli …………………………………………………………………………………………………………………………. 81

22 Creare nuovi moduli ………………………………………………………………………………………………………… 86

23 Package ……………………………………………………………………………………………………………………… 88

159
La programmazione a oggetti con Pytho

24 La programmazione ad oggetti ……………………………………………………………………………………………. 89

25 Classi in Python …………………………………………………………………………………………………………….. 95

26 Ereditarietà ………………………………………………………………………………………………………………….. 100

27 Metodi speciali ……………………………………………………………………………………………………………… 102

28 Overloading degli operatori ………………………………………………………………………………………………... 105

GUI con Python


29 GUI sui Python: i Framework da usare ……………………………………………………………………………………110

30 Tkinter …………………………………………………………………………………………………………………………115

31 PyQt …………………………………………………………………………………………………………………………..120

32 Kivy …………………………………………………………………………………………………………………………….126

Approfondimenti

33 Python e Java ………………………………………………………………………………………………………………….136


34 Python e i database …………………………………………………………………………………………………………...137
35 Python e cgi …………………………………………………………………………………………………………………….139
36 Multithreading …………………………………………………………………………………………………………………..141
37 File JSON: leggere e scrivere …………………………………………………………………………………………………145
38 File XML: leggere e scrivere …………………………………………………………………………………………………..147
39 Future statement ………………………………………………………………………………………………………………..151
40 pip: il package manager per Python ………………………………………………………………………………………….153

160