Corso Python
Le dispense per le lezioni di programmazione del prof
In questo gruppo trovate le dispense di base sulla programmazione Python per iniziare il percorso di apprendimento
–
La base di tutta la programmazione moderna: impariamo a programmare in Python secondo il paradigma della OOP!
–
Una semplice libreria Python, basata su wxWidgets per la programmazione grafica e l’implementazione di programmi basati su GUI
–
L’ultima sezione del sito, dedicata allo studio e agli approfondimenti di argomenti spesso “tangenti” al corso di programmazione Python
Python: primi passi
Python è un linguaggio di programmazione moderno, con una sintassi volutamente semplice e facile da imparare, utilizzabile in vari contesti anche molto diversi fra loro:
- sviluppo di applicazioni desktop,
- generazione siti e applicazioni Web,
- scripting per l’amministrazione di sistema,
- calcolo scientifico e numerico,
- gestione database,
- giochi,
- grafica 3D,
- etc...
In questo corso introduttivo noi cercheremo di utilizzare Python per fare i nostri primi passi nell’ambito della programmazione e passo dopo passo ci porremo dei piccoli obiettivi pratici da raggiungere per fare qualche esperienza reale di programmazione.
Io non vedo l’ora ☺️
Quattro chiacchiere su Python
In questo capitolo ci sono alcune informazioni di base su Python e la sua storia. Giusto per capire cosa è, da dove viene e per provare a capire dove sta andando.
Se non sei per le chiacchiere… salta al prossimo capitolo, quello dell’installazione!!
Python, la storia (brevissima)

Python è un linguaggio ideato da Guido van Rossum all’inizio degli anni novanta. Il nome fu scelto (tenetevi alla sedia) per via della sua passione per i Monty Python, un gruppo comico britannico attivo negli anni settanta. Per rendervi conto della incredibile comicità del gruppo… ecco uno dei primi video su YouTube ottenuto cercando Monty Python ita. Dura 84 minuti. Noi non supereremo il minuto 4…
La storia racconta che durante le vacanze di Natale del 1989, Guido progettò questo nuovo linguaggio, cercando di mettere in esso tutte le caratteristiche positive che aveva trovato negli altri linguaggi di programmazione, provando ad evitare forzature o strutture non lineari.
Subito Python diventa uno strumento imprescindibile per l’azienda in cui Guido lavora. Questo fatto e la licenza opensource con cui van Rossum distribuisce la sua creatura determinano la sua fortuna: Python diventa brevemente uno dei linguaggi più utilizzati e seguiti da una comunità attiva di sviluppatori.
Python ha avuto il suo momento di massima espansione con la versione 2.x del linguaggio, rilasciata nel 2000, momento in cui è passato da “linguaggio di programmazione settoriale” a “linguaggio tra i più utilizzati e conosciuti”.
Pochi anni dopo, ci si rende conto che per fare un passo definitivo, il linguaggio deve essere sistemato per funzionare bene in tutte le lingue (e in tutti i set di caratteri), avere una sintassi consistente e facile da dedurre e indovinare ed essere facile da potenziare tramite librerie di supporto.
Nel 2008 nasce Python 3.0.
Il tempo ha fatto il resto… la versione 2.x di Python ha fatto la storia, ma ormai deve anche rimanerci… Python 3.x (luglio 2026, versione 3.14) è attualmente il presente di uno dei migliori linguaggi di programmazione della seppur breve storia dell’Informatica.
Se controllate sul sito di riferimento potrete trovare una sezione quotes con le citazioni famose su Python da parte di personaggi famosi delle aziende più in vista e una sezione Success Stories che appunto elenca le maggiori storie di successo ove Python ha contribuito in maniera determinante.
Basta, storia finita!
Linguaggi compilati vs interpretati
Warning
Le cose che spiego in questo capitolo non sono banali per chi non ha (ancora) mai programmato con alcun linguaggio.
Sentitevi liberi di leggere velocemente questa parte, ma ricordatevi di riguardare queste righe almeno una volta all’anno, per meglio apprezzare il livello di comprensione del linguaggio che avete raggiunto!
I linguaggi di programmazione si dividono in:
- linguaggi compilati
- linguaggi interpretati
Vi dico subito che Python è un linguaggio interpretato. Nelle prossime righe cercheremo di capire cosa questo significhi e quale differenza faccia con i linguaggi compilati.
Nei linguaggi compilati:
- si scrive il codice sorgente, un file di testo semplice, nel linguaggio di programmazione scelto.
- si passa il codice ad un compilatore che genera un programma eseguibile (scritto in linguaggio macchina).
- si distribuisce il programma eseguibile e lo si esegue ogni volta che lo si desidera.
Esempi di linguaggi compilati: C, C++, Pascal, Cobol.
Creato l’eseguibile, il compilatore non serve più. Possiamo eseguire il nostro programma semplicemente invocando l’eseguibile.

Nei linguaggi interpretati:
- si scrive il codice sorgente, un file di testo semplice, nel linguaggio di programmazione scelto.
- quando vuoi eseguire il programma, passi il codice sorgente all’interprete: questo lo traduce, istruzione per istruzione in linguaggio macchina e lo esegue direttamente.
Esempi di linguaggi interpretati: Javascript, PHP, Perl, Python.
Ogni volta che si vuole rieseguire il programma, riparte l’interpretazione del codice.

Questa importante caratteristica porta inevitabilmente importanti differenze insite nei linguaggi. Ne elenco alcune:
-
I programmi generati da linguaggi compilati sono generalmente più veloci. Infatti eseguono direttamente codice nativo (pronto per essere eseguito dal compilatore) invece di doverlo interpretare al volo;
-
Quando scrivi codice, i programmi interpretati sono subito pronti per l’interpretazione e il testing, senza dover ad ogni minima modifica ricompilare.
-
I programmi generati da linguaggi compilati utilizzano meno memoria, ma quelli interpretati la usano solitamente meglio, perché è l’interprete che se ne occupa.
-
Per ogni sistema operativo supportato è necessario procedere ad una ricompilazione del codice sorgente. Infatti gli eseguibili generati dalla compilazione dipendono dal sistema operativo ove essi sono compilati;
-
il codice di un programma interpretato va bene così com’è per ogni sistema operativo, ma è necessario avere un interprete adatto per quel sistema operativo per poterlo eseguire.
Python, per massimizzare i punti a favore e ottenere il miglior comportamento possibile, può utilizzare un approccio misto, sbilanciato dalla parte dell’interpretazione:
Note
L’interprete Python, alla prima esecuzione del codice, fa una sorta di compilazione, generando un programma (bytecode, estensione .pyc) non direttamente eseguibile dal dispositivo, ma direttamente eseguibile dalla parte dell’interprete preposta allo scopo: la macchina virtuale.
In questo modo, dalla seconda esecuzione in poi, la velocità della stessa sarà paragonabile (in molti casi identica) a quella dei linguaggi compilati.

Tutto il processo descritto è completamente automatico: il programmatore non deve far altro che scrivere codice e testarne l’esecuzione nell’ambiente adatto.
Installazione
Per lavorare con Python basta avere l’interprete Python con tutte le sue librerie più importanti installato nel proprio sistema, utilizzare il blocco note (o un altro editor di testo semplice) per scrivere il codice e infine richiederne l’esecuzione all’interprete Python tramite riga di comando.
Capite che questa modalità di lavoro, benché funzionante e sufficiente, risulti complicata e inadatta a chi deve concentrarsi sull’imparare il linguaggio piuttosto che far funzionare tutto il sistema solo per eseguire la prova di un semplice programma.
Per risolvere questo problema e favorire lo sviluppo semplice e veloce delle applicazioni (RAD: Rapid Application Development) si è addirittura inventata una nuova categoria di software, denominata IDE (Integrated Development Environment).
Gli IDE sono software che contengono più o meno tutto quanto necessario per sviluppare una applicazione in uno o più linguaggi di programmazione: un editor di testo per la scrittura del codice, il compilatore e/o l’interprete del linguaggio (o dei linguaggi, se l’IDE ne supporta più di uno), gli strumenti per il debug, ovvero per la correzione degli errori del software, e… tutto quello che serve!
Gli IDE più utilizzati per sviluppare in Python sono:
- PyCharm (multipiattaforma e multilinguaggio), molto completo, ma anche molto pesante; sviluppato specificatamente per lo sviluppo in Python.
- Visual Studio Code (multipiattaforma e multilinguaggio), sviluppato da Microsoft per sviluppare su tutti i linguaggi che essa supporta e utilizza (tra cui, ovviamente, Python!)
- XCode (Specifico per MacOS, multilinguaggio) l’IDE di casa Apple, può essere configurato per lavorare con Python.
- IDLE, il più semplice IDE per Python, scritto in Python, multipiattaforma, disponibile nell’installer ufficiale di Python.
- Thonny, (multipiattaforma, specifico per Python), un IDE progettato specificatamente per imparare a programmare in Python. Semplice, leggero, con supporto per MicroPython (Python per piccoli dispositivi).
Non la facciamo lunga: noi utilizzeremo Thonny! E lo faremo per i seguenti motivi:
- Thonny è scritto in Python e disponibile per Windows, MacOS, Linux, Raspberry. Tutti sistemi operativi che potenzialmente useremo.
- Thonny è scritto non da una azienda o da una comunità, ma direttamente dal dipartimento di informatica dell’università di Tartu (Estonia)
- Thonny è stato scritto specificatamente per la didattica con Python.
- Thonny installa una copia di Python, rendendo semplicissima l’installazione dell’ambiente completo per tutti gli utenti.
- Thonny fornisce una interfaccia semplice anche per l’interazione con pip, il gestore di pacchetti Python (ne parleremo in seguito)
Insomma, un IDE a misura degli studenti!
Le istruzioni che seguono spiegano come installare Thonny sui vari sistemi operativi (in rigoroso ordine alfabetico)!
Installare Thonny su Linux
Installare software su Linux è affare allo stesso momento semplicissimo e complicatissimo. Semplicissimo perché con il comando giusto, i sistemi operativi basati su Linux eseguono download, installazione e prima configurazione del software in pochissimo tempo. Complicatissimo perché scegliere il comando giusto richiede una minima conoscenza del sistema che non sempre tutti gli utenti hanno e che varia da sistema a sistema.
In generale, ho schematizzato i comandi da terminale per tutte le distribuzioni più famose. Se pensate di non rientrare in nessuna di queste, contattatemi e vedremo insieme cosa fare:
# Debian, Rasbian, Ubuntu, Mint e altre
$ sudo apt install python3 thonny
# Fedora, Red Hat, Mandriva, OpenSuse e altre
$ sudo dnf install python3 thonny
# Arch Linux, Manjaro, Chakra, Parabola e altre
$ sudo pacman -S python3 thonny
Questo installerà l’ultima versione di Python 3.x e Thonny sul vostro sistema, rendendolo pronto all’uso! Tutto qui! Passate a testare l’installazione con l’Hello World Test in fondo!
Installare Thonny su MacOS
Per installare Thonny su Mac, andate sul sito di Thonny (https://thonny.org/), scaricate il pacchetto di installazione per Mac e installatelo sul vostro sistema. Se avete già installato sul vostro Mac un pacchetto .pkg non avrete alcuna difficoltà!

Terminata l’installazione verificate che tutto funzioni con gli Hello World Test del prossimo capitolo.
Installare Thonny su Windows
Per installare Thonny su Windows, andate sul sito di Thonny (https://thonny.org/), scaricate il pacchetto di installazione per Windows e installatelo sul vostro sistema.

C’è qualcuno che pensa evidentemente che gli utenti di Windows siano tutti tonti: non avete idea di quanti tutorial si trovano su YouTube per installare Thonny. Nel dubbio ne aggiungo qui uno…
Thonny, impostazioni di base
Per accedere al menù delle impostazioni di Thonny, selezionate la voce STRUMENTI —> OPZIONI.
Nella scheda GENERALE vi invio a selezionare la voce evidenziata. Le altre sono a vostro piacimento:

Nella scheda EDITOR ci sono le opzioni più importanti per lavorare bene durante la scrittura del software. Nel laboratorio sono impostate così. Quelle sottolineate sono quelle assolutamente necessarie!!!

Nella scheda RUN && DEBUG c’è solo una opzione interessante per noi: forza il salvataggio automatico dei programmi da eseguire!

Nella scheda ASSISTANT abbiamo disabilitato tutto! Durante l’esecuzione andremo a leggere i suggerimenti direttamente dall’interprete nella shell!

Spero tutto questo sia sufficiente. Altrimenti contattatemi per avere aiuto. Appena avete finito procedete gli Hello World Test del prossimo capitolo.
Prime cose
In questo primo capitolo cercheremo di fare semplicemente i primi passi con il linguaggio di programmazione Python e le prime esperienze con l’IDE Thonny. la cosa più semplice (e intelligente) per capire è provare a fare insieme!
Quando si comincia a studiare un nuovo linguaggio, il primo programma che si scrive di solito è il cosiddetto “Hello, World!”. È un semplice programma che scrive semplicemente “Hello, World!”, ma in realtà serve per fare un numero incredibile di cose:
- familiarizzare con l’IDE, il nuovo ambiente di programmazione
- provare a scrivere un primo programma
- riuscire nella fase di compilazione/interpretazione/esecuzione del codice
- conoscere l’istruzione principale di output
- superare psicologicamente…
Tip
“Hello World Programs”
Gli Hello World sono programmi che scrivono (o mostrano) semplicemente la scritta “Hello, World!”.
Tutti i linguaggi di programmazione hanno il loro HelloWorld… sembra una cavolata, ma cominciare con il semplice senza sottovalutarlo e assicurandosi di avere tutto chiaro (fino a lì) è sicuramente un’ottima strategia di apprendimento.
Noi proveremo a scrivere addirittura due HelloWorld!!! Infatti, essendo Python un linguaggio interpretato, è possibile accedere direttamente all’interprete Python ed eseguire codice riga per riga, oppure creare un modulo, ovvero un file di testo in cui si scrive codice Python e lo si salva con estensione .py.
Cominciamo con l’interprete.
L’interprete Python
Per accedere all’interprete Python, basta aprire Thonny e guardare in basso! Se l’interprete non è aperto, attivarlo dal menù come da immagine.

L’interprete si mostra con il suo tipico prompt fatto con i 3 maggiori >>>. Quando vedete il prompt significa che l’interprete è pronto a ricevere
ed eseguire i vostri comandi. A questo punto basta digitare l’istruzione print("Hello, World!") come nell’immagine seguente:

Tutto qui! Primo livello superato :)
Potete usare il prompt per eseguire una o più righe di codice e vedere che cosa succede, potete usarlo come calcolatrice, potete… farci tutto quello che Python è in grado di fare (tanta roba) in maniera… un pò scomoda (ma adatta a fare prove, a valutare comandi, etc…). Ecco alcuni esempi di utilizzo dell’interprete. Le spiegazioni arriveranno (tutte!!!) successivamente. Per adesso… imparate provando!
>>> print("Ciao")
Ciao
>>> 3 + 5
8
>>> 5 / 5
1.0
>>> type(1.0)
<class 'float'>
>>> type(1)
<class 'int'>
>>> numero = 5 * 2
>>> numero
10
>>> numero = numero / 4
>>> print(numero)
2.5
Moduli Python (file .py)
Praticamente la cosa più difficile che abbiamo visto finora è che Python chiama i suoi file di codice (in programmazione si dicono file sorgente) con il nome di moduli. Per creare un modulo, ovvero un file con estensione .py, basta aprire Thonny, scrivere nell’editor un pò di codice e premere SALVA!
Noi molto semplicemente proveremo di nuovo con un HelloWorld! Scrivete ancora il seguente codice:
print("Hello, World!")
Poi premete F5 per avviare l’esecuzione. Thonny prima di eseguire un modulo, salva su file le modifiche che avete fatto! Se non avete ancora salvato nulla, parte la procedura di salva con nome:

Tutto qui!
Istruzione print
La prima istruzione che abbiamo incontrato è la classica print().
Serve per visualizzare i suoi argomenti!
>>> print("Ciao")
Ciao
>>> print("Ciao","a tutti")
Ciao a tutti
>>> print("Ciao", 'ciao', "ne")
Ciao ciao ne
Non mi sembra molto complicato…
- Potete scrivere quante robe volete
- Ogni cosa va tra apici, singoli o doppi… basta che ci sia coerenza
- Per scrivere più cose basta separarle nella print con una virgola e poi queste verranno scritte con uno spazio di separazione
Commenti al codice
Se abbiamo paura di dimenticarci qualcosa… oppure vogliamo spiegare una o più righe di codice in italiano, possiamo utilizzare i commenti!
# Questo è un commento
# serve a spiegare in italiano alcune cose...
# ad esempio:
# l’istruzione print() visualizza sullo schermo il suo contenuto
print("Hello, World!")
Come vedete, ogni commento inizia con un # e fino a fine riga potete scrivere ciò che volete.
I commenti semplificano il lavoro di rilettura del codice, soprattutto quando le righe di codice si avvicinano pericolosamente alle centinaia. Inoltre, cosa ancora più importante, piacciono al vostro prof! Quindi scriveteceli! Sempre! Spiegate in ogni porzione di codice l’idea che vi frulla in mente e quello che volete fare per realizzarla!
Esercizi
Per ognuno dei seguenti quesiti, creare un modulo (un file .py) per eseguire la richiesta dell’esercizio. Tipicamente i file degli esercizi si chiamano esNUMERO.py, ad esempio es1.py oppure es001.py
e nelle prime righe si trova (come commento) il nome di chi lo ha svolto e una descrizione dell’esercizio. Una cosa tipo:
# Andrea Diamantini
# Esercizio 1
(...codice...)
Esercizio 101
Visualizzare sullo schermo la scritta seguente:
Ciao, mi chiamo Pinco Pallino
Esercizio 102
Visualizzare sullo schermo la scritta seguente (andando a capo quando necessario):
Ciao,
mi chiamo
Pinco Pallino
Esercizio 103
Visualizzare sullo schermo il seguente disegno:
+ + + +
+ +
+ +
+ + + +
Esercizio 104
Visualizzare sullo schermo il seguente disegno:
+
+++
+++++
+++++++
+++++++++
+++++++++++
+
+
Variabili e Operatori
In questo capitolo introdurremo due fra i concetti più importanti della programmazione di base in Python. Attenzione alle definizioni e agli esempi!!!
Per rendere semplice e veloce provare i concetti appresi con un pò di codice, faremo largo uso dell’interprete. Obbligatorio provare il codice che vedete sul vostro computer.
Variabili
Una variabile è una porzione di memoria identificata tramite un identificativo univoco (nome, nell’esempio), che permette di memorizzare un valore di un qualche tipo. Vediamo alcuni semplici esempi:
>>> nome = "Pippo"
>>> print(nome)
Pippo
>>> # A me non piacciono i nomi di variabile con gli accenti o gli apostrofi...
>>> eta = 8
>>> print(eta)
8
Come si vede per definire una variabile, bisogna fare 3 cose:
- scegliere un nome per la variabile (ad esempio: altezza)
- decidere un valore da assegnargli (ad esempio: 143)
- assegnare alla variabile il valore tramite l’operatore =
Scriviamo un esempio:
>>> altezza = 143
I nomi delle variabili sono identificatori univoci e per esserlo devono rispettare le seguenti regole:
- essere composto solo da lettere (a-z, A-Z), numeri (0-9) e underscore ( il carattere _ , ad esempio per
altezza_rettangolo) - non può iniziare con un numero
- non contenere spazi o caratteri speciali (ad esempio
altezza rettangolocon lo spazio, non va bene!) - non essere uguale a nessuna delle parole chiave del linguaggio (ad esempio, non puoi chiamare una variabile
print)
Warning
Python è CASE-SENSITIVE
Significa che Python fa differenza fra MAIUSCOLE e minuscole…
var,Var,VARsono tre nomi diversi
Fin qui è facile.
Occhio adesso però: Python è un linguaggio Strongly Typed,
il che significa che in ogni istante è sempre possibile sapere che tipo di dato c’è in una variabile.
Faccio un esempio banale, che può aiutare a capire:
>>> # "7" è una stringa, una parola
>>> prova = "7"
>>> # Python sa che prova contiene una stringa. Per noi è "7"
>>> print(prova)
7
>>> # 7 senza apici è un valore numerico, un numero
>>> prova = 7
>>> # Python adesso sa che la variabile prova contiene un numero. Per noi è comunque 7
>>> print(prova)
7
Praticamente in Python una variabile esiste solo quando è assegnata di un valore, tramite l’operatore = (di cui parleremo fra un attimo).
Il nome scelto, con le regole descritte sopra, serve solo da etichetta per ritrovare quel valore (che… è la variabile)!!!
Se siete curiosi di sapere anche voi il tipo di dato contenuto in una variabile, potete verificarlo utilizzando la funzione predefinita type().
>>> var = 5
>>> type(var)
<class 'int'>
>>> var = 5.0
>>> type(var)
<class 'float'>
>>> var = "5"
>>> type(var)
<class 'str'>
Operatore di assegnazione
Adesso dico una cosa che fino a qui io ho dato per scontato e che voi non avevate ancora pensato.
In Python l’operatore = non assomiglia neanche un pò all’operatore = della matematica.
Anzi… per chi inizia a programmare, forse è l’operatore più complicato.
In matematica scrivo tranquillamente 5 + 3 = 8 e questa cosa significa che la quantità a destra è uguale alla quantità a sinistra. Infatti
potrei anche scrivere 8 = 5 + 3 e tutto avrebbe ancora perfettamente senso.
In Python qualunque delle espressioni sopra da errore:
>>> 5 + 3 = 8
SyntaxError...
L’operatore = in Python non serve per fare le uguaglianze ma è un operatore di assegnazione, che funziona in un modo molto preciso:
prende la quantità scritta a destra dell’operatore, la valuta (ovvero, tiene conto del valore e del tipo presenti) e la assegna al nome
di variabile che è presente a sinistra.
>>> a = 5 # prendo il valore 5 e lo assegno ad una variabile di nome a
>>> b = a # valuto il valore di a (vale 5) e lo assegno ad una variabile di nome b
>>> b # Ovviamente, b vale...
5
Invece se scrivo…
>>> 5 = c # valuto il valore di c (non so quale sia) e vorrei assegnarlo... (no no no no...)
SyntaxError...
Spero sia tutto chiaro… Prima di procedere oltre, qualche esercizio di prova.
Esercizi semplici
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 121
Definire la variabile nome con valore il proprio nome e la variabile cognome
con valore (indovina…). Visualizzare il contenuto delle variabili (con print) ed il tipo contenuto in esse (con type).
nome = 'Andrea'
print("Nome:", nome, type(nome))
Esercizio 122
Definire le seguenti variabili:
- sesso (una lettera, ‘M’ oppure ‘F’)
- data di nascita (in realtà 3 variabili per 3 numeri: giorno, mese, anno)
- comune di nascita (ad esempio ‘Senigallia’)
- la provincia (2 lettere, ad esempio ‘AN’)
Visualizzare il contenuto delle variabili (con print) ed il tipo contenuto in esse (con type).
Esercizio 123
writeme!
Esercizio 124
plswriteme!
Numeri
Python considera i seguenti tipi numerici:
| Tipo | Oggetto | Descrizione |
|---|---|---|
| Numeri Interi | int | I numeri interi (secondo Python). Il più grande (o più piccolo) intero rappresentabile dipende dalla quantità di memoria presente nella macchina. |
| Numeri Reali | float | I numeri reali (secondo Python), tipicamente definiti a virgola mobile (floating point numbers) Sono rappresentabili numeri da -1.797693134862316 e308 a 1.797693134862316 e308 |
| Valori Booleani | bool | I valori di verità dell’algebra booleana. Utilizzano i valori True oppure False. |
Mi sembra facile da capire:
un numero intero è un qualsiasi numero, positivo o negativo, senza virgola.
Un numero reale è un qualsiasi numero con la virgola, solo che in Python il separatore decimale è il punto!!!.
Quindi:
a = 5 # intero
b = 5.0 # reale
# ... e così via...
I valori booleani sono solo True o False:
c = True
d = False
Gli operatori abbinati a questi tipi di dati sono elencati, con esempi e spiegazione, sotto.
Operatori Aritmetici
Non credo ci sarà molto da spiegare sul funzionamento intrinseco degli operatori seguenti, ma va fatta una importante precisazione! Il risultato di una operazione aritmetica in Python è un valore, dunque… una variabile! E Python vuole sempre sapere il tipo (oltreché il valore) delle sue variabili!!! Quindi, banalmente… quando c’è un float da qualche parte, il risultato sarà comunque float. Occhio agli esempi…
Addizione (Operatore +)
Non credo ci sia molto da spiegare su questo operatore…
>>> 2 + 2
4
>>> a = 3.0
>>> a + 4
7.0
Sottrazione (Operatore -)
Anche qui.. mi sembra facile…
>>> 4 - 5
-1
>>> b = 7.2
>>> b - 1
6.2
Moltiplicazione (Operatore *)
Tutto sommato, anche questo (operatore) dovrebbe essere semplice…
>>> 6 * 3
18
>>> c = -4.5
>>> c * 2
-9.0
Divisione (Operatore /)
E come alle elementari… qui le cose si complicano (un pò)… Fate attenzione all’operatore divisione, perché
in Python il risultato di una divisione è sempre un numero reale, un float.
>>> 7 / 2
3.5
>>> 6 / 2
3.0
Divisione Intera (Operatore //)
Avendo un operatore con risultato reale, ne esiste anche uno con risultato intero. Di regola questo dovrebbe essere utilizzato con due numeri interi. Il risultato di una divisione intera è sempre un intero.
Warning
I risultati di una divisione intera sono sempre interi e vengono sempre calcolati per troncamento, mai per arrotondamento!!!
``` python
>>> 7 // 2
3
>>> 6 // 2
3
>>> 39 // 10
3
```
Modulo (Resto divisione intera, Operatore %)
L’operatore modulo, comune a tutti i linguaggi di programmazione, serve per calcolare il resto della divisione intera fra due numeri (interi, ovviamente).
# 7 diviso 2 fa 3 col resto di 1, quindi...
>>> 7 % 2
1
# 6 diviso 2 fa 3 col resto di 0, quindi...
>>> 6 % 2
0
# 39 diviso 10 fa 3 col resto di 9, quindi...
>>> 39 % 10
9
Elevamento a potenza (Operatore **)
Questo operatore, introdotto in Python per mera comodità, esegue l’elevamento a potenza di due numeri.
>>> 7 ** 2
49
>>> 6 ** 0
1
>>> 2 ** 3
8
La priorità degli operatori aritmetici rispecchia la logica della matematica con moltiplicazione e divisione prima di addizione e
sottrazione. Per i raggruppamenti si usano le parentesi tonde (()):
7 + 2 * 3 # fa 13
( 7 + 2 ) * 3 # fa 27
( 1 + 8 ) ** 1/2 # fa 4.5!!!! 1 + 8 fa 9, 9 alla 1 fa 1, 9 diviso 2 fa 4.5!!!!!
( 1 + 8 ) ** (1/2) # fa 3.0!!!!! Okkio...
Gli esempi, scritti sopra e sotto per semplicità con i numeri interi, funzionano ovviamente con tutti i tipi numerici supportati da Python.
Operatori di confronto
Gli operatori di confronto in Python sono operatori binari il cui risultato è sempre un valore booleano!
| Operatore | Descrizione | Esempio | Risultato |
|---|---|---|---|
== | È uguale a | 5 == 8 | False |
!= | È diverso da | 5 != 8 | True |
> | È maggiore di | 5 > 8 | False |
< | È minore di | 5 < 8 | True |
>= | È maggiore o uguale a | 5 >= 8 | False |
<= | È minore o uguale a | 5 <= 8 | True |
Operatori Logici
Gli operatori logici in Python sono operatori binari il cui risultato è sempre un valore booleano!
| Operatore | Descrizione | Esempio | Risultato |
|---|---|---|---|
and | Congiunzione logica | x = 6 y = 3 x < 10 and y > 1 | True |
or | Disgiunzione logica | x = 6 y = 3 x == 5 or y == 5 | False |
not | Negazione logica | x = 6 y = 3 not (x == y) | True |
Esercizi sui numeri
Alcuni esercizi semplicissimi per assicurarsi di avere ben compreso la logica dei numeri e degli operatori in Python.
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 141
Definire una variabile intera, con un numero a scelta e una variabile reale, anch’essa a piacere. Visualizzare nome, valore e tipo.
Esercizio 142
Definire le variabili n1,n2 contenenti due nuemri interi a piacere. Calcolare:
- n1 + n2
- n1 - n2
- n1 * n2
- n1 / n2
- n1 // n2
- n1 % n2
- n1 ** n2
Verificare il tipo di ognuno dei risultati con la funzione type().
Esercizio 143
Definire le variabili r1,r2 contenenti due numeri reali a piacere. Calcolare:
- r1 + r2
- r1 - r2
- r1 * r2
- r1 / r2
- r1 ** r2
Verificare il tipo di ognuno dei risultati con la funzione type().
Esercizio 144
Definire le variabili p, q contenenti due numeri interi a piacere. Calcolare:
- p < q
- p == q
- p != q
- p >= q
Esercizio 145
Definire le variabili a, b, c contenenti tre numeri reali a piacere. Calcolare:
- a < b and b < c
- a == b or a != c
- not (a != b)
Stringhe
Abbiamo già visto come dichiarare una qualsiasi variabile numerica; per una stringa le cose non sono affatto più difficili:
>>> nome = "Andrea"
>>> print(nome)
Andrea
In Python gli oggetti stringa sono definibili tramite l’uso degli apici. Per “semplificare” le cose, Python non fa grande differenza fra apici singoli e doppi, ma richiede coerenza nell’utilizzo.
>>> primo = "riso"
>>> print(primo)
riso
>>> secondo = 'carne'
>>> print(secondo)
carne
l’utilizzo di un tipo di apice permette di inserire con più semplicità l’altro all’interno della stringa
>>> primo = "penne all'arrabbiata"
>>> print(primo)
penne all'arrabbiata
>>> contorno = 'patate "duchessa"'
>>> print(contorno)
patate "duchessa"
Nel caso si voglia insistere ad utilizzare un solo tipo di apice,
bisogna spiegare a Python quali sono quelli da intendere come semplici
caratteri precedendoli con il carattere di escape backslash ( \ )
>>> dessert = "cornetti con crema \"chantilly\""
>>> print(dessert)
cornetti con crema "chantilly"
>>> frutta = 'pere \'abate\' e prugne secche'
>>> print(frutta)
pere 'abate' e prugne secche
Come si vede Python interpreta correttamente le sequenze con apici e caratteri di escape. Gli stessi possono essere utilizzati anche per scrivere caratteri non stampabili, ma utili tipograficamente:
| Simbolo | Descrizione |
|---|---|
\n | Capo riga |
\t | tabulazione |
Vediamo qualche esempio per chiarezza.
>>> nome = "Andrea\nDiamantini"
>>> print(nome)
Andrea
Diamantini
>>> scuola = "Liceo\tDa Vinci\nJesi\t(AN)"
>>> print(scuola)
Liceo Da Vinci
Jesi (AN)
Spero sia abbastanza chiaro.
Le stringhe supportano l’operatore + per la congiunzione delle stringhe e l’operatore * per la ripetizione delle stesse. Anche qui, un esempio conta più di mille parole.
>>> frutto = "ba" + "na" * 2
>>> print(frutto)
banana
>>> mitraglia = "ra" + "tta" * 10
>>> print(mitraglia) # tutti stecchiti :)
rattattattattattattattattattatta
Altra caratteristica delle stringhe in Python è che esse supportano la “concatenazione automatica per vicinanza”. Provo ancora a spiegare con un esempio:
>>> gioco = "cal" "cio"
>>> print(gioco)
calcio
Se vuoi concatenare una stringa su più righe, devi usare le parentesi tonde
>>> periodoLungo = ( "Quando uno deve parlare e non ha niente da dire"
>>> " dice cose stupide e a volte... le scrive!" )
>>> print(periodoLungo)
Quando uno deve parlare e non ha niente da dire dice cose stupide e a volte... le scrive!
Ok, più o meno ci siamo! Qualche esercizio per assicurarsi che sia tutto chiaro e andiamo avanti :)
Esercizi sulle stringhe
Alcuni semplici esercizi di comprensione sulle stringhe
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 161
Definire due variabili contenenti rispettivamente il nome e il cognome di una persona.
Definire la variabile NomeCompleto formata dalla sequenza ‘sig.’ (o ‘sig.ra’) + uno spazio + il nome + uno spazio + il cognome.
Visualizzare le stringhe nome, cognome e NomeCompleto in righe diverse
Esercizio 162
Definita la stringa
es = 'Esercizio 142'
visualizzare la stringa per 18 volte in 18 righe diverse, una volta per riga (sugg: ricordate che + congiunge, “\n”
va a capo e * intero ripete…)
Esercizio 163
Definire la variabile colore1 con un colore a scelta. Ripetere l’operazione per colore2.
Definire la stringa bicolore data dal primo colore, uno spazio, la &, un altro
spazio e infine il secondo colore.
Visualizzare la stringa così definita.
Esercizio 164: Nome ed età
Definire la variabile nome con il proprio nome e la variabile eta con il numero intero che rappresenta la propria età.
Visualizzare la frase “Mi chiamo NOME ed ho ETA anni” ovviamente sostituendo opportunatamente le parole maiuscole con i valori
delle variabili definite.
f-strings
Arrivati a questo punto del corso, siete già quasi degli espertoni della funzione print() e avrete capito come questa
possa visualizzare qualunque tipo di variabile semplicemente per elencazione. Chiarifico subito con un banale esempio:
nome = "Andrea"
anni = 18 # ;)
print("Io mi chiamo", nome, "ed ho", anni, "anni")
Se eseguite questo brillante codice vedrete scritta la frase “Io mi chiamo Andrea ed ho 18 anni” con la print che visualizza senza problemi una stringa e un intero. Se siete a vostro agio con questo tipo di scrittura… saltate questo capitolo :sunglasses:
In realtà questa modalità di visualizzare informazioni risulta un pò scomoda:
- devi alternare stringhe “fisse” con variabili di tutti i tipi
- devi stare attento agli spazi
- devi ricordarti le virgole
Insomma, bene… ma non benissimo!
Python, nella speranza di fornire uno strumento più semplice, dalla versione 3.6 ha introdotto il concetto di formatted string literals, abbreviato
appunto in f-strings. Qui la documentazione ufficiale.
In poche parole, queste sono stringhe precedute da una f (da cui l’abbreviazione del nome), che permettono di visualizzare variabili semplicemente
indicando il loro nome dentro parentesi graffe {}. Un esempio e sarà chiarissimo:
nome = "Andrea"
anni = 28
print( f"Io mi chiamo {nome} ed ho {anni} anni" )
Adesso ho corretto l’età 😉
Come vedete, la stringa scritta in questo modo risulta più semplicemente leggibile dal programmatore, non ha le mille virgole di prima e, grazie alle parentesi graffe, evidenzia bene quale parte della stessa espone le variabili da visualizzare.
Adesso che le avete viste, sta a voi decidere se utilizzarle oppure no!
Tip
Perchè non provate a togliere la
fdavanti per vedere cosa succede se scrivo solo:
print( "Io mi chiamo {nome} ed ho {anni} anni" )?
formattazione
A dire la verità, grazie alle f-strings in Python è facilissimo anche formattare numeri e stringhe secondo necessità. Provo ad elencare le possibilità più conosciute in una tabella:
| Simbolo | Descrizione |
|---|---|
| `:ns | Lascia almeno n spazi per una stringa |
:+ | Esplicita il segno |
:, | Usa la virgola come separatore delle migliaia |
:b | Formato binario del numero |
:f | Formato con parte intera e decimale (float) |
:X | Formato esadecimale del numero |
:% | Formato percentuale del numero |
:0nd | Intero (d). Riempe n cifre con gli 0 |
E a spiegare con qualche esempio:
>>> nome = "Andrea"
>>> print(f"Ciao, {nome:8s}!")
Ciao, Andrea ! # notate i due spazi vuoti prima del !
>>> print(f"Ciao, {nome:4s}!")
Ciao, Andrea! # almeno 4... ma ne servivano 6...
>>> minima = -3.5
>>> massima = 5
>>> print(f"La temperatura è fra i {minima:+} e i {massima:+} gradi" )
La temperatura è fra i -3.5 e i +5 gradi
>>> num = 26
>>> print(f"{num} in binario è {num:b}")
26 in binario è 11010
>>> print(f"{num} in esadecimale è {num:X}")
26 in esadecimale è 1A
>>> numeroGrande = 1250870
>>> print(f"Numero grande: {numeroGrande:,}")
Numero grande: 1,250,870
>>> value = 0.34
>>> print(f"Con max 2 valori interi e 4 cifre dopo la virgola: {value:2.4f}")
Con max 2 valori interi e 4 cifre dopo la virgola: 0.3400
>>> print(f"Con max 1 valore intero e 1 cifra dopo la virgola: {value:1.1f}")
Con max 1 valori interi e 1 cifra dopo la virgola: 0.3
>>> print(f"percentuale {value:.0%}")
percentuale: 34%
Spero sia chiaro 😉
Input valori
La seconda istruzione Python che andiamo a introdurre si chiama input e permette all’utente, ovvero colui
che esegue il programma, di inserire un valore. Vediamo un esempio:
# la funzione input permette a chi utilizza il programma di inserire informazioni
nome = input("Come ti chiami? ")
print("Ciao", nome)
Quando si esegue questo codice vedremo qualcosa tipo:
Come ti chiami? Andrea
Ciao Andrea
Faccio un’altra prova, giusto per essere sicuro che abbiamo capito:
age = input("Quanti anni hai? ")
print(f"Ho {age} anni")
Mi sembra una istruzione davvero semplice… purtroppo l’errore è dietro l’angolo…
>>> n = input("Numero: ") # inserisci 6
>>> m = input("Numero: ") # inserisci 4
>>> print(n + m) # Tu pensi che scriverà 10, invece...
64
Python non è diventato matto stamattina… la funzione input restituisce sempre e solo stringhe, come possiamo verificare
con la funzione type:
>>> n = input("Numero: ") # inserisci 6
>>> print(n, type(n))
6 <class 'str'>
Capito cosa è successo?! Una concatenazione invece di una somma!
Per evitare questi errori dobbiamo spesso effettuare delle conversioni di tipo; le funzioni necessarie sono semplici e intuitive.
| Funzione | Descrizione |
|---|---|
bool() | Trasforma, se possibile, il suo argomento in un booleano. ( 0, 0.0 e "" valgono False, tutto il resto vale True) |
int() | Trasforma, se possibile, il suo argomento in un intero, altrimenti restituisce ERRORE |
float() | Trasforma, se possibile, il suo argomento in un reale, altrimenti restituisce ERRORE |
str() | Trasforma il suo argomento in una stringa. Funziona sempre 😉 |
Vediamo alcuni esempi per capire il concetto:
# Vuoi un intero? Chiedilo esplicitamente e trasforma il valore digitato
>>> a = int( input("Inserisci un numero intero: ") )
>>> print("Hai inserito", a)
Hai inserito 4
>>> type(a)
<class 'int'>
Certo… se l’utente non digita un numero… la conversione non funziona!!!
L’unica funzione di conversione che funziona sempre è str(): Tutto si può convertire in una stringa :)
Per chi fa più fatica a intuire al volo e vuole sempre conferme scritte aggiungo un semplicissimo esercizio svolto per capire in quali casi potrebbero essere utili le conversioni di tipo.
Esercizio svolto: area del rettangolo
Chiedi all’utente i valori di base e altezza e calcola l’area del rettangolo di quelle dimensioni.
# Area del rettangolo, date base e altezza dall'utente
# in un solo passaggio chiedo le variabili e le converto in float
print("Area rettangolo, date base e altezza")
base = float( input("Base: ") )
altezza = float( input("Altezza: ") )
area = base * altezza
print(f"Area rettangolo = {area}")
Esecuzione del codice
Area rettangolo, date base e altezza
Base: 4.5
Altezza: 2.5
Area rettangolo = 11.25
Esercizi finali
Per ognuno dei seguenti esercizi (e da adesso in poi), creare un file denominato esXXX.py, dove XXX è il numero dell’esercizio. All’interno del file le prime righe devono essere sempre un commento contenente nome, classe ed esercizio. Ad esempio:
# Andrea Diamantini
# 2 XS
# Esercizio 100: merenda
... (poi il codice dell'esercizio) ...
Chiaro?
Al lavoro!!!
Esercizio 181
Definire le seguenti variabili: la stringa nome, l’intero eta, il booleano motorino. Visualizzare il loro contenuto con a fianco il tipo di dato rappresentato.
Esercizio 182
Chiedere all’utente di inserire una stringa per il colore di un tavolino; un intero per la sua altezza in cm; un reale per il suo peso in Kg. Visualizzare tutti i dati inseriti.
Esercizio 183
Permettere all’utente di inserire due valori interi e visualizzarne la somma.
Esercizio 184
Dichiarare due variabili contenente due valori reali, visualizzarne il valore e il prodotto.
Esercizio 185
Per ognuno dei seguenti valori di x e y, visualizzare il risultato della divisione intera con resto fra i due numeri.
Ad esempio, per x = 13 e y = 5, visualizzare:
13 / 5 = 2 con resto di 3
(fatto bene una volta, il copia e incolla è tuo amico…)
x = 12 , y = 7
x = 25 , y = 6
x = 7 , y = 7
x = 13 , y = 13
x = 4 , y = 6
Esercizio 186
Chiedere all’utente di inserire 2 valori reali per la base e l’altezza di un triangolo. Visualizzare i valori inseriti e calcolare l’area del triangolo avente tali misure.
Esercizio 187
Dati tre numeri interi, inseriti dall’utente, visualizzarli come fossero ore, minuti e secondi di un orario (esempio: 5:34:27).
Considerarli dunque come hh:mm:ss di un orario determinato.
Calcolare i secondi trascorsi dalla mezzanotte del giorno prima e visualizzare il risultato a video.
Esercizio 188
Sapendo che in un parcheggio la prima ora costa 2.5 € mentre tutte lo successive costano 2 €, scrivere un programma che richieda il numero complessivo delle ore di parcheggio e visualizzi il totale da pagare.
Esercizio 190 (svolto)
Dati due valori interi A, B, scrivere una espressione che è sempre vera quando A è minore di zero e B è maggiore di 3.
A = int(input("Inserisci A: "))
B = int(input("Inserisci B: "))
expr = (A < 0 and B >= 3)
print(expr) # scriverà True o False a seconda dei valori inseriti
Esercizio 191
Dati due valori interi A, B, scrivere una espressione che è sempre falsa quando A è maggiore di 4 e B è maggiore di 12.
Esercizio 192
Dati due valori interi A, B, scrivere una espressione che è sempre vera quando i segni di A e B sono discordi (cioè uno è positivo, l’altro negativo).
Esercizio 193
Dato un valore intero A, scrivere una espressione che è sempre vera quando A assume un valore pari.
Esercizio 194
Dato un valore intero A, scrivere una espressione che è sempre falsa quando A è multiplo di 5.
Esercizio 195: unità
Chiedere all’utente di inserire un numero intero. Visualizzare poi il numero stesso e la cifra delle unità. Ad esempio se il numero inserito è 243, la cifra da visualizzare è 3. Se il numero inserito è 85, la cifra da visualizzare è 5. Se il numero inserito è 1357, la cifra da visualizzare è 7.
Esercizio 196: centinaia
Chiedere all’utente di inserire un numero intero. Visualizzare poi il numero stesso e la cifra delle centinaia. Ad esempio se il numero inserito è 243, la cifra da visualizzare è 2. Se il numero inserito è 85, la cifra da visualizzare è 0. Se il numero inserito è 1357, la cifra da visualizzare è 3.
Tip
Se sapete verificare se un numero è multiplo di un altro, sapete trovare le cifre di un numero, verificando se è multiplo di 10, 100, 1000…)
Esercizio 197: decimi
Chiedere all’utente di inserire un numero reale. Visualizzare poi il numero stesso e la cifra dei decimi.
Ad esempio se il numero inserito è 243.57, la cifra da visualizzare è 5.
Se il numero inserito è 85.12, la cifra da visualizzare è 1.
Se il numero inserito è 1357, la cifra da visualizzare è 0.
Strutture di alternanza
Il lavoro di creazione dell’algoritmo risolutivo di un problema si può descrivere, in buona approssimazione, con la stesura di una serie di operazioni da compiere per ottenere il risultato prefissato.
La programmazione strutturata schematizza sistematicamente le operazioni da svolgere secondo alcune operazioni atomiche ricorrenti:
- sequenza, in cui le istruzioni sono eseguite una dopo l’altra
- alternanza, in cui alcune istruzioni sono eseguite alternativamente (o un gruppo o un altro)
- ripetizione, in cui alcune istruzioni sono ripetute un numero finito di volte.
Accanto a queste semplici strutture possono esserne inserite altre più generali o più particolari, per schematizzare ogni algoritmo secondo i criteri che più si adattano ai propri gusti. Queste tre sono state scelte perché vale il seguente:
Teorema di Jacopini-Bohm (1966)
“Qualsiasi algoritmo può essere riscritto in maniera equivalente utilizzando solo le strutture di sequenza, alternanza, ripetizione.”
Questo algoritmo cioè sostanzialmente afferma che le tre strutture indicate sono i mattoni fondamentali per riprodurre qualsiasi algoritmo formulabile dall’uomo. In altre parole, tutti i problemi che sono risolvibile, hanno anche una soluzione costruita semplicemente con istruzioni in sequenza, alternanza e ripetizione.
Istruzione if
if è la più semplice istruzione per realizzare la struttura di alternanza. Si pone una condizione e a seconda che essa risulti vera oppure no si eseguono alternativamente due blocchi di istruzione.
Vediamo la definizione di codice:
if condizione:
# blocco di codice da eseguire se condizione è vera
# indentato di 4 caratteri
# poi si va a istruzione successiva
[elif condizione2:
# blocco di codice da eseguire se condizione2 è vera
# indentato di 4 caratteri]
# poi si va a istruzione successiva
[elif condizione3:
# blocco di codice da eseguire se condizione3 è vera
# indentato di 4 caratteri]
# poi si va a istruzione successiva
[else:
# blocco di codice da eseguire se TUTTE LE CONDIZIONI PRECEDENTI sono false
# indentato di 4 caratteri]
# ISTRUZIONE SUCCESSIVA
Le parentesi quadre sopra indicano parti opzionali della struttura di alternanza. Spero si capisca meglio con qualche esempio:
x = int (input("Inserisci un numero intero: "))
if x > 0 :
print("POSITIVO")
elif x == 0:
print("ZERO")
else:
print("NEGATIVO")
print("FINITO")
Inserisci un numero intero: 7
POSITIVO
FINITO
Dal punto di vista funzionale, il codice scorre le istruzioni sopra scritte come nel diagramma seguente:

Le parti con le istruzioni elif ed else sono opzionali,
ovvero si può costruire un if-else semplice, come questo:
y = int (input("Inserisci un altro numero intero: "))
if y % 2 == 0 :
print("Hai inserito un numero pari")
else:
print("Hai inserito un numero dispari")
Inserisci un altro numero intero: 8
Hai inserito un numero pari
oppure lasciare proprio l’istruzione if da sola:
z = int (input("Inserisci un ultimo numero intero: "))
if z != 9 :
print("Pensavo inserissi 9 :(")
Inserisci un ultimo numero intero: -5
Pensavo inserissi 9 :(
Capito??
Provate tutti questi codici e poi giù con gli esercizi…
Indentazione in Python
Note
Indentazione
Indentare significa inserire una certa quantità di spazi vuoti all’inizio di una o più righe per incolonnare opportunamente il codice sorgente.
I linguaggi più famosi, come C o Java, utilizzano le parentesi graffe per indicare un blocco di codice e opzionalmente suggeriscono di indentare il codice per rendere i blocchi più evidenti:
if (numero > 0)
{
// fai qualcosa se numero è maggiore di ZERO
}
else
{
// fai qualcosa se numero NON è maggiore di ZERO
}
Come si vede, lo spostamento a destra di alcune righe del codice le rende più evidenti. Come dicevamo comunque, per C o Java l’indentazione è assolutamente opzionale e il seguente pezzo di codice è assolutamente equivalente al precedente.
if (numero > 0) { // fai qualcosa se numero è maggiore di ZERO
} else { // fai qualcosa se numero NON è maggiore di ZERO
}
Io penso che sia alquanto evidente come il primo esempio sia molto più facile da leggere, da comprendere e imparare rispetto al secondo, anche se per il linguaggio C essi rappresentano lo stesso identico concetto.
Altri linguaggi, come il PASCAL, non utilizzano le parentesi ma parole chiave del linguaggio. Anche qui l’indentazione è opzionale.
if (numero > 0) then ( fai qualcosa se numero è maggiore di ZERO )
else
( fai qualcosa se numero NON è maggiore di ZERO ) ;
Anche qui, pur avendo le parole chiave if ... then ... else ... che racchiudono
i due blocchi, a mio parere senza indentazione la leggibilità e la
chiarezza del codice vengono molto meno.
Python obbliga i suoi utilizzatori ad usare l’indentazione come unica forma di organizzazione del codice in blocchi!
In questo modo le parole del linguaggio da imparare sono pochissime e la struttura del linguaggio è molto facile da leggere.
A questo aggiungiamo che lo stile predefinito di Python per l’indentazione è di inserire 4 spazi ogni volta si voglia identificare un blocco di codice. In questo modo tutti i programmi Python sono per definizione
- i più semplici da leggere
- con una struttura logica identica alla indentazione delle righe di codice che la implementano
- con una organizzazione uniforme in TUTTI i programmi Python.
Vediamo il solito esempio scritto in Python:
if numero > 0 :
# i blocchi Python iniziano con 4 spazi
# Qui si fa qualcosa se numero è maggiore di ZERO
else:
# altro blocco di codice
# qui si fa qualcosa se numero NON è maggiore di ZERO
print("qui i blocchi sono finiti")
Tutto qua!
D’ora in avanti quando incontreremo strutture con blocchi di codice li evidenzieremo tramite indentazione di 4 spazi. Come vedremo alcuni editor di testo ci aiutano molto per questa operazione :)
Warning
Roba da precisoni!!!
Python 3.x utilizza una specifica chiamata PEP8 che descrive per filo e per segno come scrivere qualunque struttura di codice Python.
Questo con l’obiettivo dichiarato di uniformare lo stile di qualsiasi pezzo di codice Python scritto da chiunque e semplificare così la lettura del codice!
In particolare vorrei sottolineare come questa specifica sia assolutamente allergica al carattere di tabulazione tab, presente in alto a destra in ogni tastiera. Il carattere di tabulazione NON deve essere usato negli script Python.
Tip
Se state utilizzando Thonny (come dovreste) potete utilizzare tranquillamente il tasto tab della tastiera perché esso trasforma automaticamente ogni tab in una sequenza di 4 caratteri evitando fastidi durante l’esecuzione del codice e semplificando il lavoro del programmatore che non deve digitare a mano 4, 8, 12,,, spazi!!
Per forzare gli utenti ad usare queste buone regole di uniformità, l’interprete Python segnala un errore ogni volta che trova un carattere di tabulazione nel codice. E il prof mette una insufficienza. Fate voi…
Esercizi
Caution
NO IA (almeno fino a 213)
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 201
Scrivere un programma che richiede un numero intero all’utente e visualizza la scritta “PARI” oppure “DISPARI” a seconda del valore inserito.
Esercizio 202
Scrivere un programma che richiede un numero intero all’utente e visualizza la scritta “DIVISIBILE PER 5” oppure “NON DIVISIBILE PER 5” a seconda del valore inserito.
Esercizio 203
Dati due numeri interi da parte dell’utente, da memorizzare nelle variabili n1, n2, verificare se il primo è un multiplo del secondo. Visualizzare la scritta “MULTIPLO” o “NON MULTIPLO”.
Esercizio 204
Dato un numero intero, inserito dall’utente, restituire la scritta CORRETTO se il numero è pari e compreso fra 100 e 200 (estremi esclusi), NON CORRETTO altrimenti.
Esercizio 205
Dato un numero intero, inserito dall’utente, restituire la scritta CORRETTO se il numero è dispari e compreso fra -5 e 5 (estremi inclusi), NON CORRETTO altrimenti.
Esercizio 206: confronti
Dati due numeri reali da parte dell’utente, visualizzarli in ordine crescente (prima il più piccolo, poi il più grande).
Esercizio 207
Dati due numeri reali da parte dell’utente, memorizzati nelle variabili n1 e n2, inserire il valore più piccolo nella variabile “piccolo” e il più grande nella variabile “grande”, infine visualizzarli.
Esercizio 208
Dati tre numeri reali da parte dell’utente, visualizzarli in ordine crescente (prima il più piccolo, poi il medio, poi il più grande).
Esercizio 209
Dati tre numeri reali da parte dell’utente, memorizzati nelle variabili n1, n2, n3; inserire il valore più piccolo nella variabile “v1”, il medio nella variabile “v2” e il più grande nella variabile “v3”.
Esercizio 210
Dati quattro numeri reali da parte dell’utente, memorizzati nelle variabili n1, n2, n3, n4; visualizzare il valore più grande fra i 4.
Esercizio 211
Dati quattro numeri reali da parte dell’utente, memorizzati nelle variabili n1, n2, n3, n4; visualizzare il valore più piccolo fra i 4.
Esercizio 212: metà e doppio di un intero
Programma che, dato un numero intero, ne visualizza la metà se il numero è pari o ne visualizza il doppio se il numero è dispari.
Esercizio 213: radice quadrata
Programma che dato un numero intero, dice se questo è positivo oppure no. Se il numero è positivo, ne calcola la radice quadrata. Se il numero è negativo ne calcola l’opposto.
Esercizio 214: anno bisestile
Dato un numero intero da parte dell’utente, visualizzare se questo rappresenta un anno bisestile oppure no. Un anno è bisestile se è divisibile per 4, ma non per 100 oppure se è divisibile per 400.
Ad esempio il 1900 non è stato un anno bisestile, perché 1900 è divisibile per 4 ma anche per 100. L’anno 2000 è stato bisestile perché 2000 è divisibile per 400.
Esercizio 215
Un negozio applica degli sconti diversi a seconda del prezzo dei prodotti. Sul prezzo di un prodotto viene praticato lo sconto del 3% se costa fino a 100 euro e del 5% se costa di più. Inserito da tastiera il prezzo P, calcolare il prezzo da pagare secondo la regola sopra descritta.
Esercizio 216: biglietto del treno
Sul prezzo di un biglietto di un treno viene applicato un supplemento del 7% se il treno è di tipo ‘A’, del 12% se è di tipo ‘B’ e del 18% se è di tipo ‘C’. Per gli altri treni non c’è supplemento.
Chiedere all’utente di inserire un numero reale che rappresenta il costo del biglietto e un carattere che rappresenta il tipo di treno, quindi calcolare il prezzo reale del biglietto, applicando l’eventuale sovrapprezzo secondo le indicazioni precedenti.
Infine visualizzare il prezzo iniziale (inserito dall’utente) il tipo di treno indicato e il prezzo finale.
Esercizio 217: temperature
Si permetta all’utente di inserire un numero reale che rappresenterà una temperatura in gradi Celsius. Il programma visualizzerà la temperatura inserita e poi la convertirà in gradi Fahrenheit e in gradi Kelvin. Prima della conversione, il programma verificherà se la temperatura inserita è minore allo zero assoluto (ZERO gradi Kelvin) e in quel caso visualizzerà un errore senza visualizzare le conversioni.
Tip
Per convertire le temperature, usa le seguenti formule:
Fahrenheit = (9/5) * Celsius + 32 Kelvin = Celsius + 273.15
Esercizio 218: triangolo o no
Dati 3 numeri A, B, C da parte dell’utente, che rappresentano le lunghezze di 3 lati, valutare se essi possono rappresentare un triangolo. In caso affermativo dire quale tipo di triangolo: equilatero, isoscele, rettangolo, scaleno.
Esercizio 219: data valida
Chiedere all’utente di inserire 2 numeri interi GG, MM e si determini se questi possono rappresentare i giorni e i mesi di una data. In particolare, se questo è vero si riporti la scritta DATA VALIDA, altrimenti la scritta DATA NON VALIDA.
Ad esempio,
- dati i valori GG = 25, MM = 7 si ritorna DATA VALIDA.
- dati i valori GG = 5, MM = 17 si ritorna DATA NON VALIDA.
- dati i valori GG = 31, MM = 3 si ritorna DATA VALIDA.
- dati i valori GG = 31, MM = 4 si ritorna DATA NON VALIDA.
Il 29 febbraio non è una data valida, non essendo presente il controllo sull’anno.
Tip
Filastrocca sui mesi
Trenta dì conta Novembre, con April, Giugno e Settembre. Di ventotto ce n’è uno… tutti gli altri ne han trentuno!!!
Esercizio 220: orario
Chiedere all’utente di inserire due numeri interi per le ore e i minuti di un orario e visualizzare la scritta ORARIO ERRATO se esso è una combinazione impossibile (ad esempio se i valori sono negativi, oppure se le ore superano 23, etc…).
Se l’orario è corretto, visualizzare una scritta secondo il seguente schema:
- Dalle 8.00 alle 8.59: visualizzare PRIMA ORA
- Dalle 9.00 alle 9.59: visualizzare SECONDA ORA
- Dalle 10.00 alle 10.54: visualizzare TERZA ORA
- Dalle 10.55 alle 11.04: visualizzare INTERVALLO
- Dalle 11.05 alle 11.59: visualizzare QUARTA ORA
- Dalle 12.00 alle 13.00: visualizzare QUINTA ORA
In tutti gli altri casi, visualizzare la scritta TEMPO LIBERO.
Strutture di ripetizione
In Python esistono due tipi di strutture di ripetizione, ovvero per ripetere un certo numero di volte una istruzione (fare un ciclo):
- Istruzione for, che si utilizza per scorrere gli elementi di una sequenza
- Istruzione while, che si utilizza per ripetere un blocco di istruzioni finché non si verifica una determinata condizione.
Queste istruzioni possono essere usate alla bisogna in un programma Python e rappresentano due possibilità di implementazione per la struttura iterativa. Vediamole nel dettaglio.
Istruzione for
L’istruzione for permette di scorrere in maniera semplice gli elementi di una sequenza ed effettuare una qualsivoglia operazione che si ripete con oggetto l’ennesimo elemento di essa. La struttura di base è:
for variabile in sequenza:
# blocco da ripetere, indentato di 4 caratteri
# . . .
# finita l'indentazione, termina il blocco
In questi primi esempi proveremo a lavorare solo con sequenze numeriche,
autogenerate con il comando range. Vediamo un semplice esempio:
for w in range(4):
print(w)
0
1
2
3
Penso si capisca in maniera abbastanza intuitiva come funziona range:
parte da ZERO e va avanti di UNO fino al numero immediatamente prima a
quello indicato.
Tutto quello che viene indentato (spostato a destra) di 4 caratteri fa
parte del for. Quando si ritorna incolonnati alla parola for si
passa alla prossima istruzione. Ad esempio:
for n in range(6):
if n % 2 == 0:
print (n, "è pari")
else:
print (n, "è dispari")
print("Finito!")
0 è pari
1 è dispari
2 è pari
3 è dispari
4 è pari
5 è dispari
Finito!
Notate l’indentazione dell’esempio in cui tutto il blocco if è
ripetuto nel for e le due print fanno parte ognuna di una delle
due parti dell’if. Il print finale viene eseguito al termine del
ciclo.
La funzione range utilizzata in questo modo può essere utilizzata anche semplicemente per contare le ripetizioni.
Il nome della variabile che si utilizza nel for è assolutamente libero e casuale: potete scegliere qualsiasi nome ma evitate di sceglierne uno già utilizzato nel vostro programma, per evitare confusione!!!
for x in range(5):
print("ciao")
for i in range(10):
print( "x" * i )
Capiterà spesso di voler gestire una sequenza di numeri un po’ più complessa: ad esempio una sequenza che non inizia da zero, una sequenza che avanza di 2, un conto alla rovescia…
Per tutte le casistiche possibili occorre studiare i parametri della
funzione range().
Tip
La funzione
range()è una funzione predefinita del linguaggio che crea “al volo” una sequenza numerica intera.
Essa può prendere 1,2,3 parametri! Vediamo i vari casi:
range( stop )
- la sequenza parte da 0 e avanza di 1:
- stop è il valore finale (non compreso)
Ad esempio:
range(5) # indica la sequenza (0,1,2,3,4)
range( start , stop )
- start è il valore iniziale (compreso)
- stop è il valore finale (non compreso)
- si avanza di 1
Ad esempio:
range(2,7) # indica la sequenza (2,3,4,5,6)
range( start , stop , step)
- start è il valore iniziale (compreso)
- stop è il valore finale (non compreso)
- step è il valore di avanzamento
Ad esempio:
range(3,15,2) # indica la sequenza (3,5,7,9,11,13)
È più facile di quello che sembra!
Vediamo qualche esempio:
# c'è un numero solo, è stop!
# Start è predefinito a 0, step è predefinito a 1, quindi...
for i in range(5):
print(i)
0
1
2
3
4
# due numeri, il primo è start, il secondo è stop
# step è predefinito a +1
for i in range(1, 5):
print(i)
1
2
3
4
# si parte da 1, si avanza di 2 fino a 7 (non compreso)!!!
for i in range(1, 7, 2):
print(i)
1
3
5
# si parte da 9, si "avanza" di -2 (quindi si va indietro)
# ci si ferma prima di arrivare a 0
for i in range(9, 0, -2):
print(i)
9
7
5
3
1
Altro esempio, con un po’ di codice:
for n in range(1,4):
q = n**2
print(q, "è il quadrato di", n)
1 è il quadrato di 1
4 è il quadrato di 2
9 è il quadrato di 3
Insomma, secondo me più o meno ci siamo. Dopo una trentina di esercizi, ne sarete convinti anche voi :)
Esercizi su for e range
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 301
Visualizzare i numeri da 0 a 19
Esercizio 302
Visualizzare i numeri da 1 a 20
Esercizio 303
Visualizzare i numeri dispari presenti fra 1 e 20
Esercizio 304
Visualizzare i numeri pari presenti fra 1 e 30
Esercizio 305
Visualizzare un countdown che parte da 10 e arriva a 0.
Esercizio 306
Visualizzare un countdown dei numeri pari che parte da 20 e arriva a 0.
Esercizio 307
Chiedere all’utente di inserire un numero intero positivo e visualizzare un countdown che parte da quel numero fino a 0.
Esercizio 308
Visualizzare la tabellina del 5.
Esercizio 309
Chiedere all’utente un numero intero e visualizzare la tabellina di quel numero.
Esercizio 310
Visualizzare la tabellina del 7 alla rovescia (cioè partendo da 70 e andando indietro fino a 7)
Esercizio 311
Chiedere all’utente un numero intero e visualizzare la tabellina di quel numero alla rovescia.
Esercizio 312
Visualizzare i primi 20 numeri dispari
Esercizio 313
Chiedere all’utente un numero intero e visualizzare i 10 numeri pari successivi a quel numero. Ad esempio, inserito 17, si dovranno visualizzare 18, 20, 22, 24, 26, 28, 30, 32, 34, 36.
Esercizio 314
Visualizzare in colonna i numeri da 1 a 100 (i primi 100 numeri).
Somma e Conta
Capita spesso (vedrete… capiterà!!!) di dover sommare una sequenza di numeri oppure di dover contare una sequenza di informazioni in genere.
In questo caso il ciclo for appena studiato torna veramente utile. Si tratta semplicemente di mettere insieme alcune conoscenze che già abbiamo. Vediamo dunque i 2 casi (la somma e la conta) tramite l’ausilio di 2 esempi
Esempio con la somma
In questo esempio voglio fare la somma dei numeri che vanno da 5 a 12, quindi sommare insieme 5, 6, 7, 8, 9, 10, 11, 12 e sapere quanto fa (senza fare a mente!).
Se vuoi sommare delle quantità vorrai inserire il risultato in una variabile. Mi sembra abbastanza ragionevole affermare che la variabile che contiene la somma deve partire da ZERO!
somma = 0
La seconda considerazione da fare è sul ciclo for: se voglio sommare i numeri fra 5 e 12 devo scrivere un ciclo che parte da 5 (start, compreso) e avere come stop 13, in modo che si fermi a quello prima, ovvero 12.
for s in range(5,13):
# s va da 5 a 12:
# la prima volta vale 5, la seconda 6, etc... l'ultima volta 12
Terza e ultima considerazione: se voglio sommare tutti questi numeri devo “aggiungerli tutti” alla (variabile) somma. Man mano che il ciclo for li scorre li aggiungo dunque ad essa:
...
somma = somma + s # si può scrivere in maniera compatta: somma += s
Questa istruzione aggiunge s al valore attuale di somma e assegna il nuovo valore calcolato di nuovo alla variabile somma.
Mettendo insieme i 3 pezzi, che spero abbiate compreso, otteniamo:
somma = 0
for s in range(5, 13):
somma += s
print("la somma dei numeri fra 5 e 12 fa: ", somma)
Spero sia tutto chiaro! Come al solito comunque, fra un attimo arrivano gli esercizi ;)
Esempio con la conta
In questo secondo esempio voglio contare i numeri dispari fra 15 e 30 ovvero sapere quanti sono i numeri 15, 17, 19, 21, 23, 25, 27, 29 (senza contarli a mano!).
Anche qui ci servità una variabile per “contare” i numeri, che ovviamente deve partire da ZERO!!
conta = 0
Per scorrere i dispari fra 15 e 30 devo scrivere un for molto semplice: parte da 15, arriva a 30 e va avanti di 2!
for c in range(15, 30, 2):
# In questo modo c varrà 15, 17, 19... fino a 29, il numero prima di 30!
Ultima cosa, ogni volta che scorro un numero devo contarlo, cioè aumentare la conta di 1, quindi:
conta += 1
Mettiamo insieme ancora una volta i tre pezzi del ragionamento e otteniamo:
conta = 0
for c in range(15, 30, 2):
conta += 1
print("I numeri dispari fra 15 e 30 sono:", conta)
Sinceramente credo che analizzando un pochino gli esempi e ragionando un po’ anche questo concetto sia abbastanza chiaro! Ripeto… in questo paragrafo niente di nuovo. Solo due tecniche molto comuni che abbiamo analizzato insieme.
E adesso sotto con gli esercizi!
Esercizi su somma e conta
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 321
Visualizzare la somma dei numeri da 1 a 100
Esercizio 322
Contare i numeri che vanno da 10 a 20. (dovrebbe fare 11…)
Esercizio 323
Dato un numero intero chiamato PAOLO, inserito dall’utente, visualizzare la somma dei numeri da 1 a PAOLO (compreso)
Esercizio 324
Fare la somma di 10 numeri inseriti dall’utente.
Esercizio 325
Dati 2 numeri interi A e B inseriti dall’utente, contare i numeri pari compresi fra questi, estremi inclusi.
Esercizio 326
Permettere all’utente di inserire 6 numeri interi. Alla fine dell’inserimento dire quanti numeri dispari positivi sono stati digitati.
Esercizio 327
Scrivere un ciclo che permetta all’utente di inserire 10 numeri interi. Alla fine dell’inserimento visualizzare:
- la quantità di numeri pari inseriti
- la somma dei numeri dispari inseriti
Ad esempio, se l’utente inserisce in sequenza i numeri 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 allora il programma dovrà rispondere:
I numeri pari sono 5.
La somma dei numeri dispari fa 25.
(infatti i numeri pari sono 2, 4, 6, 8, 10 ovvero 5 numeri. La somma dei dispari inseriti è 1 + 3 + 5 + 7 + 9 = 25)
Esercizio 328
Chiedere all’utente di inserire due numeri interi da memorizzare nelle variabili tot e valore.
Permettere all’utente di inserire tot numeri interi, contare quanti di questi sono più grandi di valore e sommare tutti quelli positivi ma minori o uguale a valore.
Visualizzare i risultati ottenuti.
Istruzione while
L’istruzione while si abbina ad una condizione e ripete il suo blocco di codice finché questa condizione rimane vera. Vediamo la struttura.
while condizione:
# blocco di codice da ripetere se la condizione è vera
# . . .
# altro codice
Vediamo un esempio per provare a chiarire il concetto:
a = 0
while a < 5:
print(a)
a = a + 2
0
2
4
Le istruzioni for e while permettono di implementare qualsiasi tipo di iterazione in maniera semplice e lineare. Prendiamo prima un po’ confidenza con queste e poi vediamo casistiche leggermente più articolate, in cui introdurre alcune istruzioni per modificare il comportamento dei cicli.
Esercizi su while
Warning
Attenzione alle IA
Capisco la tentazione di aiutarsi nella risoluzione tramite le IA. Ricordate però che sarete valutati sulla comprensione di quanto avete consegnato. Come hai svolto un certo esercizio e perché lo hai svolto in quel modo…
Esercizio 341
Chiedere all’utente di inserire un numero intero fra 1 e 3. Far ripetere l’inserimento finché non si è ottenuto il numero nell’intervallo richiesto
Esercizio 342
Avendo un capitale iniziale C = 100 euro e sapendo che donandolo al professore questi è in grado di farlo fruttare con un incremento del 15% annuo, dopo quanti anni esso ha raggiunto o superato il doppio del capitale iniziale?
Esercizio 343
Chiedere all’utente di inserire numeri interi finché la somma dei numeri inseriti non superi 20.
Esercizio 344
Chiedere all’utente di inserire numeri interi finché la somma dei pari inseriti non superi 50.
Esercizio 345
Chiedere all’utente di inserire numeri positivi e crescenti. L’inserimento si interrompe quando non si inserisce più un numero maggiore del precedente.
Esercizio 346
Chiedere all’utente di inserire una serie di numeri e terminare l’inserimento quando l’utente digita lo zero. Visualizzare la somma dei numeri positivi inseriti.
Esercizio 347
Chiedere all’utente di inserire una serie di numeri e terminare l’inserimento quando l’utente ne digita due uguali consecutivi. Visualizzare la quantità di numeri inseriti (l’ultimo doppio conta 1: ad esempio inseriti 1 , 3 , 4 , 4 saranno stati inseriti 3 numeri).
Esercizio 348
La malattia più contagiosa che sia mai esistita (finora) è il morbillo. Una persona con il morbillo in un’ora può contagiare fino a 18 persone.
Chiedere all’utente di inserire un numero intero che rappresenta la quantità di persone di una popolazione, fingere che una persona fra queste sia ammalata di morbillo e valutare dopo quanti giorni si supera il 50% di diffusione della malattia (nel caso più grave, ovvero che ogni ora si contagino 18 persone).
Esercizio 349
Al bar della scuola la pizza costa 1 euro, i panini 1,50 euro e le brioches salate ripiene 1,80 euro. Mario prende tot euro alla settimana (non superiore a 10 euro) e ogni giorno della settimana decide cosa prendere, scalando i soldi dai suoi possedimenti. Verificare se alla fine della settimana riesce comunque a prendere quello che gli va o è costretto a fare economia.
Al termine della settimana visualizzare il credito residuo di Mario e se per almeno un giorno è dovuto vivere in ristrettezze.
Esercizio 350
Decidere il nome di un pezzo e un prezzo iniziale (ad esempio: “orologio antico”, 50 euro) e procedere ad un’asta simulata in cui 2 persone (Anna e Marco) si sfidano alzando ogni volta il prezzo dell’oggetto. L’asta si interrompe quando uno dei due inserisce un prezzo minore o uguale al precedente.
Al termine dell’asta dire chi dei due ha acquistato il pezzo e a quale prezzo.
Esercizio 351
Si supponga che l’andamento della popolazione di un’alga si sviluppi nel seguente modo: un anno raddoppia l’anno successivo cala di un quarto (sempre numeri interi, mi raccomando).
Creare un programma che dato un valore iniziale della popolazione (maggiore di 1) e un valore da raggiungere (maggiore del valore iniziale) dica quanti anni ci mette quella popolazione a raggiungere o superare quel valore.
Istruzioni per i cicli
Per ognuna delle istruzioni seguenti, vedremo una semplice descrizione ed un esempio esplicativo prima di “tuffarci” nella moltitudine di esercizi che fortificheranno la nostra consapevolezza nell’utilizzo delle stesse. Un unico commento per chiarire la questione: ognuna di queste istruzioni funziona indifferentemente con i cicli for o i cicli while.
Provare per credere!
Istruzione break
L’istruzione break provoca un’interruzione del ciclo e un salto
immediato alla prima istruzione seguente. Esempio illuminante
indovina = 8
print ("Ho pensato un numero fra 1 e 10. Prova a indovinarlo")
while True:
tentativo = int(input("Che numero è?"))
if tentativo == indovina:
print("Incredibile! Hai indovinato!!!")
break # finito! Esci dal ciclo!
else:
print("No, mi spiace! Riprova...")
print("\nok, adesso andiamo avanti")
Ho pensato un numero fra 1 e 10. Prova a indovinarlo
Che numero è? 6
No, mi spiace! Riprova...
Che numero è? 7
No, mi spiace! Riprova...
Che numero è? 8
Incredibile! Hai indovinato!!!
ok, adesso andiamo avanti
Mi sembra sia semplice da capire e facile da usare. Andiamo avanti.
Istruzione continue
L’istruzione continue interrompe l’esecuzione del blocco di codice da ripetere e salta immediatamente alla prossima iterazione, ricominciando il blocco iterativo.
for n in range(1, 8):
if n % 3 == 0:
continue # ricomincia il ciclo dalla prossima iterazione
print ("Numero: ", n)
Numero: 1
Numero: 2
Numero: 4
Numero: 5
Numero: 7
Anche qui, facile da capire e da utilizzare. Magari è una istruzione che capiterà di usare poche volte, ma di sicuro non sarà un grande problema utilizzarla.
Istruzione else nei cicli
Una delle caratteristiche peculiari di Python, che lo differenziano dagli altri linguaggi, è questa cosa di poter utilizzare la clausola else nei cicli. Il codice nel campo else viene eseguito in due casi specifici:
- Abbinato ad un ciclo for, quando la sequenza da scorrere termina.
- Abbinato ad un ciclo while, quando la condizione diventa False
Vediamo un esempio con il ciclo for:
numero = int( input("Inserisci numero. Io ti dico se è primo o no: ") )
for divisore in range(2, numero):
if numero % divisore == 0:
print (numero,"non è primo, infatti")
print (numero, " = ", divisore, " * ", numero//divisore )
break
else: # Indentazione! else è incolonnato al for NON all'if!!!
print(numero, "è primo")
Inserisci numero. Io ti dico se è primo o no: 15
15 non è primo, infatti
15 = 3 * 5
Inserisci numero. Io ti dico se è primo o no: 17
17 è primo
Praticamente il secondo ciclo for, basato sulla variabile i, cerca eventuali divisori del numero. Se scorrendo tutti i numeri fra 2 e (numero - 1) non ne trova, allora è un numero primo! Se invece trova anche un solo divisore, lo visualizza e interrompe il ciclo con il break, rendendo nullo l’else.
Ecco un esempio analogo che utilizza la clausola else abbinata all’istruzione while.
valoreCarte = 0.0
while valoreCarte <= 7.5:
# controllo quanto ho e decido se andare avanti o no
print("In mano hai", valoreCarte)
risp = input("Vuoi una carta? (s/n)")
if risp != "s":
break
# aggiungo la nuova carta
x = float( input("Dammi una carta: ") )
valoreCarte += x
else: # Indentazione! else è incolonnato a while NON all'if!!!
print("Hai sballato, accidenti!!!")
print("In mano hai", valoreCarte)
In mano hai: 0.0
Vuoi una carta? (s/n) s
Dammi una carta: 5
In mano hai: 5.0
Vuoi una carta? (s/n) s
Dammi una carta: 3
Hai sballato, accidenti!!!
In mano hai 8.0
In mano hai: 0.0
Vuoi una carta? (s/n) s
Dammi una carta: 6
In mano hai: 6.0
Vuoi una carta? (s/n) n
In mano hai 6.0
Spero sia abbastanza chiaro. In generale ci sono altri modi per ovviare
all’utilizzo di questi for-else o while-else, semplicemente con un
paio di righe di codice in più. Di certo però queste istruzioni sono
molto pythonic :)
A voi la scelta per come e quando utilizzarle!
Controllo dei valori inseriti
Capita spesso e volentieri di voler controllare il valore che l’utente inserisce per essere sicuri non solo del tipo, ma anche del “range” del valore inserito: un numero intero fra 10 e 20, un reale diverso da 0, un carattere maiuscolo, etc…
In questi casi è possibile utilizzare il ciclo while e l’istruzione break appena studiata, abbinate ad un minimo di ingegno per far funzionare la cosa.
Facciamo una prova.
# ciclo con condizione sempre vera. Ripete all'infinito
while True:
# nell'input dobbiamo suggerire all'utente cosa fare...*
num = int(input("Inserisci un numero fra 1 e 90: "))
# controllo con interruzione
if num >= 1 and num <= 90:
break
# visualizzazione finale
print("Numero inserito:", num)
Capito vero? Verifichiamolo con qualche esercizio :)
Esercizi sul controllo valori
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 361
Chiedere all’utente di inserire un numero intero fra 1 e 10. Ripetere l’inserimento finché la condizione non viene soddisfatta. Alla fine, visualizzare il numero inserito.
Esercizio 362
Chiedere all’utente di inserire un numero intero pari fra 0 e 100. Ripetere l’inserimento finché la condizione non viene soddisfatta. Alla fine, visualizzare il numero inserito.
Esercizio 363
Chiedere all’utente di inserire un numero reale negativo. Ripetere l’inserimento finché la condizione non viene soddisfatta. Alla fine, visualizzare il numero inserito.
Esercizio 364
Chiedere all’utente di inserire un carattere, fra le maiuscole ‘A’, ‘B’, ‘C’. Ripetere l’inserimento finché la condizione non viene soddisfatta. Alla fine, visualizzare il carattere inserito.
Esercizio 365: caratteri minuscoli
Chiedere all’utente di inserire un carattere minuscolo. Ripetere l’inserimento finché la condizione non viene soddisfatta. Alla fine, visualizzare il carattere inserito.
Esercizio 366
Chiedere all’utente di inserire 2 numeri reali, di cui il secondo non nullo (da verificare). Infine visualizzare il rapporto (il risultato della divisione) fra questi.
Esercizio 367
Predisporre un programma che permetta all’utente di inserire numeri interi e che si interrompa solo quando l’utente inserisce ZERO. Alla fine dell’inserimento visualizzare la somma e la media dei valori inseriti.
Esercizio 368: interi, stringhe e congiunzioni (di stringhe)
Chiedere all’utente di inserire caratteri minuscoli, finché non inserisce il carattere ‘!’ per terminare. Alla fine si visualizzi tutti i caratteri inseriti, in una sola riga, separati da spazi.
Tip
Create una stringa congiungendo il carattere inserito e uno spazio…
Esercizio 369
Visualizzare la domanda “Hai capito?” e continuare a ripeterla finché l’utente non dice “sì” (con l’accento sulla ì, mi raccomando…)
Esercizio “ragionato” svolto
Questo esercizio è stato pensato perché arrivati a questo punto, in cui conosciamo tutte le istruzioni base del linguaggio, dobbiamo migliorare nella nostra capacità di risolvere gli esercizi. Dobbiamo trovare una strategia che ci permetta di affrontare ogni tipo di esercizio, anche molto difficile, con la speranza di riuscire a risolverlo.
A questo proposito ho cercato di dividere la mia soluzione in step ben definiti che dovete assolutamente provare a riprodurre ogni volta che affrontate un problema un pochino più articolato, come quelli che inizieranno a capitare da adesso in poi.
**STEP 0: lettura del testo dell'esercizio**
Cominciamo leggendo attentamente il testo dell’esercizio:
Dato un numero intero positivo N da parte dell’utente, dire se esso è primo oppure no.
Invece di provare a buttare giù codice nella speranza che l’esercizio “si risolva magicamente”, proviamo ad applicare un metodo “scientifico” per trovare una soluzione.
**STEP 1: organizzazione delle conoscenze sul contesto**
Cosa significa questa cosa? Significa cercare di fare mente locale su tutto quello che si sa sugli argomenti relativi all’esercizio, conoscenze che possono aiutarci a risolverlo!!!
Dedicate 5 minuti almeno a questa fase!!!
Iniziamo insieme a ragionarci su, anche rileggendo il testo dell’esercizio.
La prima cosa da notare è che l’esercizio si divide in 2 parti:
- Chiedere all’utente di inserire un numero intero positivo N
- Dire se N è primo oppure no
Mi immagino che siamo tutti sereni con la prima parte che si svolge come spiegato nel capitolo sul “Controllo dei valori inseriti”: ridateci un occhio se ci sono problemi su questa parte. Scrivo il codice necessario:
while True:
N = int(input("Inserisci un numero intero positivo: "))
if N > 0:
break
La seconda parte è quella più difficoltosa: come si capisce se un numero è primo? Come si calcola? Cos’è un numero primo???
Un numero naturale maggiore di 1 si definisce primo se è divisible solamente per 1 e per se stesso.
(Wikipedia, cit.)
Mi sembra che cosa sia un numero primo si sappia… ma per investigare bene il problema non ci si può esimere dal prossimo step!!!
**STEP 2: esempi su valori possibili per l'esercizio**
Partiamo scrivendo su carta alcuni esempi che possono aiutarci a riflettere:
- 7 è primo perché è divisibile solo per 1 e per 7. Infatti, nessuno dei numeri fra 2 e 6 divide 7.
- 18 non è primo perché è divisibile per 2, per 3, per 6, per 9…
- 11 è primo perché è divisibile solo per 1 e per 11. Nessun altro numero divide 11
- 25 non è primo perché è divisibile (anche) per 5, oltre ché per 1 e per 25 stesso.
**STEP 3: considerazioni desunte dagli esempi**
Per fare queste considerazioni, più esempi fate prima e meglio è… Dagli esempi precedenti ho capito che:
- tutti i numeri (primi o no) sono divisibili per 1
- tutti i numeri (primi o no) sono divisibili per se stessi
- Se N è primo, nessun numero fra 2 e N-1 lo divide
- Dato N, se almeno un numero fra 2 e N-1 divide N, allora N non è primo.
Siete d’accordo? Queste considerazioni le ho fatte guardando gli esempi che mi sono fatto! Fatene anche voi e provate a vedere se quello che ho scritto vi convince e se vi vengono in mente altre idee.
**STEP 4: formulazione di una strategia risolutiva**
Da questi primi esempi comincio a formulare una strategia.
Dato N:
- scorro tutti i numeri fra 2 e N-1
- vedo se qualcuno di questi divide N
- Se anche uno solo divide N, N non è primo!
- Se nessuno di questi divide N, allora N è primo!
La cosa più semplice da fare mi sembra contare quanti numeri fra 2 e N-1 dividono N. Se la conta porta ZERO, allora N è primo, altrimenti NO!
A questo punto ho un altro problema: come contare i divisori di N? Ho capito che i divisori di N sono tutti i numeri positivi minori di N e che 1 e N stessi vanno esclusi, perché non sono divisori interessanti.
Provo a organizzare le cose da fare in questo modo:
Passaggio 1
Dato N, visualizzo tutti i numeri fra 2 e N - 1
Passaggio 2
Dato N, visualizzo quali numeri fra 2 e N - 1 dividono N
Passaggio 3
Dato N, conto quanti numeri fra 2 e N – 1 dividono N
Passaggio 4 (finale)
Dato N, faccio lo step 3 e poi guardo il risultato: se la conta è ZERO,
N è primo, altrimenti no.
Attenzione adesso… siete riusciti a figurarvi i passaggi descritti sopra?!?! Bene! Allora sappiate 2 cose:
- arrivati qui avete già praticamente risolto l’esercizio
- la descrizione dei passaggi rappresenta esattamente i commenti che servono nel codice!
Capito il concetto?! I commenti che il prof tanto agogna scriviate nelle vostre risoluzioni sono esattamente questi, ovvero i passaggi che intendete fare per risolvere un quesito. Come poi li farete, ovvero con quale codice (un if, un if-else, un for oppure uno while) è secondario rispetto a questo!
**STEP 5: prima stesura del codice**
Provo a scrivere il codice:
# Dato N, visualizzo tutti i numeri fra 2 e N - 1
for div in range(2, N):
print (div)
# Dato N, visualizzo quali numeri fra 2 e N - 1 dividono N
for div in range(2, N):
if N % div == 0:
print (div)
# Dato N, conto quanti numeri fra 2 e N - 1 dividono N
conta = 0
for div in range(2, N):
if N % div == 0:
conta += 1
# Dato N, faccio lo step 3 e poi guardo il risultato
# se la conta è ZERO, N è primo, altrimenti no.
# . . .
if conta == 0:
print(N, "è primo")
else:
print(N, "non è primo")
**STEP 6: stesura finale del codice**
Fatto questo, mi basta rimettere insieme tutto il ragionamento e il gioco è fatto!
# inserimento controllato valore utente
while True:
N = int(input("Inserisci un numero intero positivo: "))
if N > 0:
break
# conta dei divisori "propri" di N
conta = 0
for div in range(2, N):
if N % div == 0:
conta += 1
# controllo numero dei divisori propri
if conta == 0:
print(N, "è primo")
else:
print(N, "non è primo")
**STEP 7: test del codice scritto**
Arrivati a scrivere tutto, non è mica finito!!! Il codice scritto va testato e controllato! Io direi che sono necessarie almeno 4 esecuzioni con valori diversi per verificarlo. Se tutto funziona, avete finito. Se ci sono problemi, bisogna analizzare a ritroso tutti gli step, a costo di ripartire dallo step 0!!!
In effetti un piccolo errore in questo codice c’è… infatti, facendo alcune prove, ci si accorge che questo codice riconosce il numero 1 (e tecnicamente… anche tutti i negativi) come primo, mentre wikipedia (vedi sopra) ci dice che non lo è…
Provate a correggere voi il codice…
Rifate i test per verificare che adesso sia tutto a posto…
Finito!
Spero che abbiate capito il ragionamento svolto e (soprattutto) come esso si sia sviluppato ragionando a partire dal testo del quesito e da alcuni esempi reali che mi sono fatto coi numeri.
Spero di avervi aiutato un po’… Ecco una serie di esercizi in cui mettervi alla prova!!!
Esercizi finali sui cicli
Gli esercizi elencati qua di seguito non sono semplici applicazioni dei concetti studiati finora, ma sono i primi esercizi che richiedono una buona dose di ragionamento e di preparazione per “progettare” la soluzione dell’esercizio.
Warning
Attenzione alle IA
Capisco la tentazione di aiutarsi nella risoluzione tramite le IA. Ricordate però che sarete valutati sulla comprensione di quanto avete consegnato. Come hai svolto un certo esercizio e perché lo hai svolto in quel modo…
Esercizio 381
Acquisire un numero positivo N e calcolarne la radice quadrata intera (ovvero il massimo intero x tale che x al quadrato sia minore o uguale a N).
Esercizio 382
Acquisire una sequenza di numeri interi e calcolare la somma di quelli positivi. Il programma deve terminare non appena l’utente inserisce per due volte consecutive un valore negativo.
Esercizio 383
Scrivere un programma che acquisisca da tastiera un numero intero assicurandosi che sia positivo e, successivamente, stampi a video i 5 anni bisestili strettamente superiori al numero acquisito.
Esercizio 384
Visualizzare la Tavola Pitagorica.
Esercizio 385: numeri perfetti
Un numero intero si dice perfetto se risulta uguale alla somma dei suoi divisori (minori del numero stesso). Ad esempio 6 = 1 + 2 + 3, che sono i suoi divisori, è un numero perfetto.
Dato un numero intero positivo C, da parte dell’utente, verificare se è perfetto.
Esercizio 386
Dato un numero F, inserito dall’utente e compreso fra 0 e 12 (verificare), visualizzare il fattoriale di F.
Note
Il Fattoriale
Il fattoriale di un numero intero positivo F si indica con ! ed è definito come il prodotto di tutti i numeri da 1 a F. Ad esempio:
6! = 1 * 2 * 3 * 4 * 5 * 6 = 720Per definizione inoltre abbiamo:
0! = 1 n! = ( n - 1 ) ! * n
Esercizio 387: cornice rettangolare
Visualizzazione di un rettangolo con cornice “+” e interno “=” di misure A e B, decise dall’utente
Esercizio 388: rettangolo
Programma che disegna un rettangolo di caratteri di BASE e ALTEZZA scelti dall’utente. Il rettangolo è formato da un carattere a scelta come cornice e da un carattere a scelta all’interno.
Esercizio 389: MCM
Inseriti 2 numeri interi positivi (verificare) da parte dell’utente, calcolare il minimo comune multiplo fra di essi.
Esercizio 390: MCD
Inseriti 2 numeri interi positivi (verificare) da parte dell’utente, calcolare il Massimo Comun Divisore fra di essi.
Esercizio 391: Quadrato Perfetto
Un quadrato perfetto è un numero della sequenza 1, 4, 9, 16, 25… ovverosia un numero qualsiasi creato come prodotto per se stesso di un qualunque numero intero positivo.
Dati dall’utente 2 numeri A, B positivi (da verificare) e tali che A sia minore o uguale a B, visualizzare il numero di quadrati perfetti presenti nella sequenza dei numeri [A, B].
Ad esempio, dati A = 6, B = 16, la risposta è 2 perché fra 6 e 16 sono presenti i quadrati perfetti 9 e 16 stesso.
Dati A = 4 e B = 5, la risposta è 1 perché fra 4 e 5 è presente un solo quadrato perfetto, cioè 4.
Esercizio 392
Scrivere un programma che riceva in ingresso un numero positivo N e determini il massimo intero K tale che la somma dei primi K interi sia minore o uguale a N. Ad esempio, se N=20 allora K risulta 5, infatti
1 + 2 + 3 + 4 + 5 = 15
mentre
1 + 2 + 3 + 4 + 5 + 6 = 21
Esercizio 393: Fibonacci
Note
La successione di Fibonacci
È una sequenza numerica tale che:
- il primo numero è 0
- il secondo numero è 1
- il numero successivo si ottiene come somma dei due precedenti
Iterando questo procedimento si ottiene la sequenza 0, 1, 1, 2, 3, 5, 8, 13, 21 … nota come Successione di Fibonacci.
Dato un numero intero N da parte dell’utente (verificare che sia positivo), visualizzare la successione di Fibonacci da 0 fino all’ennesimo numero.
Ad esempio, dato N = 8, va visualizzata la successione: 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13.
Esercizio 394
Dopo aver fatto inserire un numero intero positivo dire da quante cifre è composto il numero. Ad esempio 12345 è composto da 5 cifre.
Eccezioni
L’interprete Python è il responsabile dell’esecuzione del codice scritto nei moduli Python.
Per essere eseguito il codice viene prima controllato dal punto di vista sintattico. Un errore nella sintassi provoca una immediata interruzione da parte dell’interprete.
Quando il codice è corretto sintatticamente, ma contiene un errore di logica, si genera un errore di runtime (ovvero durante l’esecuzione): a questo punto l’interprete Python solleva una eccezione!
>>> 42 / 0
ZeroDivisionError: division by zero
Il programmatore può intercettare le eccezioni generate e gestirle in modo opportuno.
Vediamo il codice:
try:
# blocco di codice da verificare.
# indentato di 4 caratteri
except ErrorType1:
# blocco di codice da eseguire se nella clausola try accade un errore di tipo ErrorType1
# Se hai messo un try... almeno un except è obbligatorio!!!
# indentato di 4 caratteri
except ErrorType2:
# blocco di codice da eseguire se nella clausola try accade un errore di tipo ErrorType2
# indentato di 4 caratteri]
[else:
# la clausola opzionale else va DOPO le except
# Contiene codice da eseguire SOLO SE non si verificano ECCEZIONI.
# indentato di 4 caratteri]
[finally:
# blocco di codice da eseguire SEMPRE!
# indentato di 4 caratteri]
# ISTRUZIONE SUCCESSIVA
Vediamo un botto di esempi per meglio chiarire la struttura del codice descritta sopra!
try:
n = int(input("Inserisci un numero intero: "))
print("Il doppio è", n * 2)
except ValueError:
print("Non hai inserito un numero intero valido!")
try:
a = int(input("Numeratore: "))
b = int(input("Denominatore: "))
risultato = a / b
print("Risultato:", risultato)
except ValueError:
print("Devi inserire numeri interi!")
except ZeroDivisionError:
print("Non puoi dividere per zero!")
try:
eta = int(input("Quanti anni hai? "))
except ValueError:
print("Età non valida.")
else:
# eseguito SOLO se non ci sono stati errori nel try
if eta >= 18:
print("Sei maggiorenne")
else:
print("Sei minorenne")
finally:
# eseguito SEMPRE, con o senza errore
print("Grazie per aver usato il programma")
while True:
try:
n = int(input("Inserisci un numero positivo: "))
if n <= 0:
print("Il numero deve essere positivo, riprova")
continue
break
except ValueError:
print("Devi inserire un numero intero, riprova")
print("Hai inserito:", n)
Built-in Exceptions
Le eccezioni predefinite che incontrerete più spesso sono sicuramente queste:
| Eccezione | Quando si verifica |
|---|---|
| ValueError | valore “giusto” come tipo ma sbagliato come contenuto (es. int(“ciao”)) |
| ZeroDivisionError | divisione o modulo per zero |
| TypeError | operazione tra tipi incompatibili (es. “5” + 3) |
| NameError | uso di una variabile non definita |
| KeyboardInterrupt | l’utente interrompe il programma con Ctrl+C |
Esercizi
Esercizi per la comprensione della gestione delle eccezioni in Python.
Esercizio 1 — Divisione protetta
Chiedi due numeri interi all’utente e stampane il quoziente. Gestisci sia il caso in cui l’utente non inserisca un intero valido, sia il caso di divisione per zero, con messaggi diversi per ciascun errore.
Esercizio 2 — Media di due voti
Chiedi due voti (numeri, anche con la virgola) e calcolane la media. Se l’utente inserisce un valore non numerico, stampa un messaggio d’errore e termina senza calcolare la media.
Tip
float()sollevaValueErrorcomeint()
Esercizio 3 — Calcolatrice a 4 operazioni
Chiedi due numeri e un operatore (+, -, *, /). Esegui l’operazione richiesta gestendo:
- input numerico non valido (ValueError)
- divisione per zero (ZeroDivisionError)
- operatore sconosciuto (usa un else sul confronto con gli operatori validi)
Esercizio 4 — Input robusto con ciclo
Scrivi un programma che chiede ripetutamente un’età finché l’utente non inserisce un numero intero compreso tra 0 e 120. Ad ogni errore (sia di formato che di valore fuori range) stampa un messaggio specifico e richiedi di nuovo il dato.
Esercizio 5 — Conto alla rovescia sicuro
Chiedi un numero intero N e stampa un conto alla rovescia da N a 0 usando un ciclo while. Se l’utente inserisce un valore non intero, intercetta l’errore e chiedi di reinserirlo (riusa la tecnica dell’Esempio 4).
Esercizio 6 — Calcolo del BMI con finally
Chiedi peso (kg) e altezza (m) e calcola il BMI (BMI = peso / altezza**2). Usa try/except/else/finally:
exceptper input non numericoelseper stampare il risultato e la fascia (sotto/normo/sovrappeso) solo se il calcolo è andato a buon finefinallyper stampare sempre un messaggio di chiusura tipo “Calcolo completato”
Sequenze
Una sequenza è intuitivamente una collezione di elementi messi “in fila”. Pur potendo considerare, in siffatte strutture, tipologie di oggetti anche molto diversi (anche fra loro, all’interno della stessa sequenza) ci sono una serie di operazioni che funzionano comunque in questo contesto: ad esempio contare gli elementi presenti nella sequenza o verificare se un certo elemento è presente all’interno della stessa.
Le operazioni che adesso andiamo ad elencare sono comuni a tutte le sequenze.
Tip
Nella tabella seguente
x, y, z sono elementi possibilmente presenti in una sequenza,
s e t sono sequenze di esempio,
i, k, n sono numeri interi.
| Comando o Funzione | Descrizione |
|---|---|
x in s | True se un elemento di s è uguale a x, altrimenti False |
x not in s | False se un elemento di s è uguale a x, altrimenti True |
s + t | La sequenza prodotta dalla concatenazione di s e t |
s * n oppure n * s | La sequenza prodotta dalla concatenazione di s con se stessa per n volte |
s[i] | L’elemento della sequenza s al posto i, considerando che il primo elemento della sequenza si trova al posto ZERO. |
len (s) | Numero di elementi di s, lunghezza di s |
min (s) | Elemento più piccolo di s |
max (s) | Elemento più grande di s |
s.count (x) | Numero di volte in cui x compare nella sequenza s |
s.index (x) | La prima posizione della sequenza s ove si trova l’elemento x. Ritorna errore se l’elemento non è presente. |
Ora… queste operazioni sembrano molto generiche come il concetto stesso di “sequenza”. Allora, per partire con un esempio semplice pensiamo così: una stringa non è altro che una sequenza di caratteri!!
Più precisamente… le stringhe sono definite in Python come delle sequenze immutabili di caratteri. Lasciamo a dopo il significato di immutabile e concentriamoci sul fatto che sono sequenze: supportano tutte le operazioni che abbiamo appena elencato.
Facciamo degli esempi per illustrare il concetto:
if "n" in "Python":
print("Presente")
else:
print("Assente")
# scrive "Presente"
Altro esempio:
if "s" not in "Python":
print("Assente")
# scrive "Assente"
nome = "Capo" + "Stazione"
print(nome)
# scrive "CapoStazione"
Altro esempio:
parola = "Acqua" * 3
print(parola)
# scrive "AcquaAcquaAcqua"
Per quanto riguarda l’operatore sequenza[intero] che ci permette
di visualizzare l’elemento al posto richiesto, vanno precisate alcune
cose:
- il primo elemento è l’elemento al posto ZERO
- Andando avanti ogni elemento si trova (ovviamente) al posto successivo
Vediamo un esempio:
nome = "Leonardo"
print("Al posto 0 troviamo: ", nome[0])
print("Al posto 4 troviamo: ", nome[4])
print("Al posto 8 troviamo: ", nome[8])
# Scrive:
# Al posto 0 troviamo L
# Al posto 4 troviamo a
# Al posto 8 troviamo ERRORE!!!
Va inoltre specificato che la numerazione degli elementi di una sequenza funziona anche con i numeri negativi, dove:
- l’ultimo elemento è quello al posto -1
- il penultimo è quello al posto -2
- .. e così via fino al primo elemento
Ancora un esempio:
cognome = "Da Vinci"
print("All'ultimo posto: ", cognome[-1])
print("Al penultimo posto: ", cognome[-2])
print(". . ." , cognome[-9])
# Scrive:
# All'ultimo posto: i
# Al penultimo posto: c
# . . . ERRORE!!!
Riassumendo il concetto con un disegno, inizializzata una stringa, le posizioni degli elementi si contano come in figura:
linguaggio = "Python"

La funzione len(sequenza) ci dice il numero di elementi della stessa.
print(linguaggio , "contiene" , len(linguaggio) , "caratteri")
# scrive "Python contiene 6 caratteri"
La funzione sequenza.count(elemento) ci dice quante volte un
elemento compare in una sequenza.
prova = "Buonasera"
print("Quante 'a' in " , prova , ":" , prova.count('a') )
print("Quante 't' in " , prova , ":" , prova.count('t') )
# scrive:
# Quante a in Buonasera: 2
# Quante t in Buonasera: 0
Warning
Attenzione alla differenza fra maiuscole e minuscole!!!
La funzione sequenza.index(elemento) ritorna la prima posizione in cui si trova un elemento ed errore se l’elemento non è
presente, quindi ATTENZIONE!!!
prova = "Buonasera"
print( prova.index('a') )
# scrive 4
Mentre…
print( prova.index('t') )
# ERRORE!
Per assicurarci che la funzione index() non dia errore bisogna controllare la presenza o meno della variabile nella parola:
if 't' in prova:
print( prova.index('t') )
Le funzioni min(sequenza) e max(sequenza) ci ritornano rispettivamente l’elemento minore e maggiore della sequenza.
nome = "Gianmarco"
print( min(nome) )
# scrive 'G'
print( max(nome) )
# scrive 'r'

Se, come spero, grazie agli esempi sopra le funzioni tipiche delle sequenze saranno molto più chiare, rimane ancora un mistero da svelare, ovvero per quale assurdo motivo la lettera più piccola (min) della parola “Gianmarco” sia la “G” invece della ‘a’? Non funziona l’ordine alfabetico?
Questo funzionamento dipende dal fatto che i caratteri sono memorizzati all’interno dei nostri dispositivi come sequenze numeriche e la corrispondenza numero-lettera è data dal cosiddetto codice ASCII.
Guardate l’immagine sul codice ASCII che riporta i caratteri stampabili in una tabella: come si desume osservandola, le lettere maiuscole, ordinate secondo l’alfabeto tradizionale, vengono tutte prima delle minuscole e secondo il confronto numerico sono da considerarsi “più piccole”.
Lavorare con le sequenze
Quando vogliamo controllare se un valore si trova in una sequenza,
abbiamo capito di poter ricorrere all’operatore in all’interno di una
istruzione condizionale, un if-else:
parola = input("Inserisci una parola")
if "a" in parola:
print("la parola che hai scritto contiene la 'a'")
else:
print("la parola che hai scritto NON contiene la 'a'")
A seconda della parola che l’utente inserisce ci aspettiamo una risposta diversa. Questo perché l’istruzione elemento in sequenza ritorna un valore boolean che può essere True o False esattamente come serve al comando if.
Questo controllo funziona con qualunque stringa: ad esempio funziona benissimo anche per la ricerca delle sottostringhe:
if "gn" in parola:
print("la parola che hai scritto contiene la 'gn'")
else:
print("la parola che hai scritto NON contiene la 'gn'")
C’è però un altro metodo di applicare l’operatore in alle sequenze che è quello del comando for..in:
parola = "oggi"
for car in parola:
print(car)
Scrive:
o
g
g
i
Come si desume osservando l’esempio, il comando FOR elemento IN sequenza scorre tutta la sequenza, assegnando ogni volta alla
variabile ausiliaria elemento il valore di uno degli elementi della sequenza.
Se invece vogliamo scorrere le posizioni dobbiamo muoverci con un ciclo FOR...IN RANGE(), Infatti una sequenza è lunga len(sequenza)
elementi e i suoi elementi sono numerati da 0 a len(sequenza) - 1, esattamente come si comporta la funziona range(len(sequenza)).
parola = "oggi"
for pos in range(len(parola)):
print("posizione" , pos , ":" , parola[pos])
Scrive:
posizione 0: o
posizione 1: g
posizione 2: g
posizione 3: i
Facile e comodo. Tutto chiaro?
Esercizi sulle stringhe
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Negli esercizi che seguono dobbiamo ricordare che le stringhe in Python
non sono altro che un tipo particolare di sequenza (di caratteri).
Aggiungo inoltre che per ogni necessità ulteriore ci sono sempre le
funzioni dir() e help().
Esercizio 401
Chiedere all’utente di inserire una stringa e visualizzarla. Poi visualizzare i caratteri che compongono la stringa uno per riga.
Esercizio 402
Chiedere all’utente di inserire una stringa e visualizzarla. Poi visualizzare il numero di caratteri presenti nella stringa inserita.
Esercizio 403
Chiedere all’utente di inserire una stringa e visualizzarla.
Chiedere successivamente di inserire un carattere (verificare che sia un solo carattere!) e verificare se esso è presente nella stringa inserita.
Infine verificare se il carattere è presente nell’altro caso (ad esempio se l’utente inserisce il carattere “a” ma nella stringa è presente il carattere “A”).
(suggerimento: guarda dir(str) e help(str.upper) oppure help(str.lower)).
Esercizio 404
Chiedere all’utente di inserire il proprio nome, da salvare in una variabile e successivamente il proprio cognome, da salvare in un’altra variabile. Creare la variabile nominativo che contiene il nome inserito, uno spazio e il cognome inserito. Visualizzare la variabile nominativo così creata.
Esercizio 405
Chiedere all’utente di inserire una frase e un numero intero. Poi visualizzare la frase inserita per quel numero di volte.
Esercizio 406
Chiedere all’utente di inserire una frase e un numero intero. Poi creare una stringa che contiene la frase , uno spazio, la frase, uno spazio, la frase, uno spazio… per il numero di volte inserito dall’utente. L’ultimo spazio non va inserito!!!
Ad esempio, dato “cane” e 3, la stringa da creare sarà “cane cane cane”. Non basta visualizzarla: il valore va assegnato ad una variabile.
Visualizzare la stringa così creata.
Esercizio 407
Chiedere all’utente di inserire una stringa e visualizzarla tutta scritta in caratteri maiuscoli.
(suggerimento: guarda dir(str) e help(str.upper))
Esercizio 408
Chiedere all’utente di inserire una stringa e visualizzarla tutta scritta in minuscolo con iniziali maiuscole per ogni parola.
(suggerimento: guarda dir(str) e help(str.title))
Esercizio 409
Chiedere all’utente di inserire una stringa e visualizzarla. Chiedere successivamente di inserire un carattere e contare quante volte il carattere è presente nella stringa inserita.
Ripetere lo stesso calcolo ignorando il caso, ovvero contando le lettere minuscole e/o maiuscole presenti.
Esercizio 410
Chiedere all’utente di inserire una stringa e visualizzarla. Contare le vocali (maiuscole e minuscole) presenti nella stringa. (Come si fa??)
Esercizio 411
Chiedere all’utente di inserire una stringa e visualizzarla. Successivamente verificare se la stringa inizia oppure no con una vocale.
Esercizio 412
Chiedere all’utente di inserire una frase e visualizzarla in modo tale che ogni volta che si incontra uno spazio si vada a capo.
(suggerimento: help(print) )
Questo esercizio va fatto modificando il comportamento della funzione print().
Chiedetemi se l’help non vi risulta troppo chiaro!
Esercizio 413
Chiedere all’utente di inserire il proprio nome. Se esso termina per ‘o’ visualizzare la frase “Sei un maschio?”. Se esso termina per “a” visualizzare la frase “Sei una femmina?”. In tutti gli altri casi visualizzare la frase “Sei un maschio o una femmina?”.
Esercizio 414
Chiedere all’utente di inserire una stringa e visualizzarla. Successivamente chiedere all’utente di inserire un carattere e verificare se esso è presente nella stringa. Se è presente, chiedere un secondo carattere e rimpiazzare nella stringa tutte le occorrenze del primo carattere con il secondo.
(Suggerimento: help(str.replace) )
Esercizio 415
Chiedere all’utente di inserire una stringa e visualizzarla. Successivamente visualizzare il carattere più volte presente nella stringa.
Esercizio 416
Chiedere all’utente di inserire un numero intero N e calcolare il valore decimale di 1/N. Le sue cifre decimali contengono la cifra 9? In quale posizione?
(Suggerimento: calcolare 1/N come float, poi convertirlo in stringa e…)
Esercizio 417
Dichiarare la stringa alfabeto, contenente tutte le lettere dell’alfabeto, la stringa numeri, contenente tutte le cifre da 0 a 9 e la stringa alfanumerici, contenenti tutti i caratteri di punteggiatura diversi che riuscite ad inserire in una stringa.
Chiedere all’utente di inserire una stringa e dire se essa contiene lettere, cifre, caratteri alfanumerici o spazi.
Esercizio 418
Chiedere all’utente di inserire una stringa e visualizzarla. Visualizzare le vocali presenti nella stringa. Visualizzare la posizione delle vocali presenti nella stringa.
Esercizio 419
Chiedere all’utente di inserire una frase qualsiasi, lunga a piacere. Visualizzare la stringa inserita.
Chiedere poi all’utente di inserire un carattere a piacere, verificando che sia una lettera e che sia solo uno. Ad esempio “f” va bene (lettera singola), “?” non va bene (no lettera), “fa” non va bene (2 lettere). Contare quante volte il carattere è presente nella frase (minuscolo e maiuscolo).
Permettere all’utente di inserire un nuovo carattere singolo e sostituire le occorrenze del carattere precedente con il nuovo, visualizzando la stringa risultante
Ad esempio, inserita la stringa “buonasera” e indicato prima il carattere “a” e successivamente il carattere “g” dovrà essere visualizzata la stringa “buongserg”.
Tuple
Python fornisce un tipo predefinito, chiamato tupla, per indicare una sequenza immutabile di oggetti, di solito diversi fra loro anche per tipo.
In Python, un tipo di dato si dice immutabile se, una volta definito il suo valore, esso non può essere modificato.
Viceversa un tipo di dato si dice mutabile se il suo valore può essere modificato anche dopo essere stato definito.
Essendo la tupla un tipo sequenza, essa supporta tutte le operazioni definite per le sequenze e visionate poco fa per le stringhe.
Una tupla si definisce elencando dentro le parentesi tonde una serie di valori separati da virgola:
tabellina7 = ( 7 , 14 , 21 , 28 , 35 , 42 , 49 , 56 , 63 , 70 )
Come dicevamo, per le tuple valgono tutte le operazioni tipiche delle sequenze:
len(tabellina7) # vale 10
min(tabellina7) # vale 7
Possiamo controllare la presenza di un valore con IN:
num = int(input("Inserisci un numero: "))
if num in tabellina7:
print(num,"fa parte della tabellina del 7")
Oppure visualizzare i valori con un ciclo:
for num in tabellina7:
print(num)
Scrive:
7
14
21
28
35
42
49
56
63
70
Facciamo un altro esempio con valori eterogenei:
tupla = ("ciao" , 12 , 23.45 , 1000000 , 12, "come??")
print(tupla)
scrive:
( "ciao" , 12 , 23.45, 1000000 , 12, "come??" )
tupla.count(12) # vale 2
tupla.index(12) # vale 1 (la posizione della prima istanza di 12)
Ricordo che i valori presenti in una sequenza sono numerati dal primo all’ultimo a partire da zero, quindi:
tupla[0] # vale "ciao"
tupla[3] # vale 1000000
tupla[1] = "prova" ERRORE!!!
Warning
Errore!!!
tupla[1] = "prova" # ERRORE!!!Le tuple sono immutabili! Ricordate???
A occhio sembrano abbastanza facili! Attenti però…
In Python la definizione di una tupla non richiede espressamente le parentesi tonde, basta elencare i valori con la virgola. Ecco allora che lo studente sprovveduto potrebbe scrivere:
pigreco = 3,14
print(pigreco)
scrive:
( 3 , 14 )
mentre:
pi = 3.14
print(pi)
scrive:
3.14
Warning
pigreco è una tupla NON un valore reale!
type (pigreco) scrive <class 'tuple'>
type (pi) scrive <class 'float'>
In Python il separatore decimale è il punto NON la virgola!!!
Quando si usano le tuple è sempre meglio utilizzare sempre le parentesi tonde per evitare spiacevoli equivoci!!!
# scrivi le tuple con le parentesi tonde!
tupla = (3,14)
La caratteristica delle tuple di essere immutabili non limita comunque la possibilità di inserirvi dati via codice: basta semplicemente usare qualche accortezza!
nome = input("Inserisci nome: ")
cognome = input("Inserisci cognome: ")
datiPersonali = ( nome , cognome )
num = int ( input("Inserisci numero: ") )
tabellina = (num*1 , num*2 , num*3 , num*4 , num*5 , num*6 , num*7 , num*8 , num*9 , num*10)
es1 = (1,)
es2 = (1)
print("es1:",es1, type(es1))
print("es2:",es2, type(es2))
scrive:
es1: (1,) <class 'tuple'>
es2: 1 <class 'int'>
In realtà, con un po’ di furbizia e la sintassi giusta, è possibile anche “modificare” una tupla:
tupla = ()
for i in range(10):
# questo codice OGNI volta crea una nuova tupla aggiungendo (i) in fondo al valore della precedente
# es: (1,2) + (3,) diventa la tupla (1,2,3)
tupla += (i,)
print(tupla)
( 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 )
Spero sia tutto sufficientemente chiaro!
Per schiarirsi definitivamente le idee… ci sono gli esercizi :)
Esercizi sulle Tuple
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 421
Dichiarare una tupla con i nomi dei giorni della settimana. Visualizzare la tupla con una print, poi visualizzare la stessa con un ciclo, visualizzando i giorni uno per riga.
Alla fine visualizzare il numero degli elementi della tupla con la funzione len.
Esercizio 422
Dichiarare una tupla con i nomi di alcuni dei propri parenti. Chiedere all’utente di inserire un nome e verificare se la persona ha lo stesso nome di un proprio parente. Visualizzare il risultato, confermando la ricerca visualizzando anche l’elenco dei propri parenti con un elenco intervallato da virgole.
Ad esempio se la tupla contiene ("gino", "pino") e l’utente
inserisce il nome "dino", visualizzare una frase tipo: "dino" non
può essere mio parente, infatti i miei parenti si chiamano: "gino", "pino".
Esercizio 423
Dichiarare una tupla con i nomi dei mesi.
Chiedere all’utente di inserire una data digitando i numeri per giorno,
mese, anno (ad esempio: 16 , 10 , 2020). Utilizzare la tupla con i
nomi dei mesi per visualizzare la data nella forma "16 ottobre 2020".
Esercizio 424
Dichiarare una tupla con i nomi dei nani, senza visualizzarla! Chiedere all’utente di inserire i nomi dei nani e verificare se li sa tutti oppure si sbaglia.
Esercizio 425
Dichiarare una tupla che contiene la tabellina del 5 e poi scrivere un ciclo per visualizzarla nella forma:
5 x 1 = 5
5 x 2 = 10
5 x 3 = 15
. . .
Esercizio 426
Chiedere all’utente di inserire un numero intero fra 2 e 15 (da verificare) e dichiarare la tupla che contiene la tabellina del numero inserito.
Visualizzare la tupla così creata.
Esercizio 427
Chiedere all’utente di inserire i propri dati personali relativi a: nome, cognome, sesso, luogo di nascita. Poi dichiarare una tupla contenente queste 4 informazioni. Ripetere l’operazione per 3 persone. Alla fine devono rimanere nelle variabili tutti i dati inseriti!
(sfida: provate a farlo con un ciclo, in modo da non dover ripetere le operazioni…)
Esercizio 428
Chiedere all’utente di inserire un numero reale in una variabile chiamata “real” e dichiarare la tupla così definita:
tupla = ( real * -1 , 0 , real , real * 3 , real * 0.5 )
Visualizzare la tupla così ottenuta e visualizzare il valore più piccolo e più grande di essa.
Esercizio 429
Chiedere all’utente di inserire 5 numeri reali e al termine dell’inserimento visualizzare la tupla composta dai numeri inseriti dall’utente.
Chiedere all’utente di inserire un altro numero e verificare se esso sia presente nella tupla. In caso positivo visualizzare la prima posizione ove esso è presente.
Esercizio 430
Chiedere all’utente di inserire una stringa e un numero intero positivo (verificare). Creare la tupla contentente la stringa per numero volte.
Ad esempio, inserito "ciao" e 4 va creata la tupla ("ciao","ciao","ciao","ciao").
Chiedere all’utente di inserire due stringhe e un numero intero positivo (verificare). Creare la tupla contentente le due stringhe per numero volte.
Ad esempio, inseriti "pippo" , "ciccio" e 3 va creata la tupla ("pippo", "ciccio", "pippo", "ciccio", "pippo", "ciccio").
Esercizio 431
Chiedere all’utente di inserire 5 numeri interi e creare una tupla contenente questi 5 numeri. Visualizzare la tupla con una semplice print. Chiedere all’utente di inserire un numero qualsiasi e visualizzare la frase: “il numero inserito è presente nella tupla per TOT volte”. Ovviamente TOT va calcolato…
Liste
Python fornisce un tipo predefinito, chiamato lista, per indicare una sequenza mutabile di oggetti, solitamente omogenei fra loro per tipo.
Le liste vengono definite in fase di dichiarazione utilizzando le parentesi quadre per racchiudere l’elenco degli argomenti. Ancora una volta ricordo che le liste sono un altro tipo sequenza e che quindi supportano tutte le proprietà definite per esse.
lista = [ 13, 8, 6, 11, 13, 22 ]
print(lista) # scrive [ 13, 8, 6, 11, 13, 22 ]
lista[1] # vale 8
max(lista) # vale 22
Le liste sono oggetti mutabili, ovvero tali per cui è possibile cambiarne il valore. Ad esempio:
quadrati = [0, 1, 4, 10, 16, 25] # oops...
quadrati[3] = 9
print(quadrati) # scrive [0, 1, 4, 9, 16, 25]
Questa caratteristica di essere sequenze mutabili fornisce alle liste alcune funzionalità aggiuntive per la manipolazione semplice degli elementi delle stesse.
Le funzioni che seguono sono caratteristiche solo delle liste e si aggiungono a tutte le funzionalità che derivano dal loro essere delle sequenze.
| Funzione | Descrizione |
|---|---|
| list.append( elem ) | Aggiunge elem in fondo alla lista. |
| list.insert( index , elem ) | Inserisce elem al posto index della lista, spostando in avanti i successivi |
| list.pop( index = -1) | Rimuove e restituisce elemento al posto index (oppure l’ultimo) |
| list.remove( elem ) | Trova e rimuove (la prima istanza di) elem dalla lista. |
| list.sort() | Ordina la lista (se possibile) in senso crescente |
| list.reverse() | Inverte l’ordine degli elementi della lista |
| list.copy() | Crea e restituisce una copia della lista |
| list.clear() | Rimuove tutti gli elementi dalla lista |
Vediamo ancora alcuni esempi per chiarire il funzionamento dei metodi descritti:
# list.append(x) aggiunge l'elemento x in fondo alla lista
lettere = [ 'c' , 'a' , 's' , 'o' ]
lettere.append('!')
print(lettere) # scrive [ 'c' , 'a' , 's' , 'o' , '!' ]
# list.insert(n , x) inserisce l'elemento x al posto n della lista
# lettere vale [ 'c' , 'a' , 's' , 'o' , '!' ]
lettere.insert(3,'c')
print(lettere) # scrive [ 'c' , 'a' , 's' , 'c' , 'o' , '!' ]
# list.pop() estrare e ritorna l'ultimo elemento della lista
# lettere vale [ 'c' , 'a' , 's' , 'c' , 'o' , '!' ]
print(lellere.pop()) # scrive !
print(lettere) # scrive [ 'c' , 'a' , 's' , 'c' , 'o' ]
# list.pop(n) estrare e ritorna l'elemento al posto n della lista
# lettere vale [ 'c' , 'a' , 's' , 'c' , 'o' ]
print(lellere.pop(1)) # scrive a
print(lettere) # scrive [ 'c' , 's' , 'c' , 'o' ]
# list.remove(x) rimuove la prima occorrenza di x dalla lista
# lettere vale [ 'c' , 's' , 'c' , 'o' ]
lettere.remove('c')
print(lettere) # scrive [ 's' , 'c' , 'o' ]
Note
remove()elimina solo la prima occorrenza dell’elemento
# list.sort() ordina la lista stessa, senza ritornare nulla!!!
# lettere vale [ 's' , 'c' , 'o' ]
lettere.sort()
print(lettere) # scrive [ 'c' , 'o' , 's' ]
# list.reverse() rovescia l'ordine degli elementi della lista
# lettere vale [ 'c' , 'o' , 's' ]
lettere.reverse()
print(lettere) # scrive [ 's' , 'o' , 'c' ]
# list.clear() cancella la lista
# lettere vale [ 's' , 'o' , 'c' ]
lettere.clear()
print(lettere) # scrive [], la lista vuota
Ho lasciato per ultimo 2 esempi per far capire la necessità del metodo copy() che restituisce una copia della lista pronta per essere assegnata ad una nuova variabile.
list1 = [1 , 2 , 3]
list2 = list1
list2[0] = 5
print(list1)
[ 5 , 2 , 3 ]
list1 = [1 , 2 , 3]
list2 = list1.copy()
list2[0] = 5
print(list1)
[ 1 , 2 , 3 ]
Spero sia chiaro! Capisco che queste cose non siano esattamente banali… ma fortunatamente è possibile verificare chiaramente su uno ha davvero capito!
Basta provare il codice degli esempi e provare a fare gli esercizi!
Esercizi sulle liste
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 441
Dichiarare una lista vuota e procedere ad inserire al suo interno tramite un ciclo i primi 30 numeri interi. Visualizzare la lista così ottenuta.
Calcolare la somma dei numeri della lista e visualizzarla.
Esercizio 442
Dichiarare una lista vuota e procedere ad inserire al suo interno tramite codice la tabellina del 3. Visualizzare la lista così ottenuta, in modo da enunciare la tabellina come alle elementari:
3 per 1 = 3
3 per 2 = 6
. . .
Esercizio 443
Dichiarare una lista vuota e chiedere all’utente di inserire un numero intero. Riempire la lista con la tabellina di quel numero. Visualizzare la lista così ottenuta.
Esercizio 444
Dichiarare una lista vuota e riempirla con i primi 20 numeri dispari. Visualizzare la lista così ottenuta. Successivamente procedere ad eliminare dalla lista tutti i numeri multipli di 3. Visualizzare gli elementi eliminati dalla lista e alla fine della procedura la lista rimanente dei dispari che non sono multipli di 3.
Esercizio 445
Dichiarare una lista vuota e permettere all’utente di digitare 7 numeri (interi o reali) da inserire nella lista. Alla fine dell’inserimento visualizzare la lista così ottenuta. Visualizzare successivamente il valore più piccolo e il valore più grande inseriti nella lista.
Esercizio 446
Permettere all’utente di inserire numeri interi finché non digita ZERO. Inserire man mano i numeri in una lista (lo zero non va inserito). Alla fine dell’inserimento visualizzare la lista così ottenuta. Visualizzare inoltre il numero di elementi presenti nella lista e la somma dei numeri della stessa.
Esercizio 447
Dichiarare una lista di numeri interi e visualizzare un istogramma orizzontale di asterischi della lista stessa. Ad esempio, data la lista [ 3 , 7 , 5 ] visualizzare:
***
*******
*****
Esercizio 448
Dichiarare le due liste vuote “listaParole” e “listaLunghezzaParole”. Permettere all’utente di inserire 10 parole, da memorizzare nella prima lista e calcolare successivamente la lunghezza di ogni parola per riempire la seconda lista. Infine visualizzare entrambe le liste, meglio se con una serie di frasi tipo:
la parola `listaParole[num]` è lunga `listaLunghezzaParole[num]` caratteri.
Esercizio 449
Dichiarare una lista vuota e fare inserire all’utente un intero positivo (da verificare) che rappresenterà il numero di elementi da inserire nella lista. Permettere successivamente all’utente di inserire quella quantità di numeri interi. Visualizzare la lista così ottenuta. Procedere infine a visualizzare le posizioni della lista in cui si trovano dei numeri pari.
Esercizio 450
Data una lista di 20 interi, da riempire a piacere tramite codice:
- visualizzare la lista completa
- visualizzare la somma di tutti gli elementi che sono multipli di 3
- visualizzare la somma di tutti gli elementi che sono contemporaneamente multipli di 2 e di 3.
- contare gli elementi pari.
- visualizzare gli elementi in posizione pari.
- dato un numero intero a, visualizzare tutti gli elementi nelle posizioni multiple del numero.
- calcolare la somma e la media dei valori contenuti nella lista
Esercizio 451
Dichiarare una lista vuota e permettere all’utente di inserirvi 5 numeri reali. Visualizzare la lista così ottenuta. Successivamente ordinare la lista in senso crescente e visualizzarla ordinata.
Rovesciare la lista e visualizzarla così ordinata in senso decrescente.
Esercizio 452
Dichiarare una lista vuota e permettere all’utente di inserirvi 5 numeri reali. Visualizzare la lista così ottenuta. Procedere ad una copia della lista in una nuova lista chiamata listaOrdinata e procedere al suo ordinamento in senso crescente.
Procedere ad una copia della lista originale in una terza lista chiamata listaDecrescente e procedere al suo ordinamento in senso decrescente.
Visualizzare le 3 liste così ottenute.
Esercizio 453
Scrivere un programma Python che inizializza una lista con i primi 100 numeri primi.
Esercizi sulle sequenze
Gli esercizi qui presenti coinvolgono in maniera più o meno casuale tutti i tipi sequenza e concludono idealmente lo studio delle stesse: riuscire in questi significa essere pronti per la verifica su questi argomenti (molti di questi esercizi sono tratti dalle verifiche degli scorsi anni).
Warning
Attenzione alle IA
Capisco la tentazione di aiutarsi nella risoluzione tramite le IA. Ricordate però che sarete valutati sulla comprensione di quanto avete consegnato. Come hai svolto un certo esercizio e perché lo hai svolto in quel modo…
Buon lavoro!
Tip
Numeri casuali
Spesso ritorna utile (per gli esercizi) riempire una lista con una serie di numeri casuali tramite codice, in modo da poter evitare di fare l’inserimento a mano.
# questo codice riempe una lista vuota con 100 elementi interi, ognuno compreso fra 1 e 50 # serve per generare i numeri "casuali" import random # la lista da riempire lista = [] for n in range(100): # random.randint(A,B) genera numeri "casuali" fra A e B (entrambi gli estremi inclusi!!!) numero = random.randint(1,50) lista.append(numero) # visualizzo il risultato. Già fatto!!! print(lista)
Più avanti approfondiremo questi concetti! Per adesso… usateli per comodità
Esercizio 481
Creare una lista vuota e riempirla con 100 interi casuali compresi fra 1 e 50. Visualizzare la lista con 100 elementi. Procedere poi a rimuovere tutti i valori duplicati dalla lista.
Visualizzare la lista con tutti valori unici (ovvero senza elementi duplicati).
Esercizio 482
Chiedere all’utente un numero intero compreso fra 1 e 100 (da verificare) e procedere alla sua visualizzazione in lettere.
Ad esempio, dato 132 si fa ripetere l’inserimento per un numero fra 1 e 100. Dato 75, si visualizzi la scritta “settantacinque”.
Esercizio 483
Dichiarare una tupla con i nomi delle dita delle mani e una con i nomi delle dita dei piedi (li sai??). Poi visualizzare entrambe in parallelo. Una cosa tipo:
| DITO | MANO | PIEDE |
|---|---|---|
| Primo | pollice | alluce |
| Secondo | indice | illice |
| … | … | … |
Esercizio 484
Dichiarare una lista vuota e riempirla con 20 numeri casuali (libreria random) compresi fra 1 e 100. Visualizzare la lista così ottenuta, ordinarla in senso crescente poi visualizzarla. Ordinarla in senso decrescente poi visualizzarla. Contare il numero di elementi pari presenti nella lista e visualizzarlo.
Esercizio 485
Partendo da un numero intero N, compreso fra 1 e 10, a scelta dell’utente, riempire una lista di 100 interi tali che:
- il primo numero della lista sia N stesso
- se il numero è pari, il successivo sia uguale al doppio del precedente + 1
- se il numero è dispari, il successivo sia uguale a 3 meno il precedente.
Ad esempio, dato N = 6, si comincia con
6 , 13 , -10 , -19 , 22 ...
Visualizzare la lista ottenuta.
Dalla lista precedente, creare 2 liste contenenti rispettivamente i numeri pari e i numeri dispari della stessa.
Nella lista dei numeri dispari:
- Rendere tutti i numeri positivi, cambiandoli di segno se necessario.
- Ordinare la lista dei dispari positivi in senso decrescente.
Nella lista dei numeri pari:
- Rendere tutti i numeri negativi, cambiandoli di segno se necessario.
- Ordinare la lista dei pari negativi in senso decrescente.
Esercizio 486
Dato un carattere da parte dell’utente, visualizzare se esso è una
lettera, una cifra o uno spazio (consulta: help(str.isspace), help(str.isdigit), help(str.isalpha) ).
Esercizio 487
Chiedere all’utente di inserire una stringa lunga a piacere. Visualizzare la stringa e il numero di caratteri che la compone.
Ad esempio, data la stringa “cane”, visualizzerà una frase tipo:
"la stringa cane è lunga 4 caratteri".
Definire la lista vocali, inizialmente vuota. Riempire la lista con tutte le vocali presenti nella stringa. Ad esempio, con la stringa “cane”, la lista diventerà
vocali = ["a","e"]
Esercizio 488
Chiedere all’utente di inserire una stringa qualsiasi, visualizzarla e poi contare il numero di consonanti presenti nella stringa.
Successivamente visualizzare la stringa in “farfallese”, ovvero ogni volta che si incontra una vocale si aggiunge una f e si ripete la vocale:
“ciao” diventa “cifiafaofo”
Esercizio 489
Chiedere all’utente di inserire una stringa di esattamente 6 caratteri (da verificare).
Visualizzare la stringa di 6 caratteri.
Successivamente inserire ogni carattere della stringa come elemento di una tupla di 6 caratteri. Visualizzare la tupla così generata, con gli elementi mostrati uno per riga.
Esercizio 490
Chiedere all’utente di inserire 2 caratteri minuscoli (verificare ogni volta che sia un unico carattere minuscolo) e un numero intero N compreso fra 0 e 9 (verificare).
Dichiarare una lista inizialmente vuota e procedere ad inserire in essa tutte le coppie create dall’unione dei due caratteri alternati e dei numeri fino ad N.
Ad esempio, siano i caratteri inseriti ‘a’ e ‘g’ ed N = 3. Allora la lista sarà così determinata:
lista = ["a0", "g0", "a1", "g1", "a2", "g2", "a3", g3"]
Visualizzare la lista ottenuta e verificare che len (lista) == 2 * (N + 1).
Esercizio 491 (password checker)
Chiedere all’utente di inserire una stringa che rispetti i requisiti di sicurezza minimi per una password:
- lunga almeno 8 caratteri
- contenente una lettera maiuscola
- contenente una lettera minuscola
- contenente un numero
Richiedere all’utente di inserire una stringa finché essa non rispetta tutti i requisiti richiesti.
Esercizio 492 (password generator)
Scrivere un programma che genera automaticamente una password casuale che rispetta però le seguenti condizioni:
- lunga almeno 8 caratteri
- contenente una lettera maiuscola
- contenente una lettera minuscola
- contenente un numero
Visualizzare la password generata automaticamente.
Esercizio 493 (da lista a stringa)
Generare una lista di caratteri minuscoli casuali. Il numero di caratteri da generare deve essere anch’esso casuale ma compreso fra 4 e 12. Visualizzare la lista così generata.
Provvedere a formare una stringa composta dai caratteri della lista di caratteri e visualizzarla.
Ad esempio, generata la lista di 4 caratteri [ "f", "t", "h" ,"o" ] generare la stringa “ftho”.
Esercizio 494 (da stringa a lista)
Chiedere all’utente di inserire una stringa qualsiasi. Con questa generare la lista dei caratteri della stringa inserita e visualizzarla.
Ad esempio inserita la stringa “cane”, va generata la lista ["c", "a", "n", "e"].
Esercizio 495
Chiedere all’utente di inserire una frase qualsiasi, lunga a piacere. Visualizzare la stringa inserita. Successivamente procedere a riempire una lista, inizialmente vuota, con le iniziali di ognuna delle parole della frase. Ad esempio, inserita la seguente stringa:
stringa = "Rosso di sera bel tempo si spera"
la lista diventa:
lista = ["R", "d", "s", "b", "t", "s", "s"]
Funzioni
Il termine “funzione” viene utilizzato in programmazione con un significato analogo a quello della matematica: una funzione è una procedura che può ricevere valori come argomenti e restituire un unico valore come risultato. Nel linguaggio comune si dice anche che una funzione ritorna un valore.
La sintassi generale per le funzioni in Python è così strutturata:
def nome_funzione ( parametri: tipo ) -> tipo_di_ritorno :
# blocco di codice che implementa la funzione
# codice...
# la funzione ritorna un valore con l'istruzione return
# return valore (del tipo_di_ritorno)
Come al solito, cominciamo con un semplice esempio per capire il funzionamento:
def isPositive ( number:float ) -> bool:
if number > 0 :
return True
return False
numero = float(input("Inserisci un numero: "))
if isPositive(numero):
print("Hai inserito un numero positivo")
else:
print("NON Hai inserito un numero positivo")
Alcune considerazioni banali:
- la funzione si chiama “isPositive”
- la dichiarazione della funzione inizia con la clausola
def - eventuali parametri della funzione vanno fra parentesi tonde
- questa funzione prende un parametro di tipo float e ritorna un valore booleano
- PRIMA la funzione si dichiara (una volta), POI si può usare (quante volte ti pare)
Altro esempio banale:
def somma (addendo1: float , addendo2: float) -> float:
return addendo1 + addendo2
print("27 + 35 = " , somma(27,35) )
print("12 + 5 = " , somma(12,5) )
Questo solo per mettere in evidenza che, se ci sono più parametri, essi vanno elencati fra virgole dentro le parentesi tonde.
Le funzioni in sé non aggiungono nessun nuovo potere al linguaggio che le utilizza, se non una nuova sconvolgente comodità: quella di poter riorganizzare e riutilizzare con semplicità il codice!
Le funzioni infatti hanno una definizione (quella che… comincia con def!!!), utile appunto a definire il suo nome, i parametri di cui ha bisogno e il suo comportamento e poi possono essere utilizzate un’infinità di volte, semplicemente richiamando il loro nome e fornendo i parametri necessari! Quante volte fino ad oggi avete usato la funzione print()? Qualcuno si è chiesto per caso come faccia a scrivere l’argomento sul monitor? Forse, ma non più di tanto. Tutti si sono semplicemente preoccupati di capire cosa la funzione print() faccia… e poi l’hanno utilizzata. E riutilizzata. E…
Dopo aver visto le funzioni, all’interno di questo capitolo, studieremo una modalità che ci permetterà di utilizzarle ancora meglio! I moduli (l’argomento successivo) non sono altro che collezioni di funzioni divise per argomento (matematiche, statistiche, grafiche, data e ora, ecc…) già pronte all’uso! Una pacchia :)
Per adesso però, cerchiamo di prendere mano con la definizione e l’utilizzo delle funzioni. Vediamo ancora qualche piccola cosa e poi divertiamoci con una valanga di esercizi da svolgere!!!
Parametri delle funzioni
I parametri di una funzione sono quelle variabili indicate tra parentesi nella sua dichiarazione e che prendono i valori che vengono inseriti in essa al momento della sua chiamata.
Capisco lo sconcerto… riprovo con un esempio:
# la variabile "nome" è il parametro (di tipo str) della funzione saluta()
def saluta (nome: str) -> str:
return "ciao " + nome
Quando poi la funzione viene chiamata i parametri prendono dei valori:
# il valore "ciccio" sarà inserito nel parametro "nome"
# della funzione "saluta()"
print( saluta("ciccio") )
scrive
'ciao ciccio'
Se la funzione prende più di un parametro, Python fornisce 2 possibilità:
- l’inserimento ordinato dei valori
- l’inserimento nominale dei valori
Partiamo come al solito definendo una funzione di prova e verificando cosa succede:
# la funzione ritorna parola ripetuta per un certo numero di volte
# (con uno spazio di separazione in mezzo, ma non alla fine):
# ripeti("ciao", 3) ritorna "ciao ciao ciao"
def ripeti(parola:str , numero:int)->str :
res = (parola + " " ) * (numero - 1) + parola
return res
Nella chiamata con inserimento ordinato dei valori basta inserire i dati nell’ordine della definizione: prima la parola e poi il numero:
print( ripeti ("ciao", 4) ) # scrive "ciao ciao ciao ciao"
mentre se si inseriscono i dati nell’ordine errato si va incontro ad un errore:
print( ripeti (3, "ciao") ) # ERRORE!!!
Se lo si desidera è anche possibile procedere ad un inserimento nominale, indicando cioè il nome della variabile in cui inserire il valore: in questo caso, l’ordine di inserimento non conta.
print( ripeti (parola = "ciao" , numero = 3) ) # ciao ciao ciao
print( ripeti (numero = 3 , parola = "ciao") ) # ciao ciao ciao
È inoltre possibile passare alle funzioni alcuni valori di default: questi vengono utilizzati se la funzione viene chiamata senza i valori da passare ai parametri o ignorati in presenza di essi.
# definizione della funzione `saluta` con valore di default "ciccio"
# per il parametro nome
def saluta(nome = "ciccio"):
return "ciao " + nome
Allora è possibile scrivere in fase di chiamata:
saluta() # ritorna la stringa "ciao ciccio"
saluta("pippo") # ritorna la stringa "ciao pippo"
Spero sia chiaro, a me perlomeno sembra così.
Documentare le funzioni
Se ricordate la struttura di base di una funzione, proveremo adesso ad aggiungere una piccola parte: la documentazione!
def nome_funzione ( parametri: tipo ) -> tipo_di_ritorno :
""" documentazione di nome_funzione in formato doc_string """
# blocco di codice che implementa la funzione
# return valore del tipo_di_ritorno
La documentazione di una funzione va strutturata nel modo seguente:
def nome_funzione ( par1: tipo1, par2: tipo2 ) -> tipo_di_ritorno :
""" Una breve frase che descrive COSA la funzione fa (NON come lo fa).
Se la funzione ha più di un parametro, spiegare bene quali informazioni
servono per eseguirla.
Alcuni esempi di utilizzo (possibilmente in formato doctest... ci arriviamo)
"""
Facciamo una prova, implementando la funzione trova(carattere, stringa) che ritorna True se il carattere è presente nella stringa,
False altrimenti.
def trova(carattere:str, stringa:str) -> bool:
"""
Dice se carattere è presente o meno nella stringa.
Ad esempio:
>>> trova('c', 'cane')
True
>>> trova('c', 'gatto')
False
"""
if carattere in stringa:
return True
return False
Come vedete, il formato doctest prevede di scrivere gli esempi come se eseguiste la funzione a mano nel prompt Python sotto. Poi il modulo
fa la sua magia ed esegue effettivamente quel codice e verifica che il risultato sia lo stesso di quello lì scritto.
Ne riparliamo nel prossimo capitolo.
Per adesso tutto qui!
Adesso ci vuole solo una montagna di esercizi per assicurarci di aver capito tutto bene.
Per fortuna sono qui sotto ☺️
Esercizi
Caution
NO IA almeno fino al 510!
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 501
Scrivere una funzione che calcola l’area di un rettangolo e una che ne
calcola il perimetro, date la base e l’altezza.
Chiedere all’utente di inserire i valori per base e altezza e calcolare area e perimetro del rettangolo, utilizzando le
funzioni implementate.
Esercizio 502
Scrivere una funzione che calcola l’area di un cerchio e una che
calcola la sua circonferenza. Entrambe richiedono (ovviamente) come
parametro il raggio del cerchio.
Chiedere all’utente di inserire un valore per il raggio e procedere a visualizzare area e circonferenza del cerchio relativo, calcolate utilizzando le
funzioni implementate.
Esercizio 503: triangoli rettangoli
Scrivere 3 funzioni relative ai triangoli rettangoli. Tutte e tre prendono come parametri la lunghezza reale dei cateti e calcolano rispettivamente:
- l’ipotenusa
- l’area
- il perimetro.
Utilizzare la funzione ipotenusa all’interno della funzione perimetro.
Esercizio 504: potenze
Dati un parametro per la base e uno (intero) per l’esponente, implementare la funzione potenza. Attenzione ai valori negativi degli esponenti.
(Per implementare questa funzione non potete usare la sintassi Python base**esp, ma dovete scrivere i calcoli da fare)
Esercizio 505: MCD
Scrivere una funzione che, dati due numeri interi positivi come parametri, ritorna il Massimo Comun Divisore fra questi.
Esercizio 506: mcm
Scrivere una funzione che, dati due numeri interi positivi come parametri, ritorna il minimo comune multiplo fra questi.
Esercizio 507: carattere
Scrivere una funzione chiamata valutaCarattere che prende una stringa come parametro. Se la stringa è formata da più di un carattere la funzione ritorna la scritta “errore”. Se è un solo carattere, ritorna la stringa “vocale”, “consonante”, “cifra” , “altro” a seconda del carattere passato.
Esercizio 508: inverti stringa
Scrivere una funzione che, data una stringa, ritorna la stringa rovesciata. Ad esempio fornito “ciao” come parametro, la funzione ritorna la stringa “oaic”.
Esercizio 509: palindroma
Scrivere una funzione che, data una stringa, dice se è palindroma oppure
no. Una stringa palindroma (ad esempio: “anna”) si legge uguale in
entrambi i versi. Ritornare True o False a seconda che la stringa sia
palindroma oppure no.
Esercizio 510: farfallese
Scrivere una funzione che, data una stringa, ritorna la stringa convertita in farfallese. In farfallese ogni vocale viene seguita da una f + la vocale ripetuta. Ad esempio la stringa “ciao” in farfallese diventa “cifiafaofo”.
Esercizio 511
Scrivere una funzione che prende come parametro una lista di numeri interi e ritorna una lista con solo i numeri dispari della lista precedente.
Ad esempio, dispari( [1,2,3,4,5,6,7] ) ritorna [1,3,5,7].
Esercizio 512
Scrivere una funzione che prende come parametro una lista e ritorna il numero di interi presenti come elementi della sequenza. (Pensa, pensa…)
Esercizio 513
Scrivere una funzione che prende come parametro una stringa qualsiasi e ritorna una stringa con le lettere minuscole (solo le lettere) ordinate alfabeticamente e ripetute una sola volta.
Ad esempio elencaLettere("casa") ritorna "acs". elencaLettere("Hai capito?") ritorna "achiopt".
Esercizio 514
Scrivere una funzione che prende come parametro due stringhe e che ritorna l’intersezione MAIUSCOLA delle loro lettere ordinate alfabeticamente, ovvero una lista con le lettere (scritte in maiuscolo) presenti in entrambe le stringhe.
Ad esempio intersezioneMaiuscola("casa", scuola") ritorna [ "A", "C", "S"]
Moduli Python
Il capitolo sui moduli Python è probabilmente il più importante di tutti! Python è diventato uno dei linguaggi più utilizzati al mondo proprio perché grazie ai suoi moduli può arrivare in maniera veloce a gestire qualunque aspetto della programmazione: basta scegliere il modulo giusto!!!
Per organizzare al meglio il lavoro, l’argomento Moduli Python è spalmato su 3 capitoli:
-
In questo capitolo vedremo come si crea un modulo e come si utilizza.
-
Nel capitolo Python Standard Library andremo a vedere i moduli predefiniti, ovvero quelli già disponibili senza fare null’altro in qualunque installazione Python: in questo caso il focus si sposta sull’utilizzo di moduli prodotti da altri, sulla consultazione della documentazione, sull’utilizzo dei moduli Python di base.
-
Nel capitolo PyPi ci occuperemo di reperire, installare e utilizzare alcuni dei moduli disponibili su Internet, precisamente nel
Python Package Index, per ottenere in un attimo uno o più moduli che implementano una funzionalità per noi interessante e/o utile per il software che vogliamo sviluppare.
Al lavoro!
Come accennato, in questo capitolo ci occuperemo di implementare un modulo scrivendo tutto il codice, investigando i concetti di base e procedendo ai test semplici delle funzionalità implementate.
Istruzione import
In Python la clausola import si utilizza per rendere disponibile il codice in un modulo in un altro. Questo concetto rende semplice l’organizzazione del codice in senso logico, sia dal punto di vista dei file che dal punto di vista dell’organizzazione globale del codice.
Dal punto di vista dei file, ogni modulo è ben organizzato perché contiene codice coerente: un ipotetico modulo numeri conterrà funzioni che lavorano coi numeri, un modulo statistiche contiene funzioni per il calcolo statistico, etc..
Dal punto di vista dell’organizzazione globale del codice, l’importazione di un modulo genererà un namespace che manterrà semplice la gestione del codice.
Capisco che il secondo punto di vista necessita di un ulteriore chiarimento: dal punto di vista intuitivo, noi abbiamo già usato il modulo random per le nostre liste numeriche. Allora
scriviamo:
import random
# per accedere alle funzioni del modulo random, dobbiamo scrivere modulo.funzione
intero = random.randint(1,100)
Questa organizzazione imposta dall’import (ovvero modulo.funzione) permette di capire chiaramente che la funzione randint deriva dal modulo random e non è stata definita all’interno
di questo modulo. Quando le importazioni saranno di più e i moduli scritti da noi più lunghi, questa cosa inizierà ad essere molto utile.
Inoltre il namespace indotto preserva le funzioni importate da un altro importante problema: se molte persone diverse scrivono codice potrebbe tranquillamente capitare che due funzioni
scritte da persone diverse, su moduli diversi, abbiano lo stesso nome. Il namespace impedisce confusione in questo caso.
Nomi dei moduli
Tutte le persone hanno un nome che le identifica (ad esempio il mio nome è Andrea), ma quando le persone parlano di se stesse usano la parola io (io mi chiamo Andrea).
E fin qui mi sembra tutto semplice.
Abbiamo detto finora (e nel dubbio, lo ripeto con chiarezza) che anche i moduli, ovvero i file Python con estensione .py, hanno un nome, che dipende dal nome del file stesso:
ad esempio, il file pippo.py definisce il modulo di nome pippo. Ogni volta che definite un modulo, Python inizializza alcune variabili speciali, fra cui la variabile __name__
che contiene il nome del modulo (fra un attimo faccio un esempio).
Così come le persone, parlando di se stesse, dicono io, i moduli, al loro interno non utilizzano il loro nome, ma utilizzano la parola __main__.
Cerchiamo di chiarire subito questo primo concetto, il resto verrà naturalmente di conseguenza:
# file pippo.py
# la variabile __name__ si riferisce al nome del modulo pippo
# essendo al suo interno, conterrà la parola "__main__"
print(__name__)
# file ciccio.py
# importo il modulo pippo, dell'esempio 1
# importando il modulo, andrò ad eseguirlo. la print sopra (eseguita all'esterno del modulo pippo)
# scriverà il suo nome: "pippo".
import pippo
# la variabile __name__ qui sotto si riferisce al nome del modulo ciccio
# essendo al suo interno, conterrà la parola "__main__"
print(__name__)
# la variabile __name__ qui sotto si riferisce al nome del modulo pippo
# NON essendo dentro al modulo pippo, conterrà il suo nome proprio, ovvero "pippo"
print(pippo.__name__)
Tutto qui ☺️
Testing && doctest
Dalle considerazioni fatte nei precedenti capitoli, vorrei farvi capire una tecnica molto utilizzata in ambito Python per l’esecuzione dei moduli e i test delle funzioni in essi contenute.
La strategia è questa:
- scrivo il modulo con la definizione delle funzioni che mi interessano
- faccio delle prove per assicurarmi che tutto… funzioni! (faccio i test!!!) in fondo al file, dentro un
if __name__ == "__main__" - Se eseguo direttamente il modulo con le funzioni, l’if mi permette di svolgere i test (e verificare che tutto funzioni correttamente)
- Se importo il modulo in un altro contesto, il suo nome NON sarà “main” e i test non saranno svolti, permettendo di importare solo le funzioni!
Da questa spiegazione, deriva questa modalità di scrittura dei moduli con funzioni:
# file prova.py
def molt(fatt1:float,fatt2:float) -> float:
"""
prende i valori nei parametri fatt1,fatt2 e ritorna il prodotto fatt1 * fatt2
Ad esempio:
molt(4,5) ritorna 20
"""
return fatt1 * fatt2
# questa parte NON sarà eseguita all'esterno del modulo "prova"
if __name__ == "__main__":
x = 5
y = 4
print("x:",x)
print("y:",y)
print( f"molt({x},{y}):{molt(x,y)}" )
# ... e così via...
Da ora in poi scriveremo così i nostri moduli e in questo modo faremo tutti gli esercizi che seguono. Questo ci permetterà di definire un modus operandi logico e professionale, che ci tornerà enormemente utile quando le cose diventeranno inevitabilmente più complicate.
Se invece di scrivere i test a mano vogliamo usare uno strumento semplice ed automatico, ecco il modulo doctest!!!!
Questo modulo esegue automaticamente gli esempi che trova nella documentazione come se ci fosse un interprete a farli!!
Infatti, per forzare doctest a generare degli esempi da verificare, occorrono due condizioni:
- i test devono essere scritti dentro la documentazione come se fossero eseguiti lì (lo vediamo fra un attimo)
- sotto alle funzioni va eseguita l’importazione e la chiamata al modulo
doctest, come indicato qui sotto.
if __name__ == "__main__":
import doctest
doctest.testmod(verbose=True)
oppure:
if __name__ == "__main__":
import doctest
doctest.testmod()
Per scrivere la documentazione in formato doctest, bisogna solo avere attenzione ad alcuni particolari:
Se la funzione ritorna una stringa, usare gli apici singoli:
>>> 'CASA'.lower()
'casa'
Se la funzione ritorna una tupla o una lista, separare bene i valori con uno spazio:
>>> [2, 3] * 4
[2, 3, 2, 3, 2, 3, 2, 3]
Vediamo un esercizio svolto per chiarire (spero) definitivamente i dubbi
def ripeti (parola:str, volte:int) -> str:
""" ritorna una stringa composta da *parola* ripetuta *volte* volte.
Quindi *parola* è la stringa da ripetere e *volte* è il numero di volte da ripeterla
Ad esempio:
>>> ripeti ("ciao", 3)
'ciaociaociao'
>>> ripeti ('a', 7)
'aaaaaaa'
"""
return parola * volte
if __name__ == "__main__":
import doctest
doctest.testmod(verbose = True)
Copiate su un file e provate per credere… Poi, sotto con gli esercizi!
Esercizi
Ognuno dei seguenti esercizi deve essere implementato in un unico file, nominato con il nome indicato nell’intestazione dell’esercizio, completo di documentazione e di (almeno) 3 casi di test per ognuna delle funzioni implementate!!!
Warning
Attenzione alle IA
Capisco la tentazione di aiutarsi nella risoluzione tramite le IA. Ricordate però che sarete valutati sulla comprensione di quanto avete consegnato. Come hai svolto un certo esercizio e perché lo hai svolto in quel modo…
Esercizio 541: modulo “SequenzeNumeriche”
minoriDiUnElemento
Creare una funzione che, data una sequenza numerica (tupla o lista) e un
numero qualsiasi, conta quanti numeri nella sequenza sono minori del
numero.
sommaElementi
Creare una funzione che, data una sequenza numerica (tupla o lista),
restituisce la somma dei numeri della sequenza.
mediaElementi
Creare una funzione che, data una sequenza numerica (tupla o lista),
restituisce la media aritmetica dei numeri della sequenza.
Esercizio 542: modulo “ManipolazioneStringhe”
lunghezzaStringa
La funzione prende una stringa come parametro e ritorna il numero di
caratteri di cui è composta (la sua lunghezza)
contaLettera
La funzione prende una stringa e una lettera come parametro e ritorna il
numero di volte in cui la lettera è presente all’interno della stringa
tutteMaiuscole
La funzione prende una stringa come parametro e ritorna la stringa
trasformata in maiuscolo
tutteMinuscole
La funzione prende una stringa come parametro e ritorna la stringa
trasformata in minuscolo
invertiMaiuscoleMinuscole
La funzione prende una stringa come parametro e ritorna la stringa con
maiuscole e minuscole invertite.
inizialiMaiuscole
La funzione prende una stringa come parametro e ritorna la stringa
trasformata in minuscolo con le iniziali di ogni parola maiuscole
Esercizio 543: modulo “PianoCartesiano”
quadrante
La funzione prende due numeri x, y che rappresentano le coordinate del
punto P nel piano cartesiano e restituisce un numero corrispondente al
quadrante nel quale esso si trova, ovvero un numero fra 1 e 4.
Restituisce 0 nel caso che il punto sia su uno degli assi cartesiani.
distanzaOrigine
La funzione prende due numeri x, y che rappresentano le coordinate del
punto P nel piano cartesiano e restituisce la distanza del punto P
dall’origine degli assi, ovvero dal punto O di coordinate (0,0).
distanzaFraDuePunti
La funzione prende quattro numeri xP, yP, xQ, yQ che rappresentano le
coordinate dei punto P e Q nel piano cartesiano e restituisce la
distanza fra loro.
Esercizio 544: modulo “CifreNumeriche”
unità
La funzione prende un numero come parametro e restituisce la cifra delle
unità. Ad esempio unita(23) restituisce 3, unita(8174.56) restituisce 4.
decine
La funzione prende un numero come parametro e restituisce la cifra delle
decine. Ad esempio decine(23) restituisce 2, decine(8174.56) restituisce 7.
centinaia
La funzione prende un numero come parametro e restituisce la cifra delle
centinaia. Ad esempio centinaia(23) restituisce 0, centinaia(8174.56)
restituisce 1.
migliaia
La funzione prende un numero come parametro e restituisce la cifra delle
migliaia. Ad esempio migliaia(23) restituisce 0, migliaia(8174.56)
restituisce 8.
decimi
La funzione prende un numero come parametro e restituisce la cifra dei
decimi. Ad esempio decimi(23) restituisce 0, decimi(8174.56) restituisce
5.
centesimi
La funzione prende un numero come parametro e restituisce la cifra dei
centesimi. Ad esempio centesimi(23) restituisce 0, centesimi(8174.56)
restituisce 6.
Esercizio 545: modulo “FunzioniNumeriche”
sommaDivisori
La funzione prende un numero intero come parametro e restituisce la
somma dei suoi divisori propri (ovvero dei divisori minori del numero
stesso)
listaDivisori
La funzione prende un numero intero come parametro e restituisce la
lista dei suoi divisori propri (ovvero dei divisori minori del numero
stesso)
isPrime
La funzione prende un numero intero come parametro e restituisce True se
il numero è primo, false altrimenti. (sugg: se un numero è primo il suo
unico divisore proprio è 1…)
perfetto
La funzione prende un numero intero come parametro e restituisce True se
il numero è perfetto, False altrimenti (un numero si dice perfetto se e
solo se è uguale alla somma dei suoi divisori propri)
nthPrime
La funzione prende un numero intero come parametro e restituisce
l’ennesimo numero primo. Ad esempio dato 3, la funzione restituisce 5
perché, essendo i numeri primi 2, 3, 5, 7, 11, etc… 5 è il terzo
numero primo.
semiPrimo
La funzione prende un numero intero come parametro e restituisce True se
il numero è semiprimo, False altrimenti. Un numero si dice semiprimo se
è esprimibile come prodotto di due numeri primi. Ad esempio 6 = 2 per 3 è
semiprimo, 7 = 7 per 1 non è semiprimo, infatti 1 non è primo.
Python Standard Library
Ovviamente l’utilità dei moduli non è solo organizzativa, ma… storica!!! Immaginate se qualcuno avesse raccolto tutte le funzioni più “fighe” scritte in Python e le avesse catalogate in gruppi omogenei; ad esempio le funzioni aritmetiche, le funzioni per le stringhe, le funzioni per data e ora e così via.
Beh… qualcuno lo ha fatto! Ed ha creato la Python Standard Library.
Essa non è nient’altro che una collezione di moduli, inclusi per semplicità in qualunque installazione Python,
che quindi possiamo utilizzare semplicemente scrivendo import nomeModulo.
Fighissimo!
Elenco qui alcuni fra i moduli più comuni, indicando il nome e una descrizione sommaria:
random: Generazioni di numeri “pseudo-casuali”math: Operazioni matematiche comunitime: Gestione Tempodatetime: Gestione Data e Orapathlib: Accesso a file e directoriescsv: Lettura e Scrittura su file csv
Se questi elencati non vi bastano e siete curiosi su quanti ce ne siano effettivamente, provate a consultare la documentazione ufficiale.
Ancora non vi basta? Bene! Allora sappiate che ci sono altre migliaia di moduli pieni zeppi di funzioni utili per gli incarichi più disparati, già catalogati e liberi di essere utilizzati da chiunque! La differenza con i precedenti è che questi non fanno parte della Python Standard Library, ma sono elencati semplicemente nel Python Package Index.
Ma questa è la storia del prossimo capitolo (Moduli PyPi). Per adesso, guardiamo qualcuno dei moduli standard!
Tip
La documentazione ufficiale (in inglese) di ogni modulo è inserita nel modulo stesso: per ottenerla, dato il modulo “NomeModulo”, basta scrivere nell’interprete:
>>> import NomeModulo >>> dir(NomeModulo) >>> help(NomeModulo.FunzioneInteressante)Prova con uno dei moduli elencati sopra!!!
random
Il modulo random serve per operazioni che richiedono una certa “casualità”, anche se il termine corretto sarebbe “pseudocasualità”.
Vediamo una carrellata delle funzioni pià interessanti del modulo. Per tutte le altre… ormai dovreste aver capito!!!
Funzione randint
Dato un intervallo (con entrambi gli estremi inclusi) seleziona un numero pseudocasuale al suo interno
import random
# estrae un numero pseudocasuale fra 1 e 90
n = random.randint(1, 90)
Funzione choice
Applicata ad una sequenza generica ritorna un elemento casuale della stessa (utile per le estrazioni)
import random
lista = [ 1, 2, 3, 4, 5]
numero = random.choice(lista) # scegle un elemento della lista (a caso)
print(numero) # scrive.... (boh, ad esempio)... 4
Funzione shuffle
funziona solo con le liste e serve per mescolare la lista passata come parametro. Ovviamente non ritorna valori.
import random
lista = [ 1, 2, 3, 4, 5]
random.shuffle(lista) # mischia la lista
print(lista) # ad esempio: [4, 3, 1, 5, 2]
Non è affatto difficile! Provate a fare i seguenti esercizi aiutandovi anche con la documentazione integrata.
Esercizio 601 (randint)
Riempire una lista con 5 numeri casuali fra 1 e 90 diversi fra loro. Pronta la cartella della tombola!!!
Esercizio 602 (randint)
Generare 5 numeri casuali fra 0 e 1 con 2 cifre esatte dopo la virgola (sugg: genera interi fra 0 e 100, dividi per…)
Esercizio 603 (choice)
Dichiarare una tupla contenenti l’elenco dei cognomi della classe ed estrarne uno con choice. Chi esce sarà interrogato!!!
Esercizio 604 (choice)
Creare una lista contenente i numeri da 1 a 10 ed estrarre un numero con choice. Provvedere successivamente ad eliminare il numero estratto dalla lista e visualizzare la stessa.
Esercizio 605 (shuffle)
Dichiarare una lista contenente l’elenco dei cognomi della classe (in ordine alfabetico) e mischiarla con shuffle. Visualizzare la lista mescolata: quello sarà l’ordine delle interrogazioni!!!
Esercizio 606 (shuffle)
Creare una lista contenente i numeri da 1 a 10 e mescolarla. Visualizzare la lista nel nuovo ordine ottenuto e successivamente riordinarla.
math
Il modulo math contiene tutte le funzioni matematiche utili per le operazioni su
esponenziali, logaritmi, trigonometria, etc…
import math
math.log(10) # logaritmo in base e di 10
math.cos(math.pi) # coseno di 90 gradi (pi greco radianti)
math.factorial(5) # fattoriale di 5, ovvero 5! = 1 * 2 * 3 * 4 * 5
math.sqrt(12) # radice quadrata di 12
math.gcd(345,678) # Massimo Comun Divisore
math.lcm(345,678) # Minimo Comune Multiplo (da Python 3.9)
ceil/floor/trunc # RTFM
Il modulo math si porta dietro anche una serie di costanti famose:
math.e # vale 2.718281828459045 (numero di Nepero)
math.inf # vale inf (più infinito)
math.nan # nan (not a number. PS: hai combinato qualcosa)
math.pi # vale 3.141592653589793 (pi greco)
Esercizio 611 (sqrt)
Scrivere una funzione che calcola le soluzioni dell’equazione di
secondo grado Ax^2 + Bx + C = 0.
In particolare la funzione prende come parametri i coefficienti A, B, C dell’equazione e restituisce la lista delle soluzioni reali della stessa.
La lista può contenere 0, 1, 2 soluzioni (anche coincidenti) a seconda dei casi.
Esercizio 612 (sqrt, floor)
Scrivere una funzione per calcolare la radice quadrata intera di un
numero. La radice quadrata intera di un numero x è il più grande intero
n tale che n * n <= x.
Provate a scrivere questa funzione con l’aiuto del modulo math (facile) e provate a scriverne una analoga senza (un po’ più complicato)
Esercizio 613 (gcd , lcm)
Scrivere una funzione che prende numeratore e denominatore di una frazione e ritorna una tupla di 2 valori contenenti numeratore e denominatore ridotti ai minimi termini. Ad esempio, la funzione riduci(15,6) ritorna la tupla (5,2).
Esercizio 614 (gcd , lcm)
In una piazza si trova il capolinea di tre linee di tram: A, B, e C. Il tram A parte ogni TOT_A minuti, il tram B ogni TOT_B minuti, il tram C ogni TOT_C minuti. Ogni quanti minuti i 3 tram si ritrovano al capolinea insieme? Implementare la funzione calcolaMinuti(TOT_A, TOT_B, TOT_C) che ritorna il numero di minuti che bisogna aspettare per avere di nuovo i 3 tram insieme al capolinea.
time
Il modulo time in Python gestisce l’interazione con il temporizzatore del computer in cui esso viene
eseguito ed è dunque in grado di effettuare operazioni collegate al tempo, come:
- aspettare per un lasso di tempo
- controllare l’orario corrente
- formattare l’orario in base alle sue componenti.
Vediamo alcuni esempi:
Funzione sleep
Serve per mettere in stand-by lo script Python per un certo numero di secondi (vale anche la virgola!!!)
import time
print("Lo scrive subito")
time.sleep(2.5)
print("Lo scrive dopo 2 secondi e mezzo!!")
Funzione time
Questa funzione ritorna il numero di secondi trascorsi da Epoch (noi diremmo… dalla notte dei tempi)!
In realtà, epoch non è altro che January 1, 1970, 00:00:00 UTC
import time
# il numero di secondi trascorsi da Epoch
seconds = time.time()
print("Seconds since epoch:", seconds)
Partendo da questa, abbiamo varie possibilità per la formattazione e la gestione di questo tempo:
*Funzioni time
In questo caso mi viene più semplice presentare alcune delle funzioni di tempo con qualche esempio:
import time
seconds = time.time()
# ctime:
# prende i secondi trascorsi e ritorna una stringa
local_time = time.ctime(seconds)
print("Local time:", local_time)
# Scrive: Local time: Wed Dec 28 08:16:19 2022
# localtime:
# prende i secondi trascorsi e ritorna struct_time, una tupla contenente le informazioni
# sul tempo: anno, mese, giorno, ore, minuti, secondi, nome del giorno, giorno dell'anno, bisestile
res = time.localtime(seconds)
print("result:", res)
# Scrive: result: time.struct_time(tm_year=2022, tm_mon=12, tm_mday=28, tm_hour=8, tm_min=8, tm_sec=53, tm_wday=2, tm_yday=362, tm_isdst=0)
# mktime:
# prende una tupla di tipo struct_time e ritorna il numero di secondi passati da Epoch per quella data
time_tuple = (2022, 12, 28, 8, 44, 4, 4, 362, 0)
seconds = time.mktime(time_tuple)
print(seconds)
# Scrive: 1672217044.0
# asctime:
# prende una tupla di tipo struct_time e ritorna una stringa che la rappresenta
res = time.asctime(time_tuple)
print(res)
# Scrive: Fri Dec 28 08:44:04 2022
Esercizio 621
Visualizza l’ora corrente in ORE:MINUTI:SECONDI (sugg: visualizza i giusti campi della tupla di tempo)
Esercizio 622
Chiedi all’utente un numero grande a piacere e calcola se è primo. Dopo aver risposto alla domanda visualizza quanto tempo il tuo programma ha impiegato a calcolarlo.
Esercizio 623
Calcola quanti secondi sono trascorsi dal 1 gennaio di quest’anno a oggi.
datetime
Il modulo datetime contiene le funzioni per la manipolazione di data e ora. Contiene 4
classi diverse a cui si accede con l’operatore PUNTO (.):
- la classe Date per la gestione della data, con giorno, mese ed anno;
- la classe Time per la gestione dell’orario, con ore, minuti, secondi e microsecondi;
- la classe DateTime per la gestione di Data e Ora: praticamente è la classe unione delle due precedenti;
- la classe TimeDelta per il calcolo delle differenze di tempo.
Vediamo alcuni esempi per chiarire i concetti espressi.
import datetime
# nell'ordine: anno, mese, giorno
scopertaAmerica = datetime.date(1492, 10, 12)
# nell'ordine: ore, minuti, secondi
oraDellaPausa = datetime.time(16, 40, 00)
# nell'ordine: anno, mese, giorno, ore, minuti, secondi
uomoSullaLuna = datetime.datetime(1969, 7, 20, 20, 18, 00)
# con le variabili nominali: giorni, secondi, microsecondi, millisecondi, minuti, ore, settimane.
# (i campi con valore 0 possono essere omessi)
fra3giornie2ore = datetime.timedelta(days=3, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=2, weeks=0)
Successivamente vedremo uno o due esempi che coinvolgono la classe TimeDelta.
Per visualizzare informazioni sulle classi in oggetto ricordatevi di utilizzare le funzioni dir() e help():
Una delle cose più semplici da fare con queste classi è ottenere ora e data correnti:
# Per la classe Date, possiamo ottenere il giorno odierno
oggi = datetime.date.today() # vale 2019-05-23
# La classe Time NON ha un metodo per l'ora corrente.
# Dobbiamo usare la classe DateTime
adesso = datetime.datetime.now() # vale 2019-05-23 19:26:03.478039
Una volta impostata una data o un’ora è possibile accedere ai valori dei campi che la caratterizzano:
print("Anno: ", adesso.year)
print("Mese: ", adesso.month)
print("Giorno: ", adesso.day)
print("Ore: ", adesso.hour)
print("Minuti: ", adesso.minute)
print("Secondi: ", adesso.second)
L’accesso ai valori dei campi che caratterizzano una variabile
di tipoDate,DateTime,Timeè sempre in sola lettura.
Questo significa che possiamo visualizzare i valori come fatto qui sopra, ma non possiamo utilizzare questi campi per modificare una data. Per essere completamente espliciti, non è possibile fare operazioni tipo le seguenti:
dataAcaso = datetime.datetime(2022, 6, 4, 13, 0, 0)
dataAcaso.year = 2020 # ERRORE
dataAcaso.hour = 12 # ERRORE. (capito? Non si può...)
Le date non sono fatte per essere modificate. Invece di cambiarne una… create un’altra variabile!
Per visualizzare date, ore o data con ora a piacimento, tutte e tre le classi mettono a disposizione la funzione strftime() che prende
l’oggetto Data (o Ora, o Data e Ora) e ritorna una stringa personalizzata, secondo i seguenti parametri (ho messo solo i
principali…):
| Parametro | Descrizione | Esempio |
|---|---|---|
| %a | Giorno della settimana (breve) | Wed |
| %A | Giorno della settimana | Wednesday |
| %w | Giorno della settimana come numero: 0 è Domenica | 3 |
| %d | Giorno del mese: 01-31 | 31 |
| %b | Nome del mese (breve) | Dec |
| %B | Nome del mese | December |
| %m | Mese come numero: 01-12 | 12 |
| %y | Anno (breve, 2 cifre) | 18 |
| %Y | Anno | 2018 |
| %H | Ore: 00-23 | 17 |
| %M | Minuti: 00-59 | 41 |
| %S | Secondi: 00-59 | 08 |
| %% | Per visualizzare % | % |
Qualche esempio è meglio di molte spiegazioni:
>>> adesso = datetime.datetime.now()
>>> print( adesso.strftime("%d-%m-%Y") )
23-05-2019
>>> print( adesso.strftime("Sono le %H:%M") )
Sono le 19:26
>>> print( adesso.strftime("%A") )
Thursday
È facile capire che in ogni tipologia di dato potete usare solo i parametri relativi ad esso: ovvero con un oggetto date, i parametri relativi ad ore, minuti o secondi non vanno bene, mentre con un oggetto time non vanno quelli sul giorno, il mese, etc… su un oggetto datetime funzionano tutti!!!
Differenze di tempo
Ok, domanda a bruciapelo. Da quanto tempo l’uomo è andato sulla Luna? Da quanti anni? Da quanti giorni??
Le differenze di tempo sono materia per la classe TimeDelta; un elemento
della classe stessa si ottiene facendo una differenza tra due elementi
di tipo DateTime o Date (non Time… attenti! E dello stesso tipo… attentissimi!! datetime - datetime, oppure date - date)
>>> print(adesso - uomoSullaLuna)
18205 days, 12:42:12.510824
Le differenze di tempo si fanno banalmente con il segno meno:
diff = adesso - uomoSullaLuna
print(diff.days) # scrive 18205 (int)
Ok… in giorni è quello. Ma… in anni? Settimane? Ore? Minuti? Secondi?
In questo caso occorre fare un po’ di calcoli: si fa la differenza fra 2 date (o 2 datetime, NON due time), si utilizza la
funzione total_seconds() che ritorna i secondi totali e poi si fa qualche divisione…
diff = adesso - uomoSullaLuna
secondiTotali = int(diff.total_seconds()) # int() per arrotondare...
minutiTotali = secondiTotali // 60
oreTotali = minutiTotali // 60
giorniTotali = oreTotali // 24
settimaneTotali = giorniTotali // 7
anniTotali = giorniTotali // 365 # più o meno...
Come la differenza fra due date (o due datetime) risulta in un oggetto timedelta, così la somma fra una date e un timedelta risulta una data:
oggi = datetime.date.today()
duegiorni = datetime.timedelta(days=2)
dopodomani = oggi + duegiorni
print(dopodomani, type(dopodomani)) # vedremo un oggetto datetime.date con la data di dopodomani
Prendiamo confidenza con il modulo datetime grazie ad un po’ di esercizi ☺️
Esercizio 631 (now)
Visualizzare la data e l’ora corrente, scrivendo la frase “Oggi è il GG/MM/AAAA e sono le ore HH:MM”
Esercizio 632 (…)
Data l’ora di adesso e inserito dall’utente l’ora di fine lezione calcolare il numero di minuti mancanti.
Esercizio 633 (tuple, weekday)
Chiedere all’utente di inserire la data di nascita e visualizzare il giorno della settimana in cui è nato.
Esercizio 634 (…)
Chiedere all’utente di inserire due numeri per giorno e mese dell’anno corrente e calcolare quante settimane sono passate dall’inizio dell’anno a quella data.
sys
Il modulo sys fornisce vari strumenti per interagire direttamente con l’interprete Python e
gestire il flusso di esecuzione del programma: accedere agli argomenti passati da linea di comando, controllare l’input/output standard
e ottenere informazioni sull’ambiente di runtime.
Vediamo le funzioni e gli argomenti principali con l’ausilio di alcuni esempi:
Variabile sys.argv: Argomenti da Linea di Comando
Lista contenente gli argomenti da linea di comando: non è mai vuota perché al posto 0
troviamo sempre il nome dello script.
import sys
print(f"Argomenti da linea di comando: {sys.argv}")
print(f"Nome dello script: {sys.argv[0]}")
if len(sys.argv) > 1:
print(f"Primo argomento: {sys.argv[1]}")
else:
print("Nessun argomento fornito.")
Salva questo script e prova ad eseguirlo con e senza argomenti.
Funzione sys.exit(): Uscire dal Programma
Termina l’esecuzione di un programma Python, potendo specificare anche un codice di uscita:
0 per un programma terminato correttamente; qualunque altro valore per un’uscita con errore.
import sys
if len(sys.argv) < 2:
print("Errore: nessun argomento fornito.")
sys.exit(1) # Uscita con codice di errore 1
print("Programma eseguito correttamente.")
Prova con o senza argomento
Variabile sys.path: Percorso di Ricerca dei Moduli
Lista che contiene i percorsi dove Python cerca i moduli quando viene eseguito un import.
Puoi modificare sys.path per aggiungere directory personalizzate e importare moduli da percorsi specifici.
import sys
# Stampa i percorsi di ricerca dei moduli
print("Percorsi di ricerca dei moduli:", sys.path)
# Aggiungi un nuovo percorso
sys.path.append("/percorso/del/mio/modulo")
In questo modo, puoi aggiungere directory personalizzate alla lista di ricerca dei moduli e rendere disponibili moduli esterni non installati nella directory predefinita (moooolto pericoloso).
Variabile sys.version: Versione di Python
Versione di Python in esecuzione. Utile per verificare la compatibilità di uno script con diverse versioni di Python. Ritorna una stringa
import sys
print(f"Versione di Python: {sys.version}")
Variabile sys.platform: Identificare il Sistema Operativo
una stringa che identifica il sistema operativo su cui Python è in esecuzione.
import sys
if sys.platform == "win32":
print("Stai eseguendo Python su Windows.")
elif sys.platform == "linux":
print("Stai eseguendo Python su Linux.")
elif sys.platform == "darwin":
print("Stai eseguendo Python su macOS.")
else:
print("Sistema operativo non riconosciuto.")
Funzione sys.getsizeof(): Verificare la dimensione di un oggetto
Restituisce la dimensione in byte di un oggetto in Python. Può essere utile per comprendere meglio l’impatto delle strutture dati nel programma.
import sys
lista = [1, 2, 3, 4, 5]
print(f"Dimensione della lista: {sys.getsizeof(lista)} byte")
Variabile sys.modules: Moduli Importati
Dizionario che contiene tutti i moduli attualmente importati nell’interprete Python.
Può essere usata per verificare quali moduli sono stati importati e per ricaricare moduli in modo dinamico.
import sys
# import random
if "random" in sys.modules:
print("Il modulo random è già importato.")
else:
print("non importato. Lo importo...")
import random
Commenta/Decommenta l’import sopra per vedere l’effetto.
os
Il modulo os fornisce una serie di funzioni per interagire con il sistema operativo,
come la gestione dei file e delle directory, e l’accesso alle variabili d’ambiente.
import os
print(f"Directory corrente: {os.getcwd()}")
os.chdir("/nuova/directory/di/lavoro/corrente")
print(f"Nuova directory corrente: {os.getcwd()}")
import os
os.mkdir("nuova_cartella") # Creare una singola directory
os.makedirs("cartella1/cartella2/cartella3") # Creare più directory nidificate
os.rmdir("cartella_vuota") # Rimuovere una singola directory vuota
os.removedirs("cartella1/cartella2") # Rimuovere directory nidificate
import os
os.system("notepad")
Moduli correlati a os
Il modulo shutil viene spesso suggerito in combinazione a os per operazioni di alto livello su file e directory. Ad esempio:
import shutil
# Copiare un file
shutil.copy("file1.txt", "file2.txt")
# Spostare un file
shutil.move("vecchio_file.txt", "nuova_posizione/vecchio_file.txt")
Il modulo subprocess è una alternativa (più avanzata) di os.system per eseguire comandi esterni e catturarne
l’output.
import subprocess
# FIXME: fai un esempio con Windows. Meglio ancora, uno che vale su tutti gli OS
risultato = subprocess.run(["ls", "-l"], capture_output=True, text=True)
print(risultato.stdout)
Python Package Index
I moduli della libreria standard sono facilissimi da utilizzare (anche perché sono già installati sul computer…) ma fanno operazioni… standard, niente di veramente eclatante!
Python in realtà mette a disposizione dei suoi utenti miriadi di moduli per i compiti più disparati: creare un codice a barre, calcolare la distanza fra 2 stelle, trasferire una canzone tramite bluetooth, ecc…
La cosa veramente incredibile (su Python) è che tutti questi moduli, sviluppati da chicchessia, sono raccolti in un unico repository: il Python Package index, PyPi. https://pypi.org.

Qui sopra vedete la schermata iniziale del sito. Come vedete sono disponibili oltre 275.000 moduli… Potete cercare praticamente quello che volete: io ho provato con “chicken”, “football”, “rock music” e ho avuto soddisfazione…
Certo, al nostro livello non siamo in grado ancora di usare qualsivoglia modulo (soprattutto perché siete ancora particolarmente allergici a leggere la documentazione), ma imparare a scaricarli e a installarli (fatto uno, fatto tutti) e poi ad usare qualcuno dei più semplici… si può!
Per installare un modulo di questi, non c’è bisogno di mettersi a cercarlo nel sito… basta usare Thonny!

Da lì digitate la stringa di ricerca (nell’esempio c’è la stringa wxpython) e cliccate sul pulsante di ricerca…

Trovato il pacchetto che vi serve, non serve altro che cliccare sul pulsante INSTALLA in basso e attendere :)

Se invece non avete (ancora) Thonny, aprite il prompt dei comandi Windows oppure il terminale Mac o Linux e digitate:
pip install -U nomeModulo
Quando ha finito… ve ne accorgerete.
Modulo pillow
Il modulo pillow è considerato il modulo standard per la manipolazione delle immagini in Python.
In realtà sarebbe un sostituto (built-in replacement) per la vera libreria di default per il trattamento delle
immagini: PIL, ovvero la Python Image Library.
Ma ormai è considerata lo standard per lavorare con le immagini.
Installate il modulo pillow e poi procedete ad esaminare e testare il seguente esempio, aggiungendo ogni volta
un pezzetto al codice.
# per compatibilità con PIL anche il modulo pillow si chiama PIL
from PIL import Image
# Per aprire e visualizzare una immagine
# l'immagine deve trovarsi nella stessa cartella dello script
img = Image.open("python.png")
img.show()
Alcune operazioni sparse sull’immagine:
rotatedImg = img.rotate(90)
rotatedImg.show()
resizedImg = img.resize((200,200))
resizedImg.show()
# (oppure verticalmente, mettendo Image.FLIP_TOP_BOTTOM)
flippedImg = img.transpose(Image.FLIP_LEFT_RIGHT)
flippedImg.show()
convertedImg = img.convert("L")
convertedImg.show()
Per ritagliare una immagine (crop) occorre utilizzare il metodo: Image.crop( (left, upper, right, lower) )
# ritagliamo l'immagine da (0,0) in alto a sx a (300,300) in basso a dx
croppedImage = img.crop( (0,0,300,300) )
Se invece volessimo salvare l’immagine modificata… (ad esempio quella in bianco e nero)
convertedImg.save("pythonBN.png")
Proviamo adesso alcune opzioni per sfocare l’immagine (blur)
# occorre importare anche l'oggetto ImageFilter
from PIL import Image, ImageFilter
img = Image.open("prova.jpg")
blurImage = img.filter(ImageFilter.BLUR) # blur generico
boxBlurImage = img.filter(ImageFilter.BoxBlur(20)) # blur radiale
gaussImage = img.filter(ImageFilter.GaussianBlur(20)) # blur gaussiano
Il metodo expand() può essere utilizzato per aggiungere bordi alle immagini, espandendo la loro dimensione senza intaccare la vista originale
from PIL import Image, ImageOps
image = Image.open("immagine.jpg")
expanded_image = ImageOps.expand(image, border=20, fill="red")
expanded_image.show()
Nuove Immagini e testo
In pillow i colori delle immagini vengono gestiti attraverso informazioni a 4 bytes con la specifica chiamata RGBA. Questa specifica è una
estensione della classica RGB a 3 bytes in cui la A finale, anch’essa rappresentata con 1 byte, indica l’alpha color, ovvero la trasparenza,
dove 0 indica la massima trasparenza (invisibile), mentre 255 indica nessuna trasparenza (opacità).
from PIL import Image
image = Image.new('RGBA', (600,400), (255,0,0,128) )
image.show()
Se si preferisce è possibile utilizzare la notazione esadecimale
image = Image.new('RGBA', (600,400), #FF000080 )
Immaginate di voler piazzare un testo al centro del rettangolo così creato
from PIL import Image, ImageDraw
image = Image.new('RGBA', (600,400), (255,0,0,128) )
text = "Ho capito tutto!"
font = ImageFont.truetype("arial.ttf", size=30)
text_position = (150, 185)
text_color = (0,255,255)
draw = ImageDraw.Draw(image)
draw.text(text_position, text, fill=text_color, font=font)
image.show()
Screenshot
Il modulo ImageGrab serve per fare gli screenshot del Desktop. Espone banalmente la funzione grab() che realizza il compito assegnatole.
from PIL import ImageGrab
shot = ImageGrab.grab()
shot.save('screenshot.png')
L’esempio sopra fa uno screenshot del Desktop e lo salva come file “screenshot.png” nella stessa cartella ove si trova lo script Python con il codice sopra.
Se leggete la documentazione della funzione grab saprete che è possibile fare lo screenshot anche ad una sola sezione dello schermo. La prova di questo ve la lascio come esercizio.
Esercizi
Volete fare, sapere di più? Leggete la documentazione! Ma per imparare ad usare qualcosa… occorre esercitarsi!!!
Esercizio 701
Data una immagine qualsiasi (ad esempio, la fantastica pippo.jpg, da mettere nella stessa cartella del seguente script) utilizzare il modulo pillow
per creare le immagini pippoAllaRovescia.jpg, contenente l’immagine iniziale rovesciata (ruotata di 180 gradi) e pippoBiancoNeroRovesciato.jpg contenente
l’immagine iniziale rovesciata e in scala di grigi
Esercizio 702
Data una immagine qualsiasi (ad esempio, la fantastica pippo.jpg, da mettere nella stessa cartella del seguente script) utilizzare il modulo pillow
per creare le immagini pippo_100.jpg, contenente l’immagine iniziale ridimensionata a 100 x 100 pixel e pippo_100_50.jpg contenente l’immagine precedente
ritagliata dall’alto per diventare 100 x 50 pixel.
Esercizio 703
Data un’immagine foto.jpg, creare un programma che applica un effetto “galleria d’arte” creando 4 versioni della stessa immagine con
filtri diversi (normale, bianco e nero, sfocata, contorni) e le salva come foto_normale.jpg, foto_bn.jpg, foto_blur.jpg e foto_contorni.jpg. Utilizzare il filtro ImageFilter.CONTOUR per l’ultima versione.
Esercizio 704
Creare un programma che prende un’immagine logo.png e genera una miniatura (thumbnail) di dimensione massima 200x200 pixel mantenendo le proporzioni originali dell’immagine. Salvare il risultato come logo_thumb.png.
Provate a documentarvi e a utilizzare il metodo thumbnail().
Esercizio 705
Data un’immagine panorama.jpg, creare un programma che la divide in 4 parti uguali (quadrante in alto a sinistra, in alto a destra, in basso a sinistra, in basso a destra) e salva ciascun quadrante come file separato: panorama_tl.jpg, panorama_tr.jpg, panorama_bl.jpg, panorama_br.jpg.
Esercizio 706
Creare un programma che prende due immagini della stessa dimensione (sfondo.jpg e overlay.png) e le combina sovrapponendo la seconda sulla prima con trasparenza del 50%. Salvare il risultato come composizione.jpg.
Provate a documentarvi e a utilizzare i metodi Image.blend() oppure Image.alpha_composite().
Esercizio 707
Fare uno screenshot dello schermo, ridimensionarlo a 800 x 600 pixel e modificarlo in scala di grigi. Salvare l’immagine ottenuta.
Esercizio 708
Una volta eseguito, il programma fornirà 5 secondi all’utente prima di fare lo screenshot, in modo tale da fornirgli il tempo di preparare lo schermo.
Salvare il file con nome screenshot_ANNO_MESE_GIORNO_ORE_MINUTI_SECONDI.jpg.
Esercizio 709
Creare un programma che fa 3 screenshot consecutivi dello schermo a distanza di 2 secondi l’uno dall’altro e li salva con nomi progressivi: screen_1.png, screen_2.png, screen_3.png.
Esercizio 710
Creare un programma che fa uno screenshot solo della parte centrale dello schermo (ad esempio, un’area di 800x600 pixel centrata) e la salva come screen_center.png. Utilizzare la funzione grab() con i parametri appropriati per specificare l’area.
Esercizio 711
Creare un programma che chiede all’utente quanti secondi aspettare prima di fare lo screenshot, attende il tempo specificato, fa lo screenshot, lo ruota di 90 gradi in senso orario e lo salva come screen_rotated.png.
Esercizio 712
Creare un “monitor della produttività”: il programma fa uno screenshot ogni 30 secondi per 5 volte, ridimensiona ogni immagine a 320x240 pixel e le salva con timestamp nel nome (ad esempio monitor_14-30-15.png). L’utente deve avere 3 secondi di avviso prima del primo screenshot.
Modulo matplotlib
Il modulo matplotlib serve per creare dei plot, ovvero dei grafici a
partire da funzioni algebriche nel piano e nello spazio.
Immaginate di voler disegnare il grafico della retta y = 2 x.
Scegliete alcuni punti per le ascisse x = [ 1 , 2 , 3 , 4 ]
Calcolate le ordinate corrispondenti y = [ 2 , 4 , 6 , 8 ]
Disegnate il piano cartesiano e vi ponete i punti calcolati: (1,2) (2,4) (3,6) (4,8)
Infine tirate una riga che passa per questi punti
Con matplotlib si possono fare cose del genere. Ci provo per la retta dell’esempio:
import matplotlib.pyplot as plt
x = [1, 2, 3, 4]
y = [2, 4, 6, 8]
plt.plot(x, y)
plt.title("Grafico della retta y = 2 x")
plt.xlabel("Asse X")
plt.ylabel("Asse Y")
plt.show()
Con questo codice ho generato la seguente immagine:

Ovviamente potrei disegnare anche dei pezzi segmentati:
import matplotlib.pyplot as plt
x = [0, 2, 4, 6]
y = [0, 2, 1, 3]
plt.plot(x, y) # per disegnare solo i punti, usa plot(x,y,"o")
plt.title("Punti a caso")
plt.show()

Per caratterizzare il grafico si potrebbe aggiungere una griglia:
# inserisci una di queste funzioni PRIMA di plt.show()
# così visualizzi TUTTA la griglia
plt.grid()
# così visualizzi SOLO le righe VERTICALI
plt.grid(axis="x")
# così visualizzi SOLO le righe ORIZZONTALI
plt.grid(axis="y")
Se vogliamo inserire più di un grafico, si potrebbe inserire una legenda
import matplotlib.pyplot as plt
x = []
f1 = []
f2 = []
for n in range(1,10):
x.append(n)
f1.append(2*n)
f2.append(n**2)
plt.plot( x , f1 , label="y=2x") # aggiungi un'etichetta
plt.plot( x , f2 , label="y=x**2")
plt.legend() # crea la legenda
plt.show()

Se invece di visualizzare l’immagine volete salvarla da qualche parte, invece di show(),
dovete usare il metodo savefig("nomeImmagine.estensione").
plt.savefig("immagine.jpg")
Facile ☺️
Si può utilizzare questa libreria anche per disegnare barre verticali od orizzontali, i celebri istogrammi:
import matplotlib.pyplot as plt
nomi = ["Asini", "Bradipi", "Cavalli", "Daini"]
numeri = [5, 8, 2, 4]
plt.bar(nomi,numeri) # per le barre orizzontali, usa barh()
plt.show()

Con quegli stessi dati posso disegnare anche un diagramma a torta:
plt.pie(numeri , labels = nomi)
Basta!
Se volete approfondire un po’, guardate il sito ufficiale: https://matplotlib.org/
Oltre alla documentazione (so che non la leggerete) ci sono molti esempi e tutorial…
Esercizi
Se proprio volete imparare… basta solo fare un po’ di esercizio 😉
Esercizio 721
Provate a realizzare una coppia di grafici secondo le seguenti specifiche: sulle ascisse ci vanno le date delle valutazioni e sull’ordinata i voti ottenuti. Il primo grafico rappresenta i voti di una materia a vostra scelta, mentre il secondo è la riga orizzontale fissa che rappresenta la sufficienza. Visualizzare entrambi nello stesso plot.
Esercizio 722
Crea un grafico che rappresenta la funzione y = 3x + 1 nell’intervallo da x = 0 a x = 10.
- Usa almeno 11 punti (da 0 a 10)
- Aggiungi un titolo al grafico
- Etichetta gli assi X e Y
- Visualizza il grafico
Esercizio 723
Hai rilevato le temperature (in gradi Celsius) durante una settimana di ottobre:
- Lunedì: 18°
- Martedì: 20°
- Mercoledì: 19°
- Giovedì: 22°
- Venerdì: 21°
- Sabato: 17°
- Domenica: 16°
Crea un grafico a barre che mostri le temperature dei diversi giorni.
- Usa
plt.bar()per creare le barre - Aggiungi un titolo significativo
- Etichetta correttamente gli assi
Esercizio 724
Hai ottenuto i seguenti voti durante il quadrimestre in Python:
6, 7, 7, 8, 7, 9, 8, 7
Crea un diagramma a torta che mostri la distribuzione percentuale dei voti (quanti 6, quanti 7, quanti 8, quanti 9 hai preso).
- Conta quante volte appare ogni voto
- Usa
plt.pie()per creare il diagramma - Aggiungi le etichette con i voti (6, 7, 8, 9)
Prova a ripetere la generazione del grafico con un elenco di 20 voti generati casualmente scelti fra 6,7,8,9.
Esercizio 725
Disegna nello stesso grafico due funzioni:
f(x) = x²(parabola)g(x) = 4x(retta)
nell’intervallo da x = 0 a x = 5.
- Usa almeno 20 punti per avere curve fluide
- Aggiungi una legenda che distingua le due funzioni
- Aggiungi una griglia per rendere il grafico più leggibile
- Usa colori diversi per le due linee (parametro color in plot())
- Salva il grafico come file “confronto.png” invece di visualizzarlo
Files
Le operazioni di I/O (input/output) da file e verso file sono operazioni native in Python, che come vedremo, si eseguono con un semplice comando (nativo, appunto) e che servono per interagire con un generico file presente nel filesystem (quindi sul nostro computer).
Il primo capitolo è dedicato espressamente a comprendere come questo meccanismo funzioni. Nel secondo capitolo, con il modulo pathlib si apprende
uno strumento utile alla manipolazione dei percorsi dei file (es: dove si trova la cartella dei download? Voglio creare una cartella sul Desktop di…)
e alla gestione dei file e delle cartelle.
Files Management
Per interagire con il filesystem del sistema in cui è in esecuzione uno
script, Python mette a disposizione due semplici funzioni predefinite: open() e close().
Banalmente, la prima permette di aprire un file e lavorarci dentro, mentre la seconda lo chiude.
Vediamo nel dettaglio cosa questo significhi:
file_object = open ( name, mode )
La funzione open ha due parametri principali, di cui solo il primo è obbligatorio:
il nome del file da aprire. Il secondo parametro indica la modalità di apertura: lettura, scrittura o aggiunta a fine file.
Elenchiamo le principali modalità:
| Modo | Descrizione |
|---|---|
| r | Lettura (read). Il file NON sarà modificato da alcuna operazione. Se il file non esiste l’apertura fallisce. Modo di default. |
| w | Scrittura (write). Un file esistente verrà sovrascritto, altrimenti ne verrà creato uno nuovo. |
| x | Creazione file (non lo so perché x…). Ritorna un errore se il file esiste già. Se funziona, apre il file in scrittura. |
| a | Aggiunta a fine file (append). Apre oil in scrittura, ma aggiungendo in fondo a ciò che esiste quello che viene scritto. Altrimenti è come write. |
Oltre ad aprire un file per leggerci o scriverci, bisogna anche sapere come farlo. Le modalità di gestione dei file sono solo due:
| Modo | Descrizione |
|---|---|
| t | Modalità testuale. In questa modalità i dati vengono gestiti byte a byte. Modo di default. |
| b | Modalità binaria. In questa modalità i dati vengono gestiti bit a bit. |
Da queste informazioni si desume che l’apertura generica di un file corrisponde ad una apertura in lettura in modalità testuale: qualsiasi altra operazione dovrà essere esplicitata. Per evitare errori, esplicitiamo sempre la modalità di apertura! Per quanto riguarda la gestione invece… qui tratteremo solo la gestione testuale :)
Vediamo alcuni semplici esempi:
file = open("ciao.txt", "w")
file.write("Ecco qua.\nCapito?")
file.close()
In questo primo esempio il file “ciao.txt” inizialmente non esiste. L’esecuzione del codice lo crea con dentro scritto il testo indicato. Dopo aver eseguito questo codice, vedrete nella cartella del file python, un file chiamato “ciao.txt” che contiene 2 righe… Nella stessa cartella creiamo un nuovo file sorgente con questo codice:
file = open("ciao.txt", "r")
contenuto = file.read()
file.close()
print(contenuto)
Che visualizzerà:
Ecco qua.
Capito?
Penso sia tutto abbastanza intuitivo da capire. Facciamo un esempio con la modalità append
file = open("ciao.txt", "a")
file.write("\nSperiamo di sì")
file.close()
Se aprite il file “ciao.txt” DOPO aver eseguito il codice sopra, vedrete che il testo è diventato di 3 righe:
Ecco qua.
Capito?
Speriamo di sì
Se avete osservato i 3 esempi proposti avrete notato che:
- la funzione
write()scrive su file la stringa esatta che gli viene fornita come parametro senza aggiungere spazi o andare a capo (come ad esempio fa la print()) - la funzione
read()copia su una stringa tutto il contenuto del file in una volta sola.
Se invece vogliamo leggere il file di testo riga per riga possiamo sfruttare il ciclo for abbinato al descrittore di file
per ottenere in ogni iterazione una riga del file stesso:
file = open("ciao.txt", "r")
for riga in file:
print(riga)
file.close()
Warning
Attenti ai doppi a capo!!!
La lettura in questo modo visualizzerà:
Ecco qua. Capito? Speriamo di sìinfatti ogni riga di un file finisce a capo così come
La corretta esecuzione di questo codice necessita di una pulizia della riga tramite la funzione strip().
file = open("ciao.txt", "r")
for riga in file:
print(riga.strip())
file.close()
Che (finalmente) visualizzerà:
Ecco qua.
Capito?
Speriamo di sì
Basta con gli esempi! Ma fra un attimo iniziamo con gli esercizi ;)
File Objects
Un file object non è nient’altro che una variabile a cui è stato assegnato l’abbinamento con un file tramite la funzione open(). Se ad esempio scrivo:
file = open( "file.txt", "w" )
allora f diventa un file object! Questo tipo di variabile presenta alcune caratteristiche che permettono al programmatore di investigare sullo stato della relazione fra la variabile (e quindi il programma) e il file. Le caratteristiche esposte sono:
| Caratteristica | Descrizione | Esempio |
|---|---|---|
| file.name | Contiene il nome del file | “file.txt” |
| file.mode | Contiene il modo di apertura del file | “w” |
| file.closed | Indica se il file è chiuso oppure no | False |
Queste possono essere utilizzate ad esempio per verificare se è possibile scrivere sul file oppure se il file è già stato chiuso.
if file.closed :
print("impossibile interagire con il file!")
else:
if file.mode == "w" or file.mode == "a":
file.write("ciao")
else:
print("file non aperto in scrittura!")
Spero sia abbastanza chiaro. Nel dubbio… lo sapete già! Funzioni dir() ed help() !!!
Esercizi sui files
Caution
NO IA almeno fino al 715
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 801 (parte 1)
Scrivere un programma che chiede all’utente di digitare il proprio nome e poi salva la stringa digitata sul file “stringa.txt”.
Esercizio 801 (parte 2)
Scrivere un altro programma che carica in lettura il file “stringa.txt” creato nell’esercizio precedente e ne visualizza il contenuto sullo schermo (dovrebbe visualizzare la stringa digitata prima).
Esercizio 802 (parte 1)
Scrivere un programma che chiede all’utente di digitare un numero intero e poi salva il numero digitato sul file “intero.txt”.
Tip
La lettura e scrittura da file funziona sempre e solo con le stringhe, quindi quando si lavora con interi bisogna ricordarsi di convertirli in stringhe quando si scrive su file e riconvertirli in interi quando si leggono da file.
Esercizio 802 (parte 2)
Scrivere un altro programma che carica in lettura il file “intero.txt” creato nell’esercizio precedente e salva il suo contenuto,
convertito in intero, su una variabile. Visualizzare la variabile e verificare il suo tipo con la funzione type().
Esercizio 803 (parte 1)
Scrivere un programma che chiede all’utente di digitare un numero reale e poi salva il numero digitato sul file “reale.txt”.
Esercizio 803 (parte 2)
Scrivere un programma che carica in lettura il file “reale.txt” creato nell’esercizio precedente e salva il suo contenuto, convertito in reale, su una variabile. Visualizzare la variabile e verificare il suo tipo con la funzione type().
Esercizio 804 (parte 1)
Creare una lista contenente i nomi di alcuni animali, visualizzarla sullo schermo e poi andare a copiarne il contenuto nel file “animali.txt” avendo cura di mettere
ogni animale in una nuova riga.
Eseguito il codice verificare che il file è effettivamente ben formato con un animale per riga.
Esercizio 804 (parte 2)
Aprire in lettura il file “animali.txt” creato nella parte 1 e leggere il contenuto riga per riga visualizzando ogni volta l’animale “estratto”.
Esercizio 804 (parte 3)
Aprire il file “animali.txt” in modalità “append” (aggiunta a fine file). Chiedere all’utente di inserire il nome di un animale e procedere all’inserimento nel file.
Ripetere l’operazione 2 o 3 volte, aggiungendo ogni volta l’animale inserito nel file.
Terminato l’inserimento, chiudere il file e ripetere il codice dell’esercizio precedente per visualizzarne il contenuto.
Esercizio 804 (parte 4)
Creare una lista, inizialmente vuota.
Aprire il file “animali.txt” in lettura e inserire ogni animale presente nel file come elemento (stringa) nella lista.
Visualizzare la lista così ottenuta (attenzione ai caratteri di a capo!!!“)
Esercizio 810
Creare a mano un file chiamato “parola.txt” nella stessa cartella del codice di questo esercizio, digitando al suo interno una parola qualsiasi.
Scrivere un programma per provare ad aprire il file in modalità “x” per verificare se è possibile andarlo a sovrascrivere.
Ripetere l’operazione in modalità “w” e infine controllare il contenuto del file: cosa contiene adesso???
Esercizio 811: area rettangolo
Creare a mano un file di testo, chiamato “lati.txt” salvando all’interno due numeri reali qualsiasi, uno per riga.
Implementare un software che legge dal file “lati.txt” due valori reali per la base e l’altezza di un rettangolo, visualizza i dati su schermo, calcola e visualizza l’area del rettangolo e salva il valore calcolato nel file “area.txt”.
Esercizio 812: secondi trascorsi
Creare a mano un file di testo chiamato “secondi.txt”, contenente al suo interno un numero intero positivo qualsiasi.
Implementare un software che legge dal file “secondi.txt” un valore intero che rappresenta i secondi trascorsi dalla mezzanotte di un dato giorno e che trasforma questo valore, visualizzandolo su schermo in ore:minuti:secondi. Ad esempio, se il valore caricato dal file “secondi.txt” fosse 4000 (uguale a 3600, 1 ora, + 360, 6 minuti, + 40), il programma dovrà visualizzare a video 01:06:40, aggiungendo opportunamente uno ZERO in caso di cifra unica.
I numeri vanno poi memorizzati nello stesso formato nel file “orario.txt”.
Esercizio 813: stringhe
Dichiarare una lista di 5 stringhe e procedere ad un salvataggio su file delle parole, una per riga.
Esercizio 814: media dei numeri
Creare a mano un file di testo chiamato “numeri.txt” salvando al suo interno 10 numeri interi, uno per riga.
Implementare un software che legge dal file “numeri.txt”, caricarne il contenuto su una lista, procedere al calcolo della media aritmetica dei numeri inseriti, visualizzare il risultato a video e salvarlo nel file “media.txt”.
Esercizio 815 (parte 1): ricerca elementi
Creare a mano un file di testo chiamato “sequenza.txt”, salvando al suo interno una certa quantità di numeri interi, uno per riga.
Implementare un software che legge dal file “sequenza.txt” i dati, li carica su una lista e li visualizza a video. Chiedere poi all’utente di inserire un numero intero
e procedere alla ricerca di questo nella sequenza caricata dal file. Visualizzare le posizioni ove questo numero si trovi.
Scrivere tutte le posizioni trovate riga per riga nel file “posizioni.txt”. Se il numero non viene mai trovato, si inserisca nel file la scritta maiuscola “NON TROVATO”.
Esercizio 815 (parte 2): ordinamenti
Scrivete un programma che legge dal file “sequenza.txt” una sequenza di interi, la ordina in modo crescente e la salva ordinata sul file “ordinati.txt” (uno per riga).
Esercizio 817: password
Scrivere un programma che chiede all’utente di inserire una password (una stringa qualsiasi) e la salva nel file “password.txt”.
Scrivere poi un secondo programma che chiede all’utente di inserire una password, legge il contenuto del file “password.txt” e verifica se la password inserita corrisponde a quella salvata. Visualizzare “Accesso consentito” o “Accesso negato” a seconda del risultato.
Esercizio 818: statistiche testo
Creare a mano un file di testo chiamato “testo.txt” contenente alcune frasi di testo qualsiasi (minimo 3-4 righe).
Implementare un software che legge dal file “testo.txt”, conta il numero totale di righe, il numero totale di parole e il numero totale di caratteri (spazi esclusi). Visualizzare le statistiche a schermo e salvarle nel file “statistiche.txt” nel formato:
Righe: X
Parole: Y
Caratteri: Z
Esercizio 819: prezzi prodotti
Creare a mano un file di testo chiamato “prezzi.txt” contenente 5 prezzi di prodotti (numeri reali con 2 decimali), uno per riga.
Implementare un software che legge i prezzi dal file, calcola il totale, il prezzo medio, il prezzo minimo e il prezzo massimo. Visualizzare tutti i risultati a schermo e salvarli nel file “riepilogo.txt”, formattati con 2 decimali.
Esercizio 820 (parte 1): voti studente
Creare a mano un file di testo chiamato “voti.txt” contenente 8 voti (numeri interi da 1 a 10), uno per riga.
Implementare un software che legge i voti dal file, calcola la media, conta quanti sono sufficienti (>= 6) e quanti insufficienti (< 6). Visualizzare i risultati e salvarli nel file “report_voti.txt”.
Esercizio 820 (parte 2): promozione
Estendere l’esercizio precedente per determinare se lo studente è promosso o bocciato. Lo studente è promosso se la media è >= 6 E non ha più di 2 insufficienze. Aggiungere questa informazione al file “report_voti.txt”.
Esercizio 822: invertire righe
Creare a mano un file di testo chiamato “originale.txt” con 5-6 righe di testo.
Scrivere un programma che legge il file “originale.txt”, carica tutte le righe in una lista, inverte l’ordine delle righe e salva il risultato nel file “invertito.txt”. Visualizzare sia il contenuto originale che quello invertito.
Esercizio 823: filtra parole
Creare a mano un file di testo chiamato “parole.txt” contenente una lista di parole, una per riga (minimo 10 parole).
Implementare un software che legge le parole dal file, chiede all’utente di inserire una lunghezza minima (es: 5) e salva nel file “parole_lunghe.txt” solo le parole che hanno una lunghezza maggiore o uguale a quella specificata. Visualizzare quante parole sono state filtrate.
Esercizio 824: agenda telefonica semplice
Creare sul Desktop una cartella chiamata “agenda”.
Implementare un software che permette all’utente di salvare contatti in un file “contatti.txt” nella cartella agenda. Ogni contatto deve contenere nome e numero di telefono separati da una virgola (es: “Mario Rossi,3331234567”).
Il programma deve offrire un menu:
- (A) Aggiungi contatto
- (V) Visualizza tutti i contatti
- (C) Cerca contatto per nome
- (Q) Esci
Quando si cerca un contatto, visualizzare nome e numero se trovato, altrimenti comunicare che il contatto non esiste.
Modulo Pathlib
Il modulo Pathlib permette di ricavare informazioni sui percorsi ove si trovano file e directories all’interno del sistema operativo che esegue lo script Python in oggetto. In particolare, di solito viene utilizzato l’oggetto Path importato dalla libreria pathlib.
from pathlib import Path
# il file "song.mp3" della cartella "musica" su C: in Windows
canzone = Path("C:/musica/song.mp3")
print("Percorso della canzone:", canzone)
# la cartella corrente (dove abbiamo salvato questo file Python)
cur = Path.cwd()
print( "Cartella corrente:", cur )
# la HOME utente
# "C:\Users\utente" su Windows, "/home/utente" su Mac/Linux
homePath = Path.home()
print( "Home dir:", homePath )
# il percorso del Desktop
# il simbolo / congiunge i percorsi
desktop = homePath / "Desktop"
print( "Il percorso del Desktop:", desktop )
Negli esempi trattati sopra sono evidenziate alcune funzionalità:
- un percorso può essere descritto tramite una stringa, con il simbolo
/(slash) come separatore di percorsi - Un Path indica un percorso che non deve per forza deve esistere… anzi tra le funzionalità di Path c’è quella di verificare se un percorso esiste ed eventualmente crearlo
Tip
I percorsi delle cartelle principali
Dopo qualche anno a parlare del modulo
Pathlib, ho capito che gli studenti non hanno bene idea dell’organizzazione dei files in un Sistema Operativo.Cerchiamo di farla semplice: da una parte troviamo il Sistema Operativo Windows (che fa come gli pare), dall’altra tutto il resto del mondo (Linux, BSD, MacOS, iOS, Android) che segue una linea comune.
Windows
Il sistema operativo Windows gestisce gli HD in maniera unica: ogni HD viene abbinato ad una lettera, ma per motivi storici di solito si parte da C: (se hai 3 dischi saranno C: D: E:)
In uno di questi, di solito C:, c’è il sistema operativo vero e proprio, nella cartella
C:\Windows. Gli utenti hanno ognuno uno spazio personale (una home): queste cartelle si trovano tutte nel percorsoC:\Userse si chiamano con il relativo nome utente (la home dell’utente pippo saràC:\Users\pippo)All’interno della home ci sono alcune cartelle importanti per l’organizzazione dei file dell’utente. Le cartelle:
Documents: la cartella dei documentiDownloads: la cartella dove vanno a finire i downloadDesktop: la cartella che corrisponde al Desktop utente- etc…
Quindi ad esempio, il desktop dell’utente pippo si trova nella cartella
C:\Users\pippo\Desktop.
Linux, Mac, BSD, etc…
Tutti gli altri sistemi operativi uniscono tutti gli HD montati sul proprio PC in un unico spazio comune che identificano con il simbolo
/e chiamanoroot(radice).Le cartelle degli utenti si trovano nella cartella
/homee si chiamano ovviamente con il nome utente. Ad esempio lo spazio personale dell’utente ciccio sarà nel percorso/home/ciccio.Le cartelle importanti per l’utente si chiamano allo stesso modo, quindi ad esempio, la cartella Downloads di ciccio si troverà su
/home/ciccio/Downloads
Una partenza comune: Path.home()
In qualsiasi OS, Python identifica la cartella personale tramite la funzione Path.home(), così che (ad esempio) il Desktop di qualunque utente si trovi nel percorso:
percorsoDesktop = Path.home() / "Desktop"
Molto intelligente ☺️
L’oggetto Path espone fra le altre le seguenti funzioni:
| Funzione | Descrizione |———————–|–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––|
| cwd() | Ritorna il percorso della cartella corrente. Restituisce un Path. |
home() | Ritorna il percorso della cartella utente (la home, vedi sopra). Restituisce un Path. |
|---|---|
exists() | Verifica se il percorso attuale esiste oppure no. Ritorna True o False. |
is_dir() | Ritorna True se il path attuale è una cartella, False altrimenti. |
is_file() | Ritorna True se il path attuale è un file, False altrimenti. |
| ––––––––––– | —————————————————————————————– |
iterdir() | Quando il percorso punta a una directory, ritorna la lista degli oggetti percorso del contenuto della directory |
glob(pattern) | Ritorna la lista di tutti i file e le cartelle presenti nel Path e che rispettano il pattern. |
| ––––––––––– | —————————————————————————————– |
open(mode) | Apre il file indicato nel Path in modalità “mode”. Ritorna un file object come fa la funzione predefinita “open()” |
mkdir() | Crea la cartella abbinata al percorso del Path. |
| ––––––––––– | —————————————————————————————– |
unlink() | Rimuove un file (solo se è un file). Attenti a quello che fate, per favore!!! |
rmdir() | Rimuove la cartella abbinata al percorso del Path, ma solo se la cartella è vuota, altrimenti ERRORE!!! |
| ––––––––––– | —————————————————————————————– |
rename(target) | Rinomina il Path al percorso indicato nel Path “target”. |
Note
Per vedere tutte le funzioni offerte dall’oggetto
Path:>>> from pathlib import Path >>> dir(Path)(Ma questo lo sapevate già…)
Vediamo alcuni esempi per capire il funzionamento delle funzioni più “ostiche”: ovviamente ognuno di questi esempi dovrebbe iniziare con l’import dell’oggetto Path dalla libreria pathlib.
from pathlib import Path
percorso = Path.home()
pippo = percorso / "pippo"
if pippo.exists():
print("pippo esiste")
if pippo.is_file():
print("ed è un file")
if pippo.is_dir():
print("ed è una cartella")
else:
print("pippo non esiste")
desk = Path.home() / "Desktop"
for f in desk.glob("*.txt"):
print(f, "(file)")
Note
Pattern
Il parametro pattern della funzione glob è una stringa che ammette la wildcard * dove per wildcard (in informatica) si intende il carattere che può sostituire qualunque altro, anche molti.
Così il pattern “*.txt” rappresenta tutti i file che finiscono per “.txt”, il pattern “*” rappresenta tutti i file con qualunque nome, il pattern “a*” rappresenta tutti i file che iniziano per “a” e così via.
home = Path.home()
for f in home.iterdir():
if f.is_file():
print(f, "(file)")
elif f.is_dir():
print(f, "(dir)")
else:
print(f, "(boh)")
home = Path.home()
# creazione della cartella "ciao" nella home
# ATTENZIONE!!! mkdir da errore se la cartella esiste già... bisogna controllare!!!
cartellaCiao = home / "ciao"
if not cartellaCiao.exists():
cartellaCiao.mkdir()
# cambio nome della cartella "ciao" in "salve" nella home
cartellaSalve = home / "salve"
cartellaCiao.rename(cartellaSalve)
# eliminazione della cartella "salve"
cartellaSalve.rmdir()
desktop = Path.home() / "Desktop"
filePath = desktop / "pippo.txt"
file = filePath.open("w")
file.write("Ciao Pippo")
file.close()
Grazie alla funzione open() dell’oggetto Path possiamo creare o aprire in lettura file in un percorso a nostra scelta. O a scelta dell’utente.
Adesso tocca a voi provare il codice degli esempi e poi procedere con gli esercizi!
Esercizi
Caution
NO IA
Gli esercizi di questo gruppo servono solo per la comprensione dei concetti illustrati. Se non si riesce a farli significa che occorre studiare meglio i concetti sopra. Inutile in questo contesto affidarsi alle IA per la risoluzione degli stessi!!!
Esercizio 841
Scrivere un programma che elenca tutto il contenuto del proprio Desktop e verificare per ogni elemento se esso è un file oppure una cartella.
Esercizio 842
Scrivere un programma per creare sul Desktop una cartella di nome “prova”.
All’interno di essa dovrà essere inserito un file di nome “pippo.txt”.
Dentro al file “pippo.txt” ci deve essere inserita una frase qualsiasi a scelta dell’utente.
Esercizio 843
Creare sul proprio Desktop un file di nome “gatto”. Chiedere all’utente di inserire una parola (ad esempio: “cane”) e rinominare il file precedentemente creato da “gatto” alla parola inserita dall’utente.
Esercizio 843 bis
Chiedere all’utente se vuole eliminare il file dell’esercizio precedente. Se sì, eliminarlo. Se il file non è più presente, comunicare all’utente che il file non è più presente sul Desktop.
Esercizio 844
Scrivere un programma creare una cartella di nome “cartella” sul Desktop e al suo interno un file vuoto di nome “file”.
Procedere ad eliminare la cartella “cartella”, verificando che è impossibile farlo se prima non si elimina ogni file al suo interno.
Esercizio 845
Creare sul Desktop la cartella “esercizio”. Al suo interno generare un file chiamato “numeri.txt” contenente 10 numeri casuali compresi fra 1 e 100. I numeri vanno scritti in colonna (uno sotto l’altro).
Creare dentro la cartella “esercizio” un file chiamato “sommaPari” contenente la somma dei numeri pari contenuti nel file “numeri.txt”.
Esercizio 846
Ogni volta che esegui il programma “es736.py” questo aggiorna un file chiamato “contatore.txt”, salvato nella home, a seconda del suo contenuto. Se il file non esiste, si crea scrivendoci dentro il valore 1. Se esiste, se ne legge il contenuto e si modifica il file scrivendo in esso il numero successivo. Ad esempio, se il file contiene il numero 3, si scriverà al suo posto il numero 4. E così via, ad ogni esecuzione del programma.
Esercizio 847
Chiedere all’utente di inserire una stringa.
Visualizzare l’elenco di tutti i file con estensione txt oppure py presenti nella propria cartella Downloads al cui interno è presente la stringa inserita.
Esercizio 848
Creare un programma per ripulire la cartella dei Downloads.
Prima visualizza la stringa: “Nella cartella Downloads ci sono TOT files. Vuoi cancellarli?” (T) Sì, tutti. (U) guardiamoli uno per volta (N) No
Se l’utente seleziona T, cancella tutto e via.
Se seleziona N, non fare niente
Se seleziona U, per ogni file visualizza il nome e chiedi: “Vuoi cancellarlo? (S/N)”
Esercizio 849 (Sposta Files)
Crea sul Desktop una cartella chiamata ESERCIZI. Cerca i files Python presenti nella tua cartella Documenti e spostali nella cartella ESERCIZI.
Modulo PlatformDirs
Il modulo platformdirs fornisce un modo standardizzato per determinare la directory dei file di configurazione, la directory dei dati e la directory della cache per un’applicazione su diverse piattaforme.
Il modulo va installato scaricandolo da PyPI, quindi dal menù strumenti di Thonny, aprite il Terminale (la shell di sistema) e digitate:
$ pip install platformdirs
Questo modulo presenta un elenco di funzioni semplicissime, in grado di ritornare una str oppure un Path per individuare una directory utente, seguendo una logica semplicissima.
Definisci prima un oggetto di tipo PlatformDir, indicando il nome della tua App e il fatto che vuoi creare le cartelle se non esistono:
from platformdirs import PlatformDirs
dirs = PlatformDirs("MyApp", ensure_exists=True)
Livello utente vs Livello sistema
Ognuna delle directory può essere individuata su due livelli: utente e sistema. Il livello utente è tipico di ogni utente, le cartelle individuate si trovano da qualche parte nella sua home directory e contengono le configurazioni, i dati e la cache utente. Ad esempio, le opzioni di Thonny o di Google Chrome vengono salvate lì… infatti se cambiate postazione o un altro si siede sul vostro PC deve riconfigurare tutto da capo!
Il livello di sistema è tipico di ogni computer. Lì ci stanno configurazioni, dati, cache del software comuni a tutti gli utenti.
Nella nostra semplice panoramica illustreremo solo le directory a livello utente: più utili, meno… dannose, più gestibili.
directory di configurazione utente
Le directory di configurazione sono utilizzate per archiviare file di configurazione dell’applicazione e preferenze dell’utente.
# questa è una str.
conf_str = dirs.user_config_dir / "config.txt"
# questo è un Path
conf_path = dirs.user_config_path / "config.txt"
directory dei dati
La directory dei dati può essere utile per un database, un file scaricato da internet, contenuto creato dall’utente o altro.
# questa è una str. Su Windows, sarà "C:\Users\NomeUtente\AppData\Local\MyApp\"
data_str = dirs.user_data_dir
# questo è un Path
data_path = dirs.user_data_path
directory della cache
La directory della cache può essere utilizzata per archiviare dati temporanei che possono essere ricreati se necessario.
# questa è una str.
cache_str = dirs.user_cache_dir
# questo è un Path
cache_path = dirs.user_cache_path
Dizionari, JSON, CSV
In questo capitolo tratteremo il tipo Python chiamato dizionari e vedremo come utilizzare questo concetto per lavorare con due delle strutture dati più utilizzati in tutto il contesto informatico:
- il formato JSON
- i file CSV.
Dizionari
I dizionari (dict) sono un tipo Python predefinito, sequenza, mutabile, non ordinato (e non ordinabile) che contiene elementi (items)
formati ognuno da una coppia chiave (key): valore (value).
Per dichiarare gli elementi di un dizionario si utilizzano le parentesi graffe e questo ci fa capire che i tipi predefiniti sono finiti, perché sono finiti i tipi di parentesi esistenti ☺️
Vediamo con un esempio il concetto:
prefisso = { "IT": 39, "ES": 34, "DE": 49}
In questo semplice esempio abbiamo memorizzato i prefissi internazionali di alcuni stati: data la chiave (il nome dello stato) si può visualizzare il prefisso corrispondente (il “valore”), ricorrendo però alle parentesi quadre:
print( prefisso["IT"] ) # scrive 39
Per completezza, ricordo che è possibile verificare il tipo della variabile con il comando type()
type(prefisso) # vale <class 'dict'>
e che è possibile creare un dizionario vuoto semplicemente utilizzando le parentesi graffe: essendo i dizionari un tipo mutabile sarà sempre possibile aggiungervi dati successivamente.
dizionarioVuoto = {}
Torniamo all’esempio del dizionario dei prefissi e vediamo come è semplice inserire un nuova coppia chiave: valore nel dizionario. Occhio all’utilizzo delle parentesi, che in questo caso sono le quadre:
prefisso["FR"] = 34
print(prefisso)
{ "IT": 39 , "ES": 34 , "DE": 49 , "FR": 34 }
Come vedete ci sono 2 stati (“ES” e “FR”) che hanno lo stesso valore, lo stesso prefisso. Questo è assolutamente possibile nei dizionari. I dizionari però, non ammettono valori duplicati nelle chiavi, quindi non è possibile inserire due chiavi uguali per valori diversi. Da questa considerazione si capisce anche che le chiavi di un dizionario devono essere per forza tipi immutabili (ad esempio numeri, stringhe, tuple, etc… solitamente interi o stringhe) mentre per i valori si può dare spazio alla fantasia.
L’inserimento dei dati dipende dall’esistenza o meno della chiave nel dizionario:
# se la chiave non esiste, viene aggiunta una nuova coppia nel dizionario
prefisso["UK"] = 44
# se la chiave è già presente, viene modificato il valore in essa
prefisso["FR"] = 33
print(prefisso)
{ "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
Warning
I dizionari non possono essere ordinati!!!
Non è possibile ordinare i valori presenti in un dizionario. Per procedere ad una ricerca bisogna fare affidamento solo sulle chiavi!!!
Lavorare con i dizionari
Per le nostre considerazioni, partiremo analizzando il dizionario “prefisso” già definito:
print(prefisso)
{ "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
Come sappiamo, è possibile interrogare una sequenza per sapere se un elemento è presente in essa. Nei dizionari l’istruzione IN funziona con le chiavi:
"IT" in prefisso # vale True
"US" in prefisso # vale False
Quindi, il seguente codice:
if "IT" in prefisso:
print("il prefisso \"IT\" è presente e vale", prefisso["IT"])
else:
print("il prefisso \"IT\" non è presente")
scriverà:
il prefisso "IT" è presente e vale 39
L’istruzione for...in che ben conosciamo, scorre le chiavi dei dizionari:
for chiave in prefisso:
print(chiave)
IT
ES
DE
FR
UK
Questo significa che è semplicissimo usare il FOR…IN per ottenere le chiavi e visualizzare dunque i valori contenuti nel dizionario, con la sintassi dizionario[chiave] = valore:
for chiave in prefisso:
print( "Prefisso", chiave, ":", prefisso[chiave] )
Prefisso IT : 39
Prefisso ES : 34
Prefisso DE : 49
Prefisso FR : 33
Prefisso UK : 44
I dizionari supportano inoltre alcuni metodi aggiuntivi tipici della classe dict. Dopo la tabella riassuntiva trovate alcuni esempi esplicativi.
| Metodo | Descrizione |
|---|---|
dic.keys() | Restituisce una sequenza speciale (tipo una lista) che contiene tutte le chiavi del dizionario dic, aggiornate in tempo reale. |
dic.values() | Restituisce una sequenza speciale (tipo una lista) che contiene tutti i valori del dizionario dic, aggiornati in tempo reale. |
dic.get(key, default) | Restituisce il valore corrispondente a key, oppure default se non esiste.Se default è nullo, non restituisce nulla. |
dic.pop(key, default) | Restituisce il valore corrispondente a key e lo elimina dal dizionario. Se non presente restituisce default.Se non presente e default non impostato, genera un errore chiamato KeyError. |
dic.copy() | Clona dic |
dic.clear() | Cancella dic, ovvero rimuove tutti i suoi elementi |
dic.items() | Restituisce i dati del dizionario come una lista di tuple (chiave,valore) |
Esempio 1: keys
chiavi = prefisso.keys()
print(chiavi) # scrive dict_keys( ["IT","ES","DE","FR","UK"] )... simile ad una lista, vedete?
for k in chiavi:
print(k)
IT
ES
DE
FR
UK
prefisso["PR"] = 25 # aggiungo un elemento al dizionario
print(chiavi)
dict_keys( ["IT","ES","DE","FR","UK","PR"] ) # valori aggiornati
Esempio 2: values
valori = prefisso.values()
print(valori) # scrive dict_values( [39, 34, 49, 33, 44] ) ... simile ad una lista, vedete?
for v in valori:
print(k)
39
34
49
33
44
prefisso["PR"] = 25 # aggiungo un elemento al dizionario
print(valori)
dict_values( [39, 34, 49, 33, 44, 25] ) # valori aggiornati
Esempio 3: get
# prefisso vale { "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
prefisso.get("US") # la chiave "US" non c'è. Non ritorna nulla
prefisso.get("US", 0) # "US" chiave non presente, ritorna 0
prefisso.get("IT") # "IT" chiave presente, ritorna il valore 39
prefisso.get("IT", 0) # essendo "IT" presente, ritorna comunque 39
Esempio 4: pop
# prefisso vale { "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
prefisso.pop("IT") # chiave "IT" presente, ritorna il valore 39
print(prefisso) # scrive { "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
# la coppia "IT":39 è stata eliminata dal dizionario
prefisso.pop("US") # la chiave "US" non c'è. ERRORE
KeyError
prefisso.pop("US", 0) # valore di default impostato a 0, ritorna 0
Tip
Con un valore di default impostato
pop()non da mai errore!!!
Esempio 4: items
# prefisso vale { "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
# scrive tutte le coppie "chiave valore" una per riga
for chiave in prefisso:
print(chiave, prefisso[chiave])
# in maniera analoga con dict.items()
for k,v in prefisso.items():
print(k, v)
Spero che osservando gli esempi sia tutto chiaro sul funzionamento dei dizionari e sulle funzioni disponibili per essi. Mi raccomando di provare voi stessi a riprodurre gli esempi per meglio capire quello che succede. E poi di provare a fare gli esercizi qui sotto :)
Esercizi sui dizionari
Esercizio 901
Creare un dizionario vuoto, chiamato DatiPersonali. Chiedere all’utente di inserire i propri dati personali (nome, cognome, indirizzo, numero civico, città, provincia, CAP) e inserirli nel dizionario con il tipo di dato come chiave (ad esempio “nome”) e il dato inserito dall’utente come valore.
Visualizzare i dati contenuti nel dizionario tramite un ciclo for..in sulle chiavi del dizionario.
Esercizio 902
Definire un dizionario vuoto chiamato “quadrati” e inserirvi all’interno una serie di valori tramite codice: le chiavi saranno i numeri interi da 1 a 100 e i valori saranno i quadrati delle chiavi.
Visualizzare tutto il dizionario scrivendo per ogni riga: “chiave” al quadrato = “valore”
Esercizio 903
Definire un dizionario vuoto chiamato “dispari” e inserirvi all’interno una serie di valori tramite codice: le chiavi saranno i numeri da 0 a 99 e i valori saranno i primi 100 numeri dispari. Mostrare il dizionario così ottenuto, visualizzando per ogni elemento la stringa “chiave: key, numero dispari abbinato: value”.
Chiedere all’utente di inserire un numero intero e se esso è una chiave del dizionario, visualizzare il numero corrispondente. Altrimenti visualizzare la scritta “chiave non presente nel dizionario”.
Esercizio 904
Definire il dizionario vuoto chiamato “pari” e inserirvi all’interno una serie di valori tramite codice: le chiavi saranno i numeri da 1 a 50 e i valori saranno i primi 50 numeri pari. Mostrare il dizionario così ottenuto, visualizzando per ogni elemento la stringa “chiave: key, numero pari abbinato: value”.
Chiedere all’utente di inserire un numero intero e se esso è una chiave del dizionario, eliminare la coppia chiave:valore corrispondente. Altrimenti visualizzare la scritta “chiave non presente nel dizionario”.
Esercizio 905
Definire un dizionario, inizialmente vuoto, chiamato “studentiDaInterrogare”. All’inizio va riempito con una serie di coppie dove la chiave sono i numeri interi che vanno da 1 in avanti (la posizione sul registro) e il valore è il cognome dello studente.
Definire la lista “interrogati”, inizialmente vuota. Estrarre 5 numeri casuali fra 1 e il numero di studenti da interrogare. Se il numero estratto è presente come chiave del dizionario, eliminare da esso l’elemento che corrisponde a quella chiave e inserire quel valore nella lista “interrogati”.
Al termine delle 5 estrazioni visualizzare la lista “interrogati” e il dizionario “studentiDaInterrogare”.
Esercizio 906
Definire un dizionario vuoto chiamato “Magazzino”, che sarà riempito con elementi tali che le chiavi rappresentano il prodotto presente in esso e i valori il numero di pezzi dello stesso. Inserire al suo interno i seguenti prodotti: 25 bastoni, 138 scaffali, 2000 scatole, 50 attaccapanni.
Visualizzare il contenuto del magazzino.
Chiedere all’utente di selezionare un prodotto; se il prodotto è presente, visualizzare il numero di elementi di quel prodotto presenti nel magazzino e permettere
all’utente di indicare quanti ne desidera. Il magazzino deve essere aggiornato togliendo dal magazzino i prodotti acquistati dall’utente.
Ad esempio, se l’utente seleziona scaffali, si visualizzi una scritta tipo: “Presenti 25 scaffali. Quanti vuole acquistarne? Se l’utente digita 30, ovviamente il
programma insulta l’utente. Se digita, ad esempio, 5, si visualizza una frase tipo “venduti 5 scaffali” e il magazzino viene aggiornato al nuovo numero di scaffali
presenti (25 erano, 5 sono stati venduti… 20!!!)
Visualizzare il magazzino, elencando i prodotti rimasti. Per ognuno dei prodotti rimasti, togliere 1 pezzo. Visualizzare di nuovo il magazzino.
Esercizio 907
Definire un dizionario chiamato “cartellaTombola” e riempirlo con 5 numeri interi random fra 1 a 90 diversi fra loro. Essi saranno la chiave dell’elemento inserito, mentre il valore sarà per tutti il booleano False. Visualizzare le chiavi del dizionario “cartellaTombola”.
Ad esempio potrebbe essere generato un dizionario tipo { 23: False, 45: False, 56: False, 78: False, 90:False}.
Ripetere 10 volte l’estrazione casuale di un numero compreso fra 1 e 90. Se il numero è una chiave della cartella, modificare a True il valore corrispondente. Alla fine delle 10 estrazioni verificare quanti e quali numeri della cartella sono stati estratti.
Esercizio 908
Permettere all’utente di inserire una parola e creare con questa il dizionario delle frequenze delle lettere.
Ad esempio, data una stringa “ababcc”, otterremo in risultato {"a": 2, "b": 2, "c": 2, "d": 0…...}.
Visualizzare alla fine la parola e tutti gli elementi del dizionario. Analizzando il dizionario trovare la lettera più frequente nella parola inserita. Visualizzare infine solo gli elementi del dizionario che hanno frequenza positiva.
Esercizio 909
Creare un dizionario ItalianoInglese e inserire una serie di termini con la parola italiana come chiave e la corrispondente traduzione inglese come valore. Ad esempio alla chiave “cane” viene abbinato il valore “dog”.
Data una qualunque stringa da parte dell’utente procedere a visualizzare la stringa “tradotta” sostituendo tutte le parole trovate presenti nel dizionario con la corrispondente parola inglese.
Esercizio 910
Definire un dizionario, chiamato “voti”, inizialmente vuoto. In esso dovranno essere aggiunti i voti di tutte le vostre materie, in modo tale che il nome della materia sia la chiave ed il voto corrispondente il valore. Visualizzare il dizionario con un ciclo, visualizzando in ogni riga la chiave (il nome della materia) e il valore corrispondente (il voto).
- Calcolare la media aritmetica dei voti e visualizzarla.
- Visualizzare la materia con il voto più alto (la materia preferita).
- Visualizzare i nomi di tutte le materie insufficienti (ahi ahi ahi…)
- Dato un nome di materia da parte dell’utente, visualizzare il voto se la materia è presente nel dizionario, oppure scrivere “materia non presente”.
Esercizio 911
Definire un dizionario chiamato “ortofrutta” inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un prodotto per un negozio di frutta e verdura, organizzate in questo modo:
- la chiave deve essere il nome del prodotto (es: “pere”, “patate”, ecc…)
- il valore sarà una tupla di 2 informazioni:
- un numero per la quantità presente (in chilogrammi)
- un numero per il costo al chilo del prodotto
Inserire tramite codice una serie di almeno 6 prodotti da vendere all’ortofrutta e procedere ad una visualizzazione come indicato:
prodotto: NOME
quantità: VALORE kg
costo: VALORE euro al chilo
(riga vuota)
Chiedere all’utente di inserire un prodotto. Se esso è presente nel dizionario, chiedere quanti chili di esso se ne vogliono acquistare. Se il numero inserito è minore o uguale alla quantità presente, diminuire la quantità attuale dei chili venduti e visualizzare all’utente il costo totale del prodotto acquistato.
Esercizio 912
Definire un dizionario chiamato “squadra” inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un giocatore della squadra, organizzate in questo modo:
- la chiave deve essere il numero di maglia del giocatore
- il valore sarà una tupla di 2 informazioni:
- una stringa per il nome del giocatore
- un carattere per il ruolo (’P per portiere, ‘D’ per difensore, ‘C’ per centrocampista, ‘A’ per attaccante)
Inserire tramite codice una serie di almeno 6 giocatori nella squadra e procedere ad una visualizzazione come indicato:
numero: NUMERO
nome: NOME
ruolo: RUOLO
(riga vuota)
Definire un nuovo dizionario chiamato “ruoli” ove vi saranno inseriti 4 elementi: le chiavi saranno i ruoli “P”, “D”, “C”, “A” possibili per i giocatori della squadra, i valori saranno il numero di giocatori per quel ruolo presenti nel dizionario “squadra”.
Visualizzare il dizionario così completato.
Esercizio 913
Definire un dizionario chiamato “rubrica” inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un contatto della rubrica, organizzate in questo modo:
- la chiave deve essere il nickname del contatto (ad esempio: “mamma”, “prof”, etc…)
- il valore sarà una tupla di 3 informazioni:
- una stringa per il numero di telefono del contatto
- una stringa per l’indirizzo di residenza
- un intero per l’anno di nascita
Inserire tramite codice una serie di almeno 5 contatti nella rubrica e procedere ad una visualizzazione come indicato:
nome: NOME
telefono: NUMERO
indirizzo: INDIRIZZO
anno di nascita: ANNO
(riga vuota)
Riempire una lista con i nickname dei contatti minorenni, visualizzare l’elenco e dire quanti sono in totale. Permettere all’utente di inserire un nick e se presente visualizzare il suo numero di telefono, altrimenti visualizzare la scritta “CONTATTO NON PRESENTE”.
Esercizio 914
Definire un dizionario chiamato “pazienti” inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna una persona in cura da un dietologo, organizzate in questo modo:
- la chiave deve essere il nome della persona
- il valore sarà una tupla di 4 informazioni:
- peso (in kg)
- altezza (in cm)
- età (in anni)
- sesso (
M' oppureF’)
Inserire tramite codice una serie di almeno 5 persone e procedere ad una visualizzazione come indicato:
nome: NOME
peso: VALORE kg
altezza: VALORE cm
maschio di TOT anni (oppure femmina di... a seconda del valore)
(riga vuota)
- Visualizzare i nomi di tutte le femmine.
- Contare il numero di minorenni più alti di 180 cm e visualizzare i loro nomi.
- Visualizzare il nome del maschio e della femmina che pesano di più.
Modulo JSON
JSON (JavaScript Object Notation) è un formato di testo per rappresentare dati strutturati. È leggibile dagli esseri umani e facilmente elaborabile dai programmi. Lo trovi ovunque: API web, file di configurazione, scambio dati tra dispositivi (ad esempio ESP32 e un server Flask!).
Un documento JSON assomiglia a un dizionario Python:
{
"nome": "Andrea",
"età": 30,
"attivo": true,
"sensori": ["temperatura", "umidità"],
"ultima_lettura": null
}
Attenti, però! Si scrive come un dizionario, ma è una semplice stringa!!! Per passare dalla stringa JSON al dizionario Python occorre il modulo json!
import json
Il modulo appartiene alla libreria standard, non occorre installare nulla!!!
Le sue funzioni principali sono:
| Funzione | Cosa fa |
|---|---|
json.dumps(diz) | dict Python → stringa JSON |
json.loads(string) | Stringa JSON → dict Python |
Vediamo alcuni esempi per capire meglio:
import json
dati = {}
dati["stazione"] = "ESP32-01"
dati["temperatura"] = 23.5
dati["umidità"] = 60
dati["attivo"] = True
testo_json = json.dumps(dati)
print(testo_json)
# {"stazione": "ESP32-01", "temperatura": 23.5, "umidità": 60, "attivo": true}
# Indentazione leggibile
print(json.dumps(dati, indent=2))
# Ordinare le chiavi
print(json.dumps(dati, sort_keys=True))
# Gestire caratteri non ASCII (es. lettere accentate)
print(json.dumps(dati, ensure_ascii=False, indent=2))
Quando converti i dati da un dizionario Python a JSON e viceversa, è bene tenere a mente la seguente tabella delle corrispondenze:
| Tipi Python | Tipi JSON |
|---|---|
dict | object {} |
list, tuple | array [] |
str | string "" |
int, float | number |
True / False | true / false |
None | null |
import json
testo = '{"stazione": "ESP32-01", "temperatura": 23.5, "attivo": true}'
dati = json.loads(testo)
print(type(dati)) # <class 'dict'>
print(dati["stazione"]) # ESP32-01
print(dati["temperatura"]) # 23.5
print(dati["attivo"]) # True ← true JSON diventa True Python
File JSON
Serializzare con dumps() restituisce una stringa normale: per salvarla su file è un attimo!
import json
letture = []
letture.append( {"id": 1, "temp": 22.1, "hum": 55} )
letture.append( {"id": 2, "temp": 23.4, "hum": 58} )
letture.append( {"id": 3, "temp": 21.8, "hum": 62} )
file = open("letture.json", "w")
# scrive il file a capo con indentazione a 2 spazi:
# rende il JSON facilmente leggibile anche dalle persone!
file.write( json.dumps(letture, indent=2) )
file.close()
Il file letture.json (scritto con indent=2) conterrà:
[
{"id": 1, "temp": 22.1, "hum": 55},
{"id": 2, "temp": 23.4, "hum": 58},
{"id": 3, "temp": 21.8, "hum": 62}
]
Allo stesso modo, leggere un file JSON significa leggere una stringa con read() e poi deserializzarla con loads().
import json
file = open("letture.json", "r")
letture = json.loads( file.read() )
file.close()
print(type(letture)) # <class 'list'>
for r in letture:
print(f"ID {r['id']}: {r['temp']}°C, {r['hum']}%")
Warning
Se il JSON è malformato, Python solleva
json.JSONDecodeError:Gestione degli errori su JSONimport json testo_errato = '{"nome": "Andrea", "età": }' # JSON non valido! try: dati = json.loads(testo_errato) except json.JSONDecodeError as e: print(f"Errore nel parsing JSON: {e}")
Esercizi su JSON
Esercizio 941
Crea un dizionario Python con almeno 5 chiavi (nome, cognome, età, città, hobby) e convertilo in una stringa JSON stampata in modo leggibile (indentata). Infine salva la stringa nel file dati.json.
Esercizio 942
Ricarica il contenuto dal file dati.json dell’esercizio precedente e convertila di nuovo in un dizionario Python. Visualizza solo i valori (non le chiavi) uno per riga.
Esercizio 943
Crea un dizionario Python che contenga: una stringa, un intero, un float, un booleano, None, una lista e un dizionario annidato. Convertilo in JSON e verifica come cambiano i tipi visualizzando il risultato con indent=2.
Esercizio 944
Crea un file config.json che contenga le impostazioni di un’applicazione:
- nome app, versione, debug (bool), porte permesse (lista di interi).
Scrivi uno script che legge il file e visualizza ogni impostazione con una riga descrittiva.
Esercizio 945
Crea manualmente una lista di almeno 4 dizionari, ognuno con i campi id, tipo, valore, unità.
Salvala in sensori.json, poi rileggila e visualizza gli id dei sensori
che hanno valore superiore a 25.
Esercizio 946
Leggi il file sensori.json creato nell’esercizio precedente, aggiungi un nuovo sensore alla lista e risalva il file.
Verifica aprendo il file che il nuovo sensore sia presente.
Esercizio 947
Crea un dizionario che rappresenti una scuola: nome, indirizzo (a sua volta un dizionario con via, città, CAP), e una lista di classi (ogni classe ha nome e numero di studenti).
Salvalo in JSON nel file ‘scuola.json’. Rileggi il file e ricarica i dati, poi leggili tutti uno per riga con chiave:valore vicini.
Esercizio 948
Crea uno script che simula un logger di dati. Ad ogni esecuzione:
- Legge il file
log.json(se esiste); se non esiste, parte da una lista vuota. - Aggiunge un nuovo record con
{"lettura": N, "valore": <numero casuale tra 20 e 30>}dove N è il numero progressivo. - Salva il file aggiornato.
Esegui lo script più volte e verifica che il file cresca correttamente.
Note
Gli esercizi da qui in poi si basano sui file JSON reperibili da https://adjam.org/data/.
Ogni esercizio indica il file da scaricare per poter eseguire l’esercizio correttamente.
Esercizio 961
Si basa sulla mappa dei cinema italiani.
- Step 0
- Caricare i dati dal file JSON in una opportuna lista dati. Visualizzare il numero di elementi presenti
- Step 1
- Data una regione (es: Marche), visualizzare, per ogni cinema, il “Nome” (del cinema) e il “Comune”, ove esso è locato.
- Step 2
- Data una regione (es: Marche) contare il numero di cinema presenti.
- Step 3
- Creare una nuova lista con tutti e soli i cinema che presentano un anno di inserimento. Salvare i dati nel file “CinemaConAnnoDiInserimento.json”.
Esercizio 962 (da sistemare)
Si basa sull’anagrafica delle scuole italiane.
- Step 0
-
Caricare i dati dal file JSON in una opportuna lista dati. Visualizzare il numero di dati presenti.
- Step 1
-
Dato un codice scuola, digitato dall’utente, visualizzare il nome, l’indirizzo, la città, il numero di telefono, la mail e il sito web della scuola corrispondente. Oppure la scritta “Codice Scuola non esistente”.
- Step 2
-
Contare le tipologie di scuole, dividendo tra scuola dell’infanzia, prima, e secondaria di primo e secondo grado e fra scuola statale e paritaria. Il risultato dovrà essere visualizzato in un formato tipo:
Scuola dell'infanzia (statale): 98 Scuola dell'infanzia (paritarie): 34 ... - Step 3
-
Inserita una città da parte dell’utente, visualizzare tutte le scuole (nome e via) divise per tipologia (es: prima quelle dell’infanzia, poi le scuole primarie, etc…)
- Step 4
-
Selezionato un CAP da parte dell’utente, visualizzare i nomi e i codici scuola di tutte le scuole presenti in quel CAP.
Esercizio 963 (da sistemare)
Si basa sulla mappa dei monumenti italiani.
- Step 0
-
Caricare i dati dal file JSON in una opportuna lista dati. Visualizzare il numero dei monumenti presenti.
- Step 1
-
Creare una tupla con i nomi delle regioni italiane. Visualizzare i nomi delle regioni e permettere all’utente di selezionarne una. Visualizzare tutti i nomi dei monumenti di quella regione (tra virgolette) con accanto il nome della città in cui si trova (separata da virgola) e tra parentesi il “tipo” di monumento.
-
Ad esempio, selezionata “Puglia”, uno dei monumenti potrebbe essere:
"Eraclio", Barletta (Monumento) - Step 2
-
Data una regione (es: Puglia) creare nel Desktop dell’utente un file denominato “Monumenti
Regione.json” (ad esempio “MonumentiPuglia.json”) contenente tutti e solo i monumenti della regione selezionata. - Step 3
-
Inserito un anno a scelta a dell’utente visualizzare il numero di monumenti inseriti nel file in quell’anno.
- Step 4
-
Per ognuno degli anni dal 2010 al 2019, visualizzare il numero di monumenti inseriti in quell’anno.
- Step 5
-
Inserito un ID OpenStreetMap da parte dell’utente, visualizzare tutti i dati relativi al monumento selezionato oppure visualizzare la scritta: “nessun monumento esistente con ID:” e visualizzare l’ID inserito.
Modulo CSV
Il modulo csv è un modulo della libreria predefinita che serve per trattare i file in formato CSV (Comma Separated Value).
Il CSV è un formato di testo utilizzato per l’importazione ed esportazione di una tabella di dati da fogli elettronici o da basi di dati. Essere in grado di manipolarlo ci permette dunque di migliorare di molto (ma con poco sforzo) le nostre attuali capacità di programmatori!
Per fare un esempio semplice con il formato CSV, immaginate di voler memorizzare la seguente tabella dati:
| Nome | Ruolo | Squadra |
|---|---|---|
| Maradona | Centrocampista | Napoli |
| Gullit | Attaccante | Milan |
| Brehme | Difensore | Inter |
| Falcao | Centrocampista | Roma |
Ogni riga della tabella nel formato CSV termina quando si va a capo. Ogni colonna della tabella termina con una virgola. Da cui si ottiene:
Nome,Ruolo,Squadra
Maradona,Centrocampista,Napoli
Gullit,Attaccante,Milan
Brehme,Difensore,Inter
Falcao,Centrocampista,Roma
Mi sembra molto semplice da capire :)
Note
Il modulo CSV permette di caricare in maniera automatica i dati estratti in strutture dati che noi ben conosciamo: i dizionari!
Da tutte queste considerazioni insieme capite la necessità di studiare
questo modulo. Per caricare i dati da un file csv si può semplicemente
utilizzare la funzione DictReader() del modulo CSV, che carica ogni
riga del file (successiva alla prima) in un dizionario, con la prima riga che
diventa la chiave di ogni voce.
Facciamo alcuni esempi caricando i dati dal file “giocatori.csv” di cui conosciamo già la struttura.
import csv
# la lista su cui caricheremo i dati
dati = []
file = open("giocatori.csv", "r")
lettore = csv.DictReader(file)
for riga in lettore:
dati.append(riga)
file.close()
# da adesso in poi basta lavorare sulla variabile dati
# ...
# visualizzazione dati
for giocatore in dati:
print(giocatore)
L’output di questo codice diventa questo:
{'Nome': 'Maradona', 'Ruolo': 'Centrocampista', 'Squadra': 'Napoli'}
{'Nome': 'Gullit', 'Ruolo': 'Attaccante', 'Squadra': 'Milan'}
{'Nome': 'Brehme', 'Ruolo': 'Difensore', 'Squadra': 'Inter'}
{'Nome': 'Falcao', 'Ruolo': 'Centrocampista', 'Squadra': 'Roma'}
Caution
ATTENTI a EXCEL!!!
Se create il file CSV con Microsoft Excel questo imporrà come separatore il punto e virgola
;invece della virgola,.Invece di mettersi a cambiare tutti i file, è più semplice indicare a Python quale è il simbolo delimitatore dei campi, con questa sintassi:
file = open("giocatori.csv", "r") lettore = csv.DictReader(file, delimiter=";")
In maniera analoga è possibile scrivere in un file CSV partendo da una serie di
dizionari (una… lista di dizionari!!!) utilizzando la funzione DictWriter.
import csv
dati = []
dati.append( {"nome":"Gino","cognome":"Panino","professione":"imbianchino"} )
dati.append( {"nome":"Pino","cognome":"Pinguino", "professione":"spazzacamino"} )
campi = ["nome","cognome", "professione"]
# il parametro newline così impostato assicura che il codice funzioni uguale su Win,Mac,Linux
file = open("esempio.csv", "w", newline="")
scrittore = csv.DictWriter(file, campi)
# inserisce l’intestazione nel file
scrittore.writeheader()
for riga in dati:
scrittore.writerow(riga)
file.close()
Questo codice produce un file CSV chiamato “esempio.csv” contenente i seguenti dati:
nome, cognome, professione
Gino, Panino, imbianchino
Pino, Pinguino, spazzacamino
Spero sia tutto chiaro!
Warning
Se avete una lista di dizionari con tanti campi e volete creare un file CSV con un numero ristretto (di campi) fate così:
# ... # indicate i campi che volete scrivere nel file campi = ["nome","cognome"] file = open("giocatori.csv", "w", newline="") # dite a DictWriter di ignorare i campi mancanti scrittore = csv.DictWriter(file, campi, extrasaction='ignore') # ...
Arrivati qui dovete semplicemente riprovare il codice proposto in questi esempi e poi mettervi alla prova con i seguenti esercizi!
Esercizi su CSV
Note
Gli esercizi che seguono si basano tutti sui file CSV reperibili da https://adjam.org/data/.
Ogni esercizio indica il file da scaricare per poter eseguire l’esercizio correttamente.
Esercizio 981
Si basa sulla mappa dei cinema italiani.
- Step 0
- Caricare i dati dal file CSV in una opportuna lista dati.
- Step 1
- Visualizzare, per ogni cinema, il “Nome” (del cinema) e il “Comune”, ove esso è locato.
- Step 2
- Come lo step precedente, ma limitandosi solo a quelli della regione “Marche”. La regione si trova nel campo “Regione”.
- Step 3
- Estrapolare i cinema inseriti dopo il 2015 per la regione “Lombardia”. Con i suddetti dati creare un nuovo file CSV chiamato “LombardiaPost2015.csv” con i dati delle colonne “Nome”, “Comune”, “Provincia”, “Longitudine”, “Latitudine”.
Esercizio 982
Si basa sull’anagrafica delle scuole italiane.
- Step 0
-
Caricare i dati dal file CSV in una opportuna lista dati.
- Step 1
-
Dato un codice scuola, digitato dall’utente, visualizzare il nome, l’indirizzo, la città, il numero di telefono, la mail e il sito web della scuola corrispondente. Oppure la scritta “Codice Scuola non esistente”.
- Step 2
-
Contare le tipologie di scuole, dividendo tra scuola dell’infanzia, prima, e secondaria di primo e secondo grado e fra scuola statale e paritaria. Il risultato dovrà essere visualizzato in un formato tipo:
Scuola dell'infanzia (statale): 98 Scuola dell'infanzia (paritarie): 34 ... - Step 3
-
Inserita una città da parte dell’utente, visualizzare tutte le scuole (nome e via) divise per tipologia (es: prima quelle dell’infanzia, poi le scuole primarie, etc…)
- Step 4
-
Selezionato un CAP da parte dell’utente, visualizzare i nomi e i codici scuola di tutte le scuole presenti in quel CAP.
Esercizio 983
Si basa sulla mappa dei monumenti italiani.
- Step 0
-
Caricare i dati dal file CSV in una opportuna lista dati.
- Step 1
-
Creare una tupla con i nomi delle regioni italiane. Visualizzare i nomi delle regioni e permettere all’utente di selezionarne una. Visualizzare tutti i nomi dei monumenti di quella regione (tra virgolette) con accanto il nome della città in cui si trova (separata da virgola) e tra parentesi il “tipo” di monumento.
-
Ad esempio, selezionata “Puglia”, uno dei monumenti potrebbe essere:
"Eraclio", Barletta (Monumento) - Step 2
-
Creare nella home dell’utente il file “MonumentiPuglia.txt” (se la regione selezionata è la Puglia) con i monumenti elencati uno per riga (ripeto, nella stessa visualizzazione dello step precedente).
- Step 3
-
Inserito un anno a scelta a dell’utente visualizzare il numero di monumenti inseriti nel file in quell’anno.
- Step 4
-
Per ognuno degli anni dal 2010 al 2019, visualizzare il numero di monumenti inseriti in quell’anno.
- Step 5
-
Inserito un ID OpenStreetMap da parte dell’utente, visualizzare tutti i dati relativi al monumento selezionato oppure visualizzare la scritta: “nessun monumento esistente con ID:” e visualizzare l’ID inserito.
Esercizio 984
Si basa sulla mappa delle strutture ricettive marchigiane.
- Step 0
- Caricare i dati dal file CSV in una opportuna lista dati.
- Step 1
- Inserito dall’utente un numero S compreso fra 1 e 5, visualizzare tutte le strutture ricettive classificate come “Alberghi S stelle” dove S è il numero inserito dall’utente
- Step 2
- Come il precedente ma permettendo all’utente di selezionare anche una provincia (da scegliere in un elenco opportuno). Saranno dunque visualizzate tutte le strutture classificate in un certo modo di quella provincia.
- Step 3
- Visualizzare il numero totale di strutture ricettive per provincia.
- Step 4
- Selezionata una provincia, visualizzare i nomi di tutte le città della provincia che presentano strutture ricettive.
Object Oriented Programming
OOP è l’acronimo di Object Oriented Programming, programmazione orientata agli oggetti. Python è un linguaggio di programmazione completamente basato sulla OOP, ovvero tutti i tipi di dato che abbiamo incontrato (interi, reali, stringhe, liste, etc…) sono in realtà oggetti che condividono caratteristiche comuni.
In questo capitolo cercheremo prima di capire cosa è un oggetto e come si definisce in Python e poi vedremo tutte quelle peculiarità che fanno della OOP il punto di svolta della programmazione fino a renderla in grado di produrre il software della qualità odierna.
Sarà bellissimo! ☺️
Classi e Oggetti
La prima cosa da fare è cercare di capire cosa si intende quando si utilizzano i termini Classe e Oggetto: vediamo se si riesce al primo colpo!!!
Partiamo da un concetto qualsiasi, ad esempio… la sedia!
Le classi servono per descrivere i concetti: parlando delle sedie, potremmo citare ad esempio:
- il materiale
- il colore
- l’altezza
- l’ampiezza della seduta
- etc…
Una classe descrive il concetto attraverso una serie di informazioni (ad esempio, quelle che abbiamo citato prima) che saranno dello stesso tipo per tutte le sedie (cioè, tutte le sedie sono fatte di un materiale, hanno un colore, etc…) ma che chiaramente possono avere valori diversi (sedie di legno o di ferro, bianche oppure blu, etc…).
Gli oggetti rappresentano le istanze dei concetti descritti dalle classi. Ad esempio, se state leggendo seduti, la vostra sedia è un oggetto, mentre un’altra sedia è un altro oggetto.
Se in cucina avete sei sedie significa che avete sei oggetti (diversi) della (stessa) classe Sedia.
Note
Classi e Oggetti
Le Classi sono un concetto analogo ai tipi di variabile, ad esempio il tipo intero, che descrive tutti i numeri che sono interi.
Gli Oggetti sono un concetto analogo alle variabili, che possono contenere un unico valore! Quello è l’oggetto!
Spero di essere stato chiaro… più di così non riesco!!!
Le Classi
Partiamo sempre da un concetto, ad esempio l’Automobile.
Per implementare la classe Automobile potremmo definire caratteristiche come la marca, il modello, il colore, la
cilindrata.
Un’automobile è però un concetto in grado anche di alcuni comportamenti: ad esempio avvia, spegni, accelera, frena, curva, ecc…
Tip
Attributi e Metodi di un oggetto
Le caratteristiche, o
attributidi un oggetto, sono quegli elementi utili a descriverne le proprietà e lo stato. Sono solitamente individuate tramite dei sostantivi.I comportamenti, o
metodidi un oggetto sono quelle funzionalità che mette a disposizione per interagire con esso. Sono solitamente individuati tramite dei verbi.
Attributi e metodi definiti all’interno di un oggetto vengono comunemente definiti i membri dell’oggetto.
Per provare a scrivere il nostro primo esempio in codice, partiamo da una classe semplicissima, come la classe Cerchio.
I suoi attributi sono semplicemente il raggio, mentre come metodi metteremo quelli per il calcolo di Area e Circonferenza.
# File "cerchio.py"
import math
class Cerchio:
# inizializza gli attributi della classe
def __init__(self, raggioInserito):
# aggiunge l’attributo "raggio" alla classe e lo inizializza
self.raggio = raggioInserito
def calcolaArea(self):
a = math.pi * self.raggio * self.raggio
return a
def calcolaCirconferenza(self):
c = 2* math.pi * self.raggio
return c
# -------------------------------------------
if __name__ == "__main__":
cx = Cerchio(4) # crea un oggetto della classe Cerchio con raggio 4
print("Cerchio")
print("raggio:", cx.raggio)
print("area:", cx.calcolaArea())
print("circonferenza:", cx.calcolaCirconferenza())
che dovrebbe visualizzare:
Cerchio
raggio: 4
area: 50.26548245743669
circonferenza: 25.132741228718345
A questo punto i concetti dovrebbero essere chiari, mentre la sintassi utilizzata ancora no!!!
Cerchiamo di chiarire spiegando il codice nei punti più oscuri:
Terminologia
Chiariamo bene i termini che andremo ad utilizzare per definire una classe manualmente e che sono utilizzati da Python.
class Cerchio:
Questo si capisce: come per dichiarare una funzione si utilizza la clausola def, per definire una classe utilizziamo la clausola class.
Ricordate?
Per evitare confusione fra i concetti mi piace utilizzare i termini classe e oggetto. In OOP solitamente si usa il termine classe per la definizione dell’oggetto, mentre oggetto è l’istanza del tipo classe definito. Prima abbiamo scritto:
cx = Cerchio(4)
Bene: cx è un oggetto di tipo Cerchio, un’istanza della classe, mentre Cerchio è la classe.
self
Avrete notato questo parametro predefinito nelle funzioni della classe e per inizializzare la variabile membro. Questo parametro viene automaticamente istanziato da Python in questo modo:
cx.area() diventa Cerchio.area(cx)
In questo modo il parametro self permette accesso alla classe, ma l’utente della classe non ha bisogno di occuparsene. Spero sia chiaro, più facile di così non riesco. In pratica, la variabile self va messa come primo membro di ogni metodo di una classe, in modo da poter collegare il metodo alla classe stessa.
Metodi che iniziano (e finiscono) con doppio underscore __ Ce ne sono
parecchi in ogni classe e hanno ognuno un compito specifico. Vengono
definite Funzioni Speciali. Ne introdurremo molte altre più avanti. La
cosa importante da capire su di esse è che questi metodi non vanno
eseguiti “volontariamente” (ad esempio come fareste con il metodo
“area()” della classe Cerchio se voleste calcolarne l’area) ma vengono
eseguiti automaticamente in determinate situazioni. Quindi ogni volta
che incontrate una funzione speciale dovete farvi sempre due domande:
- In quale momento particolare questa funzione speciale viene eseguita automaticamente?
- Voglio modificare il comportamento della classe in quel caso particolare? Bene, se la risposta a questa domanda è sì significa che dovete re-implementare quella funzione. Man mano vedremo come.
Per adesso vediamo le 2 funzioni speciali più comuni in assoluto:
def __init__( self , . . . )-
Questa particolare funzione speciale viene eseguita automaticamente quando si definisce un oggetto di una classe. La sua implementazione serve per dare un valore iniziale agli attributi della classe, siano essi inseriti come parametri della stessa o inizializzati ad un valore scelto dal programmatore.
Vediamo un esempio per chiarirci al meglio le idee. Definiamo la classe Quadrato. L’utente potrà scegliere il lato del Quadrato, ma inizialmente esso sarà disegnato con sfondo bianco e lati neri.
class Quadrato:
def __init__(self, lato):
self.lato = lato
self.coloreSfondo = "bianco"
self.coloreBordo = "nero"
# ...
# nella riga sotto viene eseguita automaticamente la funzione __init__
obj = Quadrato(4)
# si definisce così un oggetto della classe Quadrato, di lato 4
# con colore di sfondo bianco e colore del bordo nero.
def __str__ (self)-
La funzione
__str__serve per visualizzare in maniera semplice informazioni sulla classe (praticamente per visualizzare il valore dei suoi attributi). Questa funzione viene eseguita automaticamente quando si esegue la funzione print() con parametro un oggetto della classe.La funzione
__str__non prende MAI parametri e ritorna sempre una stringa, ricordatelo! Ad esempio, definiamo la funzione__str__per la classe Quadrato definita sopra:
def __str__(self):
s = ""
s += "Quadrato di lato " + str(self.lato)
s += ", sfondo " + self.coloreSfondo
s += ", bordo " + self.coloreBordo
return s
...
# riferito all’oggetto obj definito prima
print(obj)
# visualizzerà "Quadrato di lato 4, sfondo bianco, bordo nero"
Esercizio svolto: la classe Rettangolo
Definire un oggetto Rettangolo, tramite i parametri base e altezza e implementare i metodi per il calcolo dell’area e del perimetro. Fornire un test per un Rettangolo di base = 5 cm e altezza = 3 cm, visualizzando i parametri e calcolando area e perimetro dello stesso.
class Rettangolo:
def __init__(self, b = 0, h = 0):
self.base = b
self.altezza = h
def __str__(self):
s = "Rettangolo: " + str(self.__dict__)
return s
def calcolaArea(self):
a = self.base * self.altezza
return a
def calcolaPerimetro(self):
p = 2*(self.base + self.altezza)
return p
if __name__ == "__main__":
ret = Rettangolo(5,3)
print(ret)
print("Base:", ret.base)
print("Altezza:", ret.altezza)
print("Area:", ret.calcolaArea())
print("Perimetro:", ret.calcolaPerimetro())
Tip
self.__dict__
self.__dict__è una variabile automatica degli oggetti Python che contiene il dizionario delle variabili membro!Per ogni attributo, il nome dello stesso diventa una chiave del dizionario, mentre il suo valore diventa il valore associato!
Esercizi di comprensione
Prima di andare avanti, proviamo a definire alcune classi e proporre con esse qualche test in cui inserire e modificare i valori degli attributi definiti e visualizzare i risultati delle chiamate ai metodi definiti.
Per ognuna ricordate che è obbligatorio definire sempre la funzione
__init__ e la funzione __str__ e verificarne il funzionamento,
definendo un oggetto della classe e visualizzandone i valori con la
print().
Esercizio 1101
Definire la classe Persona con attributi nome, cognome, data e luogo di
nascita, sesso (M/F). La funzione di __init__ della classe deve prevedere tutti i parametri
necessari per l’inizializzazione degli oggetti.
Definite un maschio e una femmina della classe a libera scelta.
Esercizio 1101 bis
Definire la classe Persona con attributi nome, cognome, data e luogo di
nascita, sesso (M/F). La funzione di __init__ della classe non deve
avere argomenti.
Definite un maschio e una femmina della classe a libera scelta.
Esercizio 1101 ter
Definire la classe Persona con attributi nome, cognome, data e luogo di
nascita, sesso (M/F). La funzione di __init__ della classe deve prevedere parametri e valori di default
per tutti i parametri tranne nome e cognome.
Definite un maschio e una femmina della classe a libera scelta.
Esercizio 1102
Definire la classe TriangoloRettangolo inserendo come attributi i due cateti. Aggiungere i metodi per il calcolo dell’ipotenusa, dell’area e del perimetro.
Definire un oggetto della classe TriangoloRettangolo con cateti uguali a 4 cm e 3 cm, visualizzare i suoi attributi e calcolare l’ipotenusa, l’area e il perimetro.
Esercizio 1103
Definire la classe Animale con attributi nome e specie. Aggiungere il metodo “corri” (ritorna la stringa “sto correndo…”) e “mangia” (ritorna la stringa “sto mangiando”).
Definire un cane di nome “Piero” e farlo correre e mangiare. Visualizzare i suoi attributi.
Esercizio 1104
Definire la classe Persona con attributi nome, età e sesso (M/F). La
funzione di __init__ della classe deve prendere come argomento solo
il nome della persona, mentre l’età va impostata automaticamente a ZERO
e il sesso a “M” (o “F”, scegliete voi).
Aggiungere i metodi invecchia (aggiunge un anno di età) e saluta (restituisce
“signore” o “signora” a seconda del sesso della persona).
Definire 2 persone: “Augusto”, maschio di 47 anni e “Marianna”, femmina di 44
anni. Utilizzare i metodi invecchia e saluta per entrambi,
procedere poi a visualizzare gli attributi di entrambi.
Esercizio 1105
Definire la classe ContoCorrente con attributi proprietario e capitale;
il proprietario va definito tramite parametro della funzione
__init__ mentre il capitale va inizializzato a ZERO.
Definire il metodo deposita, che prende un parametro reale, controlla che sia
positivo ed eventualmente lo aggiunge al capitale e il metodo
preleva, che prende anch’esso un parametro reale, verifica che il
parametro sia positivo, verifica che sia minore del capitale ed
eventualmente lo sottrae da esso (il metodo ritorna True se possibile,
False altrimenti).
Definire il Conto Corrente di “Gigetto”. Depositare in esso 1000 euro. Prelevare 600 euro per 2 volte. La seconda volta l’operazione dovrebbe fallire. Visualizzare dopo ogni operazione i valori dell’oggetto con la funzione print().
Esercizio 1106
Definire la classe Crittografia con attributo un numero intero che
indica lo spiazzamento dei caratteri. Questo numero sarà utilizzato per criptare le stringhe traslando i caratteri
di numero posti sull’alfabeto: ad esempio se il numero è 3 e vuoi criptare la stringa “ale”, prendi la “a” e vai avanti
di 3 sull’alfabeto (“d”), prendi la “l” e vai avanti di 3 sull’alfabeto (“o”) e lo stesso con la “e”: la stringa criptata ottenuta è “doh”.
La classe contiene due funzioni:
-
cripta, che prende una stringa come parametro e restituisce la stessa trasformata (criptata) secondo la regola descritta sopra; -
decripta, che prende una stringa come parametro e la rimette “a posto” (la decripta).
Definire due oggetti della classe Crittografia con parametro a piacere e provare a “criptare” e “decriptare” una stringa, verificando che la stringa decriptata sia uguale a quella inserita prima di essere criptata.
Esercizio 1107
Definire la classe Automobile con attributi marca, modello, velocità e numero di persone trasportate. La funzione init prende come parametri la marca e il modello e imposta a ZERO gli altri attributi. Implementare i seguenti metodi:
faiSalirePersona: se l’auto è ferma, aggiunge una persona al numero di persone trasportate fino ad un massimo di 5. Ritorna True se è stato possibile aggiungere una persona, False altrimenti.faiScenderePersona: se l’auto è ferma, toglie una persona al numero di persone trasportate (Se possibile ovviamente). Ritorna True se è stato possibile togliere una persona, False altrimenti.accelera: se è presente almeno una persona (l’autista) aggiunge 20 kmh alla velocità di marcia, fino ad un massimo di 120 kmh. Ritorna True se è stato possibile aumentare la velocità, False altrimenti.rallenta: se è presente almeno una persona (l’autista), toglie 20 kmh alla velocità di marcia, ovviamente fino a fermarsi. Ritorna True se è stato possibile diminuire la velocità, False altrimenti.frena: azzera la velocità di marcia. Ritorna True se è stato possibile frenare, False altrimenti.
Definire un oggetto della classe Automobile e progettare un test in modo che ognuna delle funzioni venga eseguita almeno 2 volte, una ritornando True, una ritornando False.
Esercizio 1108
Definire la classe TrapezioRettangolo, che prende come parametri la base minore, la base maggiore e l’altezza del Trapezio.
Definire i metodi calcolLatoObliquo, calcolaArea, calcolaPerimetro. Dichiarare un oggetto
TrapezioRettangolo e procedere a visualizzare i suoi parametri, la sua
area e il suo perimetro.
Esercizio 1109
Definire la classe “Orario”, con parametri i tre interi per ore, minuti e secondi. Implementare inoltre le seguenti funzioni:
contaSecondiDaMezzanotte(): restituisce l’intero che rappresenta il numero di secondi trascorsi dalla mezzanotte.aggiungiTempo(ore, minuti, secondi): aggiunge tempo all’orario corrente. Ad esempio, se l’oggetto della classe Orario segna le 03:14:22 e si esegue su di esso la funzione aggiungoTempo(1,2,3) l’orario diventa le 04:16:25. Attenzione a quando “il giro ricomincia”…verificaMomento(): ritorna la stringa “mattina” se orario è fra le 8 e le 13, “pomeriggio” se fra 13 e 20, “sera” fra 20 e 23, “notte” altrimenti
Esercizio 1110
Definire la classe CartaFedeltà, per la gestione degli utenti di un
grande magazzino. La carta fedeltà è nominativa (appartiene ad un solo
cliente) e consente l’accumulo dei punti (attraverso il metodo
accumulaPunti(soldiSpesi)) calcolati sulla base della spesa effettuata:
ogni 12€ di spesa si aggiunga un punto. All’inizio ovviamente il numero
di punti è ZERO. Il cliente può decidere, in ogni momento, di usufruire
di una parte dei punti accumulati per l’ottenimento di un premio
(implementare un opportuno metodo utilizzaPunti(quantita): il metodo
ritorna True… False altrimenti).
Definire le carte fedeltà per 2 clienti. Il primo cliente fa una spesa pari a 150€. Il secondo cliente fa una spesa di 300€. In seguito, il primo cliente fa una spesa pari a 1500€ e, dopo aver pagato, decide di utilizzare 100 dei punti accumulati per ritirare un premio. Il secondo cliente chiede di utilizzare 50 dei punti accumulati.
Esercizio 1111
Definire la classe Giocatore con nome, numero di maglia e ruolo ricoperto. Il nome del giocatore va impostato tramite parametro, mentre il numero va impostato inizialmente a ZERO e il ruolo a “X”.
Definire la funzione impostaRuolo(stringa) che prende una stringa come
parametro e imposta il ruolo del giocatore. Fare in modo che i ruoli
accettabili siano solo “P” (per portiere), “D” (per difensore),
“C” (per centrocampista), “A” (per attaccante). La funzione ritorna
True o False a seconda del fatto se il ruolo viene effettivamente
modificato oppure no.
Definire la funzione cambiaNumero(intero) che modifica il numero di
maglia solo se esso varia fra 1 e 99. Anche qui, la funzione ritorna
True o False…
DIFFICILE E OPZIONALE: è possibile fare in modo anche che i numeri di maglia dei vari giocatori siano tutti diversi fra loro? Proponi una soluzione al problema. Definire un giocatore di nome “Edoardo”. Tramite la funzione impostaRuolo, impostare il suo ruolo ad attaccante (con “A”) e poi modificarlo a terzino (“T”). La seconda modifica dovrebbe fallire. Utilizzare la funzione cambiaNumero per modificare il suo numero a 103 e poi a 7. Definire un giocatore di nome “Alessandro”. Tramite la funzione impostaRuolo, impostare il suo ruolo a centrocampista (con “C”). Utilizzare la funzione cambiaNumero per modificare il suo numero a 7 (se avete implementato la parte opzionale, dovrebbe fallire) e poi a 11.
Esercizio 1112
Definire la classe EstrazioneLotto. La classe contiene una lista,
inizialmente vuota, di stringhe che rappresentano le città ove ci sono
le ruote di estrazione. Prevedere un metodo aggiungiRuota(stringa) che
verifica se il nome della città da inserire sia già presente nelle ruote
e in caso negativo la aggiunge alla lista. Definire una funzioni
estrai(stringa) che verifica se il nome della città passata come
parametro è presente nelle ruote. In caso negativo, ritorna una tupla
vuota. In caso positivo, ritorna una tupla di 5 numeri casuali, diversi
fra loro, fra 1 e 90.
ULTERIORE DIFFICOLTA’ (opzionale): fare in modo che i numeri della tupla siano ordinati in senso crescente.
Definire una funzione eseguiEstrazioniDellaSettimana() che permette a tutte le
ruote presenti di estrarre i numeri del lotto. La funzione ritorna un
dizionario che ha come chiavi i nomi delle ruote in cui avvengono le
estrazioni e come valori le tuple dei 5 numeri estratti.
Definire un oggetto della classe EstrazioneLotto.
Inserire tramite il metodo aggiungiRuota le seguenti città: Jesi, Senigallia, Ancona, Jesi (dovrebbe fallire, già presente), Monsano, Moie, Ancona (err). Visualizzare il risultato della funzione estrai(“Jesi”), estrai(“Ancona”), estrai(“Milano”). L’ultima dovrebbe ritornare una tupla vuota. Eseguire la funzione estrazioniDellaSettimana e visualizzare ordinatamente il dizionario ottenuto, con una visualizzazione simile a questa:
- Jesi: (1,5,9,23,89)
- Senigallia: (23,34,45,67,88)
- Ancona: (39, 43, 44, 78, 81)
Esercizio 1113: Agenda
Una agenda contiene una serie di impegni identificati con una descrizione generica e con il giorno in cui questo impegno è preso. Esempi di impegni potrebbero essere:
- Calcetto, Lunedì
- Parrucchiere, Mercoledì
- Pizza con gli amici, Venerdì
Quando si crea un oggetto della classe Agenda si parte (ovviamente) con una lista di impegni vuota! La classe presenta inoltre le seguenti funzioni:
inserisciImpegno ( descrizione, giorno ): prende i dati dai parametri della funzione e inserisce il nuovo impegno in agenda. Non ritorna nulla.elencaImpegniDi ( giorno ): prende come parametro un giorno della settimana e ritorna la lista delle descrizioni degli impegni per quel giorno.rimuoviImpegno ( descrizione ): prende come parametro la descrizione di un impegno e, se lo trova in agenda, rimuove l’impegno corrispondente. Ritorna True se viene rimosso un impegno, False altrimenti.trovaImpegno ( descrizione ): prende come parametro la descrizione di un impegno e, se lo trova in agenda, ritorna il giorno in cui quell’impegno è stato preso. Se non trova nulla, ritorna la stringa “NON TROVATO”.
Definire la classe Agenda, un oggetto della classe stessa, inserire in essa almeno 4 impegni (tramite la funzione inserisciImpegno) e fare un test di utilizzo di tutte le altre funzioni
Esercizio 1114: Rubrica
Una rubrica contiene una lista di contatti. Ogni contatto comprende un nome ed un numero di telefono (memorizzabile comunque come una stringa). Esempi di contatti potrebbero essere:
- prof , 555-12345
- tizio di Dallas, 214-748-3647
- casa, 0731-24680
Quando si crea un oggetto della classe Rubrica, si parte con un elenco di contatti vuoto. La classe presenta inoltre le seguenti funzioni:
inserisciContatto ( nome , numero ): prende i dati dai parametri della funzione e inserisce il nuovo contatto in rubrica. Non ritorna nulla.modificaContatto ( nome , nuovoNumero ): modifica il contatto identificato dal parametro nome, aggiornando il suo numero con il parametro nuovoNumero. Ritorna True se il numero viene aggiornato, False altrimenti.rimuoviContatto ( nome ): rimuove dalla rubrica il contatto identificato dal parametro nome. Ritorna True se è stato possibile rimuovere il contatto, false altrimenti.trovaNumero ( nome ): ritorna il numero del contatto con nome la stringa riportata nel parametro. Se non si trova nessun contatto, ritorna la stringa “NESSUN CONTATTO”.
Definire la classe Rubrica, un oggetto della stessa, inserirvi almeno 4 contatti (tramite la funzione inserisciContatto) e fare un test di utilizzo di tutte le altre funzioni.
Accesso agli attributi
Nei linguaggi di programmazione più antichi e strutturati (come C++ e Java) esiste il concetto di visibilità di un membro (un attributo o un metodo) della classe. Ogni membro può essere specificato come:
Nei linguaggi (molto) strutturati:
public, ovvero ereditabile e visibile a chiunque utilizzi la classe e le sue istanze;protected, ovvero visibile solo all’interno della propria classe di appartenenza e dall’interno di ogni sua classe derivata;private, ovvero non ereditabile e visibile solo all’interno della propria classe di appartenenza. Questa è la visibilità di default.
Questi concetti in Python (che è un linguaggio molto più… moderno. Non so quanto in questo caso sia un bene…) sono stati tradotto in maniera molto particolare.
In Python:
public, ovvero ereditabile e visibile a chiunque utilizzi la classe e le sue istanze; Questa è la visibilità di default in Pythonprotected, ogni variabile membro che inizia con underscore_. Funziona come pubblico. Il livello protetto è sociale (ovvero i programmatori per educazione, dovrebbero evitare l’utilizzo esterno alla classe)private, ogni variabile membro che inizia con doppio underscore__. Non ereditabile e Visibile solo all’interno della propria classe di appartenenza.
La visibilità di default è diventata quella public per eliminare alla radice qualunque problema di accesso. Di sicuro una mossa a favore di chi è poco esperto.
Per ottenere la visibilità protected basta iniziare il nome della variabile con un underscore _. Una roba tipo:
class Persona:
def __init__(self, name):
self._nome = name
Attenzione però! Il livello di visibilità che si introduce con un underscore è puramente sociale!!! Questo significa che se qualcuno volesse accedere alla vostra variabile dall’esterno, potrà sempre farlo e l’interprete Python non si lamenterà! Qualunque programmatore Python però… lo considererebbe alquanto scortese!
p = Persona("Ciccio")
print(p._nome) # ecco... adesso sono un programmatore maleducato. Corretto, ma maleducato!!!
La visibilità private si ottiene iniziando il nome di una variabile (o di una funzione membro) con 2 underscore. Stavolta però le cose cambiano…
l’interprete custodisce eccome i membri privati e riporta un AttributeError. Insisto col mio esempio:
class Persona:
def __init__(self, name):
self.__nome = name
p = Persona("Ciccio")
print(p.__nome) # ERRORE!!! Adesso sono anche un somaro...
Capito come Python rende la visibilità dei membri delle classi, passiamo alla domanda di concetto: che cosa può interessarci tutto ciò? Risposta: a proteggere le variabili! Dall’esterno. Da un uso libero. Da chi vuole visualizzarle senza permesso. Da chi vuole modificarne il valore come crede.
Inserisco un pezzo di codice di linguaggio C++ nella speranza di rendere evidente il concetto:
class Persona
{
// attributi privati (in C gli attributi si segnano con un solo underscore)
string _nome;
// metodi pubblici
public:
// setter method: permette di impostare il valore dell'attributo _nome
bool setNome(string n) {
if (n == "")
return False;
_nome = n;
return True;
};
// getter method: ritorna il valore dell'attributo _nome
string nome() const {
return _nome;
};
};
Anche se non conosciamo il linguaggio C++, quello che vediamo è molto semplice da comprendere: l’attributo _nome è privato e quindi
NON modificabile direttamente dall’esterno della classe Persona.
Questo significa che un codice del genere da errore:
Persona pers;
pers._nome = "Andrea"; // ERRORE! L'attributo è PRIVATO!!!
Questa sovrastruttura, tipica dei linguaggi compilati come C e Java, fornisce un livello di protezione aggiuntivo ai valori delle variabili membro!
Se il programmatore vuole che all’esterno venga visualizzato il nome della Persona, inserisce nella classe un metodo getter (tipicamente nome())
cioè un metodo che ritorna il valore di _nome.
Se il programmatore vuole che dall’esterno sia possibile modificare il nome della Persona, inserisce nella classe un metodo setter (tipicamente setNome(string n)),
cioè un metodo che permette di impostare un nuovo valore per _nome (se il valore accettabile!)
Warning
Privato o protetto?
Adesso che abbiamo capito il concetto di visibilità (a grandi linee, mi rendo conto…) facciamo una semplificazione: per ora utilizzeremo solo le visibilità:
- pubbliche (cioè lasceremo tutto identico a prima…)
- private (quindi metteremo DUE underscore e…)
La visibilità protetta sarà reintrodotta nel prossimo capitolo!!!
Nel linguaggio Python esiste uno strumento che permette una implementazione analoga (ovvero… simile, NON uguale) a questo livello di protezione delle variabili che si definisce il Python @property decorator.
Un codice funzionalmente analogo a quello sopra in C++, che scriviamo in Python, potrebbe essere fatto così:
class Persona:
def __init__(self, name):
# il ragionamento è identico con la visibilità protetta!!!
self.__nome = name
@property
def nome(self):
""" DOCUMENTA QUI LA TUA PROPRIETA': il nome della persona """
return self.__nome
@nome.setter
def nome(self, name):
if name == "":
raise ValueError("Tutte le persone hanno un nome...")
self.__nome = name
return
Mamma mia, quante cose da spiegare… Cominciamo!
def __init__(self, name):
self.__nome = name
Secondo la convenzione già esposta, si intende mantenere nascosta la variabile membro __nome.
@property
def nome(self):
""" DOCUMENTA QUI LA TUA PROPRIETA': il nome della persona """
return self.__nome
Questo decoratore (@property con la chiocciolina davanti si dice decoratore in Python) fa in modo che nome (in questo caso, il nome della funzione)
diventi una Python property. La docstring interna specifica la descrizione della proprietà.
Una proprietà è una caratteristica tipica di un oggetto, a cui possono essere aggiunti funzioni
getter,settere… un’altra che non studieremo!!!.
La definizione della proprietà implica automaticamente l’esistenza della funzione getter, quella che ritorna il valore. Se vogliamo inserire anche la funzione
setter, che permette i modificare il valore della proprietà, allora scriviamo:
@nome.setter
def nome(self, name):
if name == "":
# questa la ignoriamo??? La scriviamo così e basta :D
raise ValueError("Tutte le persone hanno un nome...")
self.__nome = name
return
A questo punto, la proprietà nome diventa una sorta di nuova variabile membro della classe, che invoca automaticamente le funzioni getter e setter ad essa collegate:
pers = Persona()
pers.nome = "Andrea" # esegue la funzione "setter" automaticamente
print("Nome:", pers.nome) # esegue la funzione "getter" automaticamente
Questo modo di implementare le variabili membro di una classe è la via scelta dai programmatori Python per gestire i valori delle stesse.
Read-Only Properties
Capire il concetto di proprietà di sola lettura è molto semplice: basta dimenticarsi di aggiungere ad una prorietà la funzione setter… ed ecco che diventa impossibile modificarla al di fuori della classe!!!
class Oggetto:
def __init__(self):
self.__valore = 0
@property
def valore(self):
return self.__valore
obj = Oggetto()
obj.valore = 5 # ERRORE!!!! NO setter!!!
Questa caratteristica delle proprietà ritorna utile quando abbiamo delle caratteristiche derivate da altre: ad esempio, l’area di un rettangolo può essere definita come proprietà read-only, in modo che essa venga calcolata automaticamente a partire dai valori attuali di base e altezza, ma non possa essere impostata da codice. Proviamo:
class Rettangolo:
def __init__(self, b, h):
self.base = b
self.altezza = h
@property
def area(self):
return self.base * self.altezza
ret = Rettangolo(5,4)
print("Area:", ret.area) # scrive "Area: 20"
ret.area = 30 # ERRORE!!! Quali sono base e altezza di un rettangolo di area 30? Boh...
Provate a verificare la vostra comprensione coi seguenti esercizi.
Esercizi sulle proprietà
Esercizio 1200: SVOLTO
(Ri)definire la classe Rettangolo, facendo in modo che base e altezza siano numeri comunque positivi e che area e perimetro siano calcolate automaticamente come proprietà in sola lettura.
Il codice che segue mi sembra alquanto chiaro. Provate a leggerlo con calma, a copiarlo sul vostro computer e a provare alcune modifiche per essere sicuri di aver capito tutto!
class Rettangolo:
def __init__(self, b, h):
# i valori passati nella init NON sono sottoposti al controllo
# imposto dai decoratori sotto, quindi...
self.__base = abs(b)
self.__altezza = abs(h)
def __str__(self):
return "Rettangolo: " + str(self.__dict__)
@property
def base(self):
return self.__base
@base.setter
def base(self, value):
if value < 0:
raise ValueError("La base di un rettangolo non può essere negativa")
self.__base = value
return
@property
def altezza(self):
return self.__altezza
@altezza.setter
def altezza(self, value):
if value < 0:
raise ValueError("L'altezza di un rettangolo non può essere negativa")
self.__altezza = value
return
@property
def area(self):
return self.__base * self.__altezza
@property
def perimetro(self):
return (self.__base + self.__altezza) * 2
if __name__ == "__main__":
r1 = Rettangolo(5,3)
print(r1)
# se decommenti sotto vedi il ValueError con la nostra spiegazione
#r1.base = -12
r1.base = 12
print("base:", r1.base)
print("altezza:", r1.altezza)
# area e perimetro sono proprietà NON funzioni, quindi vanno SENZA parentesi
print("area:", r1.area)
print("perimetro:", r1.perimetro)
# se decommenti sotto, vedi l'interprete lamentarsi per l'assegnazione
#r1.area = 23
E adesso sotto con un esercizio analogo!
Esercizio 1201
(Ri)definire la classe Cerchio, facendo in modo che il raggio sia un numero sempre positivo e che il diametro, l’area e la circonferenza siano calcolate automaticamente come proprietà in sola lettura.
Esercizio 1202
Definire una classe Temperatura, che NON prende parametri in fase di inizializzazione. Ha una sola variabile membro, la temperatura in gradi Kelvin, che (come sapete) deve essere non negativa. Presenta inoltre due proprietà in sola lettura per visualizzare la temperatura in gradi Celsius e in gradi Farheneit.
Tip
Kelvin, Celsius, Farheneit
temp:K C = temp - 273.15 F = temp * 9 / 5 - 459.67
Esercizio 1203
Definire la classe Data con giorno, mese, anno che NON possono essere modificati (capito come fanno le classi Datetime???)
Esercizio 1204
Definire la classe Contatore. Essa ha un solo valore membro (la conta, appunto). La conta parte da zero, non può essere modificata e viene incrementata ogni volta che viene visualizzata.
Membri di Classe
Fino ad adesso, ogni attributo che abbiamo definito nelle nostre classi era un attributo di istanza: ogni oggetto aveva la sua copia, con il suo valore indipendente dagli altri oggetti della stessa classe.
Ma a volte ci sono informazioni che non appartengono a un singolo oggetto, bensì alla classe intera. Pensate ad esempio a un contatore: quante istanze di questa classe sono state create? Non ha senso chiederlo a un singolo oggetto, ha senso chiederlo alla classe.
Per gestire questi casi, Python mette a disposizione tre strumenti che andremo a vedere in questo capitolo:
- le variabili di classe
- i metodi di classe (
@classmethod) - i metodi statici (
@staticmethod)
Variabili di classe
Una variabile di istanza è definita dentro __init__ tramite self, e ogni oggetto ne ha una copia propria:
class Persona:
def __init__(self, nome):
self.nome = nome # variabile di ISTANZA: ogni oggetto ha la sua
p1 = Persona("Gino")
p2 = Persona("Pina")
# p1.nome è "Gino", p2.nome è "Pina": copie indipendenti
Una variabile di classe viene invece definita dentro la classe ma fuori da qualsiasi metodo, e appartiene alla classe stessa: tutti gli oggetti la condividono e ne vedono lo stesso valore.
class Persona:
numero_persone = 0 # variabile di CLASSE: condivisa fra tutti gli oggetti
def __init__(self, nome):
self.nome = nome
Persona.numero_persone += 1 # si accede con il nome della classe, non con self
p1 = Persona("Gino")
p2 = Persona("Pina")
p3 = Persona("Gigi")
print(Persona.numero_persone) # stampa 3
Notate la differenza di accesso: le variabili di istanza si leggono e scrivono tramite self, le variabili di classe tramite il nome della classe (Persona.numero_persone).
Note
Variabile di classe vs variabile di istanza
- Una variabile di istanza (
self.nome) appartiene al singolo oggetto. Ogni oggetto ha la sua copia.- Una variabile di classe (
Classe.nome) appartiene alla classe. Tutti gli oggetti la condividono.
Metodi di Classe (@classmethod)
Un metodo normale riceve come primo parametro self, ovvero il riferimento all’oggetto su cui viene chiamato.
Un metodo di classe riceve invece come primo parametro cls, ovvero il riferimento alla classe stessa (non a un singolo oggetto).
Si indica con il decoratore @classmethod.
Il caso d’uso più classico è il costruttore alternativo: quando vogliamo offrire più modi di creare un oggetto, ma Python permette
un solo __init__, possiamo aggiungere dei @classmethod che costruiscono l’oggetto a partire da dati in un formato diverso.
Esempio: la classe Orario accetta ore, minuti e secondi. Ma vogliamo anche poter costruire un orario a partire da una stringa nel formato "HH:MM:SS":
class Orario:
def __init__(self, ore, minuti, secondi):
self.ore = ore
self.minuti = minuti
self.secondi = secondi
def __str__(self):
return f"{self.ore:02d}:{self.minuti:02d}:{self.secondi:02d}"
@classmethod
def da_stringa(cls, stringa):
"""Costruisce un Orario da una stringa nel formato HH:MM:SS"""
parti = stringa.split(":")
ore = int(parti[0])
minuti = int(parti[1])
secondi = int(parti[2])
return cls(ore, minuti, secondi) # cls(...) equivale a chiamare Orario(...)
# -------------------------------------------
if __name__ == "__main__":
o1 = Orario(8, 30, 0)
print(o1) # 08:30:00
o2 = Orario.da_stringa("14:45:22")
print(o2) # 14:45:22
cls(ore, minuti, secondi) chiama il costruttore della classe, esattamente come Orario(ore, minuti, secondi): scrivere cls invece del nome della classe
è una buona abitudine perché, se in futuro si eredita la classe, cls si adatta automaticamente alla sottoclasse.
Note
selfvscls
self→ metodo di istanza: riceve l’oggetto su cui è chiamato. Può accedere agli attributi di istanza e di classe.cls→ metodo di classe (@classmethod): riceve la classe stessa. Può accedere e modificare variabili di classe, ma non ha accesso agli attributi di istanza.
Metodi Statici (@staticmethod)
Un metodo statico non riceve né self né cls: è una funzione che sta dentro la classe per motivi organizzativi
(è concettualmente legata ad essa), ma non ha bisogno di accedere né all’istanza né alla classe stessa.
Si indica con il decoratore @staticmethod e si chiama tramite il nome della classe (o tramite un oggetto, anche se è meno leggibile).
L’utilizzo tipico è per funzioni di utilità e validazione legate al dominio della classe:
class Orario:
def __init__(self, ore, minuti, secondi):
self.ore = ore
self.minuti = minuti
self.secondi = secondi
def __str__(self):
return f"{self.ore:02d}:{self.minuti:02d}:{self.secondi:02d}"
@staticmethod
def formato_valido(stringa):
"""Ritorna True se la stringa è nel formato HH:MM:SS, False altrimenti"""
parti = stringa.split(":")
if len(parti) != 3:
return False
ore, minuti, secondi = int(parti[0]), int(parti[1]), int(parti[2])
if 0 <= ore <= 23 and 0 <= minuti <= 59 and 0 <= secondi <= 59:
return True
return False
@classmethod
def da_stringa(cls, stringa):
if not Orario.formato_valido(stringa):
raise ValueError(f"Formato non valido: '{stringa}'. Usare HH:MM:SS")
parti = stringa.split(":")
return cls(int(parti[0]), int(parti[1]), int(parti[2]))
# -------------------------------------------
if __name__ == "__main__":
print(Orario.formato_valido("14:45:22")) # True
print(Orario.formato_valido("25:00:00")) # False (ore fuori range)
print(Orario.formato_valido("pippo")) # False
o = Orario.da_stringa("09:15:30")
print(o) # 09:15:30
Tip
Quando usare cosa?
- Metodo normale (
self): quando devo accedere o modificare gli attributi di quell’istanza.- Metodo di classe (
@classmethod,cls): quando devo accedere a variabili di classe o costruire un nuovo oggetto in modo alternativo.- Metodo statico (
@staticmethod): quando la funzione è concettualmente legata alla classe ma non ha bisogno di accedere né all’istanza né alla classe.
Esercizio svolto: la classe Automobile con contatore
Definiamo la classe Automobile con un contatore di istanze (variabile di classe), un costruttore alternativo
che accetta una stringa nel formato "marca:modello", e un metodo statico di validazione del formato.
class Automobile:
numero_automobili = 0 # variabile di classe: conta le auto create
def __init__(self, marca, modello):
self.marca = marca
self.modello = modello
Automobile.numero_automobili += 1
def __str__(self):
return f"Automobile: {self.marca} {self.modello}"
@classmethod
def da_stringa(cls, stringa):
"""Crea un'Automobile da una stringa nel formato 'marca:modello'"""
if not Automobile.formato_valido(stringa):
raise ValueError(f"Formato non valido: '{stringa}'. Usare 'marca:modello'")
parti = stringa.split(":")
return cls(parti[0].strip(), parti[1].strip())
@staticmethod
def formato_valido(stringa):
"""Ritorna True se la stringa contiene esattamente un ':' con testo da entrambi i lati"""
parti = stringa.split(":")
return len(parti) == 2 and len(parti[0].strip()) > 0 and len(parti[1].strip()) > 0
# -------------------------------------------
if __name__ == "__main__":
a1 = Automobile("Fiat", "Panda")
a2 = Automobile("Renault", "Clio")
a3 = Automobile.da_stringa("Ford : Fiesta")
print(a1)
print(a2)
print(a3)
print("Auto create:", Automobile.numero_automobili) # 3
# test del metodo statico
print(Automobile.formato_valido("Toyota:Yaris")) # True
print(Automobile.formato_valido("pippo")) # False
Output atteso:
Automobile: Fiat Panda
Automobile: Renault Clio
Automobile: Ford Fiesta
Auto create: 3
True
False
Esercizi di comprensione
Esercizio 1301
Definire la classe Libro con attributi titolo e autore. Aggiungere una variabile di classe numero_libri
che tenga traccia di quanti oggetti Libro sono stati creati.
Creare almeno 4 libri e verificare che Libro.numero_libri riporti il valore corretto.
Esercizio 1302
Riprendere la classe Giocatore dell’esercizio 1111. Aggiungere una variabile di classe numero_giocatori
che si incrementa ad ogni creazione di un nuovo giocatore.
Creare una squadra di 5 giocatori e verificare il contatore.
Esercizio 1303
Definire la classe Data con attributi giorno, mese, anno.
Aggiungere un @classmethod da_stringa(cls, stringa) che costruisce un oggetto Data
a partire da una stringa nel formato "GG/MM/AAAA".
Aggiungere un @staticmethod formato_valido(stringa) che verifica se la stringa
è nel formato corretto (solo controllo del formato, non della validità della data).
Testare la creazione di una data tramite il costruttore normale e tramite il classmethod. Testare il metodo statico con almeno una stringa valida e una non valida.
Esercizio 1304
Definire la classe ContoCorrente (riprendendo l’esercizio 1105) e aggiungere:
- una variabile di classe
numero_contiche conta i conti creati; - una variabile di classe
totale_depositiche tiene il totale di tutti i versamenti eseguiti su tutti i conti (si aggiorna ogni volta che un deposito va a buon fine); - un
@classmethodreport(cls)che stampa il numero di conti aperti e il totale dei depositi complessivi.
Creare 3 conti, eseguire alcuni depositi e prelievi, poi chiamare ContoCorrente.report().
Esercizio 1305: DIFFICILE
Definire la classe Dado con attributo facce (numero di facce del dado, default 6).
Aggiungere:
- un
@staticmethodlancia_dado(facce)che ritorna un numero casuale fra 1 efacce; - un metodo normale
lancia(self)che usa il metodo statico per lanciare il dado dell’istanza; - un
@classmethoddado_percentuale(cls)che crea e ritorna un dado da 100 facce; - un
@classmethoddado_destino(cls)che crea e ritorna un dado da 20 facce.
Testare tutti i metodi e i costruttori alternativi.
Esercizio 1306
Definire la classe Orario completa come mostrata negli esempi del capitolo
(attributi ore, minuti, secondi; __init__; __str__; formato_valido come @staticmethod; da_stringa come @classmethod).
Aggiungere inoltre:
- il metodo normale
in_secondi(self)che ritorna il numero di secondi trascorsi dalla mezzanotte; - un
@classmethodda_secondi(cls, secondi_totali)che costruisce unOrarioa partire dal numero di secondi trascorsi dalla mezzanotte.
Verificare che Orario.da_secondi(o.in_secondi()) ricostruisca correttamente l’orario originale per almeno 3 valori diversi.
Ereditarietà
L’ereditarietà è un concetto tipico della programmazione orientata agli oggetti che ovviamente qui sarà declinato in salsa Python 😉
Essa è la capacità di definire una classe (la classe derivata) a partire da una già esistente (la classe base), facendo in modo che la nuova classe erediti tutte le caratteristiche della classe iniziale.
class Persona:
def __init__(self, name):
self.nome = name
def saluta(self):
return f"buongiorno, mi chiamo {self.nome}"
class Studente(Persona): # la classe Studente "deriva" dalla classe Persona
pass
# ------------------------------------
if __name__ == "__main__":
p = Studente("Gino")
print(p.saluta()) # la classe Studente "eredita" il metodo saluta()
# scrive: buongiorno, mi chiamo gino
Fin qui, mi sembra molto semplice… La classe Studente deriva dalla classe Persona e quindi eredita tutte le sue caratteristiche, in particolare, le sue funzioni! Quindi può usare la funzione saluta() ereditata.
Adesso pensate che uno studente voglia comportarsi in modo diverso da una generica Persona… salutando in maniera più sciolta, più informale,
E’ possibile modificare il comportamente di una funzione ereditata semplicemente riscrivendola da capo nella classe derivata: questo comportamento si definisce OVERRIDING, cioè la nuova funzione implementata va a sovrapporsi alla funzione ereditata nascondendola dall’esecuzione.
class Persona:
def __init__(self, name):
self.nome = name
def saluta(self):
return f"buongiorno, mi chiamo {self.nome}"
class Studente(Persona): # la classe Studente "deriva" dalla classe Persona
def saluta(self):
return f"bella raga, io sono {self.nome}!!!"
# ------------------------------------
if __name__ == "__main__":
p = Studente("Gino")
print(p.saluta()) # la classe Studente utilizza il metodo saluta() reimplementato
# scrive: bella raga, io sono gino!!!
Visti questi due esempi, possiamo azzardare a dare una definizione del comportamento dell’ereditarietà in Python.
In Python il meccanismo dell’ereditarietà si abilita semplicemente scrivendo:
class Derivata(Base): ...il che significa che la nuova classe Derivata eredita tutte le caratteristiche della classe Base, da cui deriva.
Ok, ma… quali sono queste caratteristiche che vengono ereditate??? E qui, la risposta mi sembra abbastanza evidente: le funzioni!!!
In Python, dal punto di vista pratico, l’ereditarietà permette il passaggio di tutte le funzioni presenti nella classe Base alla classe Derivata
Quando attivi l’ereditarietà, tutte le funzioni definite nella classe Base passano automaticamente nella classe Derivata.
Warning
Se pensate alle semplici classi che scriviamo adesso, l’ereditarietà è solo una inutile complicazione.
Se pensate però al prossimo step, in cui importeremo librerie di classi fighissime (fatte da altri!!!) e con funzionalità incredibili, capite bene che programmare in OOP diventerà una sciccheria!
Con una riga di codice avrete già implementato il mondo ☺️
E funziona tutto!!!
Se vuoi modificare il comportamento di una funzione devi agire tramite overriding reimplementando la funzione nella classe Derivata.
I metodi ereditati dalla classe Base, ma reimplementati tramite overriding non vengono cancellati, ma sono semplicemente nascosti dal metodo reimplementato (overriding si potrebbe tradurre con prevalente, nel senso che il metodo reimplementato è prevalente rispetto a quello ereditato: non lo cancella, lo prevarica).
Per accedere ai metodi ereditati dalla classe Base, ma nascosti tramite overriding, Python mette a disposizione la funzione super().
Vediamo un esempio di utilizzo:
class Persona:
def __init__(self, name):
self.nome = name
def saluta(self):
return f"buongiorno, mi chiamo {self.nome}"
class Studente(Persona): # la classe Studente "deriva" dalla classe Persona
def saluta(self):
s = super().saluta()
return s + " e sono uno studente"
# ------------------------------------
if __name__ == "__main__":
p = Studente("Gino")
print(p.saluta()) # la classe Studente utilizza il metodo saluta() reimplementato
# scrive: buongiorno, mi chiamo Gino e sono uno studente
La funzione super() permette di accedere a tutti i metodi ereditati dalla classe Base (la classe Persona, nel caso della classe Studente).
Capisco che l’esempio sopra è un pò sempliciotto, però rende l’idea: prima ottengo l’output della funzione saluta() ereditata e poi ci aggiungo
qualcosa (leggi: ci faccio quello che mi pare).
In programmazione OOP la funzione super() è clamorosamente utile nel caso di overriding della funzione __init__: vediamo perché.
Nel seguente esempio proveremo ad aggiungere una variabile membro alla classe Studente relativa alla scuola frequentata dallo studente.
Per farlo dobbiamo forzatamente reimplementare la funzione __init__ della classe, modificando così anche il numero dei parametri.
Questa cosa mi forza (poiché facendo overriding, la funzione __init__ ereditata viene nascosta) a reimplementare tutto quanto viene definito
nella funzione __init__ della classe Base.
class Persona:
def __init__(self, name):
self.nome = name
class Studente(Persona):
def __init__(self,name,school):
self.nome = name
self.scuola = school
# ------------------------------------
if __name__ == "__main__":
p = Studente("Gino", "Liceo")
Questo modo di fare può funzionare per classi banali come la classe Persona e la classe Studente… NON può funzionare quando si eredita da classi
complesse: bisogna usufruire della funzione __init__ ereditata grazie alla funzione super():
class Persona:
def __init__(self, name):
self.nome = name
class Studente(Persona):
def __init__(self,name,school):
super().__init__(name)
self.scuola = school
# ------------------------------------
if __name__ == "__main__":
p = Studente("Gino", "Liceo")
La funzione super() che richiama la funzione __init__ ereditata deve essere eseguita per prima cosa, fornendole tutti i parametri necessari (nel nostro caso, solo il nome),
mentre l’attributo aggiunto (self.scuola) si definisce subito dopo.
Python Object class
L’ereditarietà è un meccanismo talmente comodo che tutto il linguaggio Python è basato su di esso.
In Python 3 esiste una classe predefinita, chiamata object, da cui automaticamente tutte le classi derivano, secondo una
sorta di eredità forzata. Significa che tutte le classi che dichiariamo in Python 3 automaticamente derivano dalla classe object.
Questo concetto di avere una unica classe base da cui per ereditarietà derivano tutte le altre non è una idea partorita in seno alla comunità Python ma una “genialata” che le comunità Java e Qt/C++ sperimentano già da decenni.
L’idea alla base di questa “moda” è quella di sfruttare l’ereditarietà per condividere con tutti gli oggetti una serie di proprietà comuni che possano facilitare la gestione del codice e potenziare con semplicità e in maniera automatica tutto il sistema OOP Python.
Per verificare la struttura di ereditarietà che vi ho prospettato potete utilizzare le funzioni predefinite:
isinstance()issubclass()
La funzione isinstance() prende due parametri, un oggetto e una classe (ricordate questi termini? Controllate nella terminologia) e ritorna
True se l’oggetto è una istanza della classe, False altrimenti. Come al solito, con un esempio è più facile capire:
class Prova:
pass
a = Prova()
b = 2
isinstance(a, Prova) # ritorna True
isinstance(b, Prova) # ritorna False
isinstance(a, object) # ritorna True
La funzione issubclass() prende due parametri, due classi e ritorna True se la prima classe è una sottoclasse della seconda.
issubclass(Prova, object) # ritorna True
issubclass(int, Prova) # ritorna False
issubclass(int, object) # ritorna True, tutto deriva da object. Ricordate?
Come avete visto, porta tutto :)
Esercizi su ereditarietà
Esercizio 1401
Definire la classe Quadrato con attributo il lato e metodi “area” e “perimetro”. Da quella derivare la classe Cubo, in cui va aggiunto il metodo “volume” e ridefinito il metodo “area” in modo che esso restituisca l’area delle 6 facce del cubo.
Dichiarare un oggetto Quadrato di lato 4, visualizzando i suoi attributi e il risultato dei metodi area e perimetro.
Dichiarare un oggetto Cubo di lato 4, visualizzando i suoi attributi e il risultato dei metodi area e volume.
Esercizio 1402
Definire la classe Persona, con attributi nome ed età e metodi saluta() (restituisce la stringa “ciao, sono + nome”) e invecchia() (aggiunge un anno all’età).
Derivare da questa la classe Docente, che aggiunge l’attributo materia, inizialmente vuoto.
Un docente invecchia più velocemente di una persona normale… ridefinire la funzione invecchia() che aggiunge ad ogni chiamata 2 anni al docente.
Aggiungere alla classe Docente il metodo insegna(materia), che prende una stringa per il nome della materia da insegnare. Se la materia è una fra “matematica”, “italiano”, “inglese” la funzione imposta l’attributo materia e ritorna True. Altrimenti ritorna False.
Definire la persona “Giacomo”, di anni 28. Testare i metodi saluta, invecchia e poi visualizzare gli attributi della classe.
Definire il docente “Francesca”, di anni 31. Testare i metodi saluta, invecchia (ridefinito) e il metodo insegna con le materie “diritto” e “inglese”. Visualizzare infine gli attributi della classe.
Esercizio 1403
Definire la classe Punto2D, con attributi le coordinate del punto nel piano cartesiano e le funzioni “distanzaDalCentro” e “distanzaDalPunto”. Questa seconda funzione prende come parametro un ulteriore Punto2D da cui calcolare la distanza nel piano.
Derivare dalla classe Punto2D la classe Punto3D.
Definire il Punto2D di coordinate (3,4), visualizzare i suoi attributi ed eseguire le funzioni distanzaDalCentro e distanzaDalPunto. Per quest’ultima funzione definire il Punto2D di coordinate (6,8).
Definire il Punto3D di coordinate (4,5,6), visualizzare i suoi attributi ed eseguire le funzioni distanzaDalCentro e distanzaDalPunto. Per quest’ultima funzione definire il Punto3D di coordinate (1,1,2).
Esercizio 1404
Definire la classe Cerchio con gli attributi e i metodi che ritenete opportuni.
Derivare da essa la classe Sfera.
Definire un cerchio di raggio 5, di cui calcolare area e circonferenza. Definire una sfera di raggio 3, di cui calcolare area e volume.
Esercizio 1405
Definire la classe Contatto con nome, nick, numero, mail. In fase di definizione la classe prende solo nome e numero e imposta gli altri alla stringa vuota.
Definire i seguenti contatti:
- Giacomo (detto “Jack”), numero +39-340-1234567, mail jack@mail.com.
- Giovanni (detto “John”), numero +39-333-4567890, mail john@mail.com.
La classe ContattoLavoro deriva dalla classe Contatto, ma aggiunge le informazioni fax e partitaIVA. Il costruttore prende ancora una volta solo nome e numero, impostando le restanti variabili alla stringa vuota.
Definire il seguente contatto di lavoro:
- Alessandro (detto “Alex”), numero +39-345-6789012, mail alex@mail.com, fax +39-0721-098765, partitaIVA 1234567890A
Esercizio 1406
Definire una classe Persona con attributi nome e anno di nascita, forniti tramite parametri e indirizzo, inizialmente impostato alla stringa vuota.
Derivare da essa una classe Abbonato, che comprenda il numero di noleggi effettuati e la percentuale di sconto a cui l’utente ha diritto. Ovviamente il numero di noleggi all’inizio è zero, mentre lo sconto iniziale è del 5% per tutti gli adulti fino a 50 anni e del 10% per i più grandi (da 50 in su).
Ogni 2 noleggi lo sconto aumenta del 5% fino ad un massimo del 50% di sconto sul prezzo di noleggio. Definire una funzione “noleggiaFilm” che aggiunge un noleggio all’abbonato, eventualmente aggiornando le informazioni dell’abbonato.
Esercizio 1407
Definire una classe Veicolo, che contempli fra le sue caratteristiche la possibilità di indicare la velocità massima (in km/h) e che in fase di definizione imposta il numero dei chilometri percorsi a ZERO.
Implementare in essa, oltre alle funzioni che ritenete opportune, una funzione chiamata “faiStrada” che richiede la velocità da tenere (che dovrà essere minore della velocità massima) e il tempo per cui tenerla: la funzione dovrà aggiornare il numero di chilometri percorsi.
Derivare da questa una classe Autobus, che comprende, oltre alle caratteristiche ereditate, il numero massimo di posti all’interno e il numero di persone attualmente all’interno, da inizializzare a ZERO.
Modificare la funzione di __init__ in modo tale che il bus non possa
avere una velocità massima superiore ai 100 km/h.
Implementare una funzione chiamata “eseguiFermata” che prende come parametro il numero di persone che salgono e il numero di persone che scendono e aggiorna il numero di persone attualmente presenti nell’autobus.
Classi Astratte e Polimorfismo
Nel capitolo sull’ereditarietà abbiamo visto che una classe derivata può reimplementare (overriding) un metodo ereditato, cambiandone il comportamento. In questo capitolo portiamo questo concetto alle sue conseguenze naturali, introducendo due idee strettamente collegate: il polimorfismo e le classi astratte.
Polimorfismo
La parola polimorfismo viene dal greco e significa, letteralmente, molte forme. In OOP indica la possibilità di chiamare lo stesso metodo su oggetti di classi diverse, ottenendo risultati diversi a seconda del tipo reale dell’oggetto.
Partiamo da un esempio concreto. Abbiamo già incontrato Cerchio, Quadrato e TriangoloRettangolo negli esercizi precedenti.
Tutte e tre queste classi hanno un metodo calcolaArea(). Proviamo a metterle insieme in una lista:
import math
class Cerchio:
def __init__(self, raggio):
self.raggio = raggio
def calcolaArea(self):
return math.pi * self.raggio ** 2
class Quadrato:
def __init__(self, lato):
self.lato = lato
def calcolaArea(self):
return self.lato ** 2
class TriangoloRettangolo:
def __init__(self, base, altezza):
self.base = base
self.altezza = altezza
def calcolaArea(self):
return self.base * self.altezza / 2
# -------------------------------------------
if __name__ == "__main__":
forme = [Cerchio(5), Quadrato(4), TriangoloRettangolo(6, 3)]
for forma in forme:
print("Area:", forma.calcolaArea())
Output:
Area: 78.53981633974483
Area: 16
Area: 9.0
Su ogni oggetto della lista chiamiamo calcolaArea(): Python capisce da solo, a runtime,
di che classe è quell’oggetto e quale versione del metodo chiamare. È il polimorfismo in azione.
Note
Polimorfismo
Il polimorfismo è la capacità di chiamare lo stesso metodo su oggetti di classi diverse, ottenendo un comportamento specifico per ogni classe. Python lo gestisce automaticamente grazie all’ereditarietà e all’overriding.
Duck Typing
Python porta il polimorfismo anche oltre: il codice sopra funziona anche se Cerchio, Quadrato e TriangoloRettangolo
non hanno nessuna relazione di ereditarietà fra loro. L’unica cosa che conta è che tutte e tre abbiano il metodo calcolaArea().
Questo principio si chiama duck typing e si riassume con una frase famosa:
“Se cammina come un’anatra e starnazza come un’anatra, allora è un’anatra.”
In Python, se un oggetto ha il metodo calcolaArea(), può essere usato ovunque ci si aspetti una “forma con area”, a prescindere dalla sua classe di appartenenza.
L’interprete non controlla il tipo dell’oggetto, ma solo se il metodo richiesto esiste.
class Rombo:
def __init__(self, d1, d2):
self.d1 = d1
self.d2 = d2
def calcolaArea(self):
return (self.d1 * self.d2) / 2
# Rombo non eredita da niente di speciale, eppure funziona benissimo nella lista di prima!
forme = [Cerchio(5), Quadrato(4), Rombo(6, 4)]
for forma in forme:
print("Area:", forma.calcolaArea())
Il duck typing è comodo, ma ha un limite: non garantisce nulla a priori.
Se dimenticate di implementare calcolaArea() in una classe e la inserite nella lista, il programma sembrerà funzionare
fino al momento in cui Python arriva a quell’oggetto… e lì si ferma con un AttributeError.
class Trapezio:
def __init__(self, b1, b2, h):
self.b1 = b1
self.b2 = b2
self.h = h
# ops... mi sono dimenticato di implementare calcolaArea()!
forme = [Cerchio(5), Trapezio(4, 6, 3)]
for forma in forme:
print("Area:", forma.calcolaArea()) # AttributeError su Trapezio!
Come si risolve questo problema in maniera sistematica? Con le classi astratte.
Classi Astratte
Una classe astratta è una classe che definisce un contratto: stabilisce quali metodi le classi derivate devono implementare, senza implementarli essa stessa.
Se una classe derivata non implementa tutti i metodi richiesti, Python si rifiuta di istanziarla e genera un errore immediatamente, molto prima che il programma arrivi a chiamare il metodo mancante.
Per usare le classi astratte in Python si importa il modulo abc (Abstract Base Class):
from abc import ABC, abstractmethod
ABCè la classe base da cui deve derivare la nostra classe astratta;@abstractmethodè il decoratore che marca un metodo come obbligatorio per le classi derivate.
Riscriviamo l’esempio delle forme geometriche in modo sicuro:
from abc import ABC, abstractmethod
import math
class Forma(ABC): # Forma deriva da ABC: diventa una classe astratta
@abstractmethod
def calcolaArea(self):
"""Calcola e ritorna l'area della forma"""
pass
@abstractmethod
def calcolaPerimetro(self):
"""Calcola e ritorna il perimetro della forma"""
pass
Adesso Forma definisce il contratto: chiunque voglia essere una Forma deve implementare calcolaArea() e calcolaPerimetro().
Note
Classi astratte
Una classe astratta è una classe che non può essere istanziata direttamente. Serve solo come modello per le classi derivate, imponendo loro di implementare i metodi marcati con
@abstractmethod.
Se proviamo ad istanziare direttamente Forma:
f = Forma() # TypeError: Can't instantiate abstract class Forma
# with abstract methods calcolaArea, calcolaPerimetro
Python ci ferma subito con un messaggio di errore chiaro: la classe è astratta e non può essere istanziata.
Adesso implementiamo le classi concrete che derivano da Forma:
class Cerchio(Forma):
def __init__(self, raggio):
self.raggio = raggio
def __str__(self):
return f"Cerchio di raggio {self.raggio}"
def calcolaArea(self):
return math.pi * self.raggio ** 2
def calcolaPerimetro(self):
return 2 * math.pi * self.raggio
class Quadrato(Forma):
def __init__(self, lato):
self.lato = lato
def __str__(self):
return f"Quadrato di lato {self.lato}"
def calcolaArea(self):
return self.lato ** 2
def calcolaPerimetro(self):
return 4 * self.lato
class TriangoloRettangolo(Forma):
def __init__(self, base, altezza):
self.base = base
self.altezza = altezza
def __str__(self):
return f"Triangolo rettangolo di base {self.base} e altezza {self.altezza}"
def calcolaArea(self):
return self.base * self.altezza / 2
def calcolaPerimetro(self):
ipotenusa = math.sqrt(self.base**2 + self.altezza**2)
return self.base + self.altezza + ipotenusa
E adesso se dimentico di implementare un metodo, Python me lo dice subito:
class Trapezio(Forma):
def __init__(self, b1, b2, h):
self.b1 = b1
self.b2 = b2
self.h = h
def calcolaArea(self):
return (self.b1 + self.b2) * self.h / 2
# ho dimenticato calcolaPerimetro()!
t = Trapezio(4, 6, 3) # TypeError: Can't instantiate abstract class Trapezio
# with abstract method calcolaPerimetro
L’errore arriva al momento della creazione dell’oggetto, non quando si chiama il metodo mancante. Molto meglio!
Adesso il codice che usa le forme è sia comodo che sicuro:
if __name__ == "__main__":
forme = [Cerchio(5), Quadrato(4), TriangoloRettangolo(6, 3)]
area_totale = 0
for forma in forme:
print(forma)
print(f" area: {forma.calcolaArea():.2f}")
print(f" perimetro: {forma.calcolaPerimetro():.2f}")
area_totale += forma.calcolaArea()
print(f"\nArea totale: {area_totale:.2f}")
Output:
Cerchio di raggio 5
area: 78.54
perimetro: 31.42
Quadrato di lato 4
area: 16.00
perimetro: 16.00
Triangolo rettangolo di base 6 e altezza 3
area: 9.00
perimetro: 15.71
Area totale: 103.54
Metodi concreti in una classe astratta
Una classe astratta può avere anche metodi normali (non astratti), che vengono ereditati da tutte le classi derivate. Questo è utile quando una parte del comportamento è comune a tutte le sottoclassi, mentre solo alcune parti variano.
class Forma(ABC):
@abstractmethod
def calcolaArea(self):
pass
@abstractmethod
def calcolaPerimetro(self):
pass
def descrivi(self):
# metodo CONCRETO: non è astratto, funziona uguale per tutte le forme
print(f"Area: {self.calcolaArea():.2f}, Perimetro: {self.calcolaPerimetro():.2f}")
Tutte le classi che derivano da Forma ereditano descrivi() gratuitamente, senza doverla reimplementare:
c = Cerchio(5)
c.descrivi() # Area: 78.54, Perimetro: 31.42
Note
Classe astratta: cosa può contenere
- Metodi astratti (
@abstractmethod): nessuna implementazione, obbligatori per le classi derivate.- Metodi concreti: implementati normalmente, ereditati da tutte le classi derivate.
- Variabili di classe e
@classmethod/@staticmethod: tutto ciò che abbiamo visto nel capitolo precedente funziona normalmente.
Esercizio svolto: gerarchia Veicolo
Definiamo una gerarchia di veicoli con la classe astratta Veicolo, che impone a tutte le sottoclassi di definire
il metodo consumo(km) (consumo in litri per percorrere km chilometri) e il metodo tipo() (descrizione testuale del veicolo).
from abc import ABC, abstractmethod
class Veicolo(ABC):
def __init__(self, marca, modello):
self.marca = marca
self.modello = modello
def __str__(self):
return f"{self.tipo()}: {self.marca} {self.modello}"
@abstractmethod
def tipo(self):
"""Ritorna una stringa che descrive il tipo di veicolo"""
pass
@abstractmethod
def consumo(self, km):
"""Ritorna il consumo in litri per percorrere km chilometri"""
pass
def costo_viaggio(self, km, prezzo_litro):
"""Metodo concreto: calcola il costo del viaggio in euro"""
return self.consumo(km) * prezzo_litro
class Auto(Veicolo):
def __init__(self, marca, modello, consumo_per_100):
super().__init__(marca, modello)
self.consumo_per_100 = consumo_per_100 # litri per 100 km
def tipo(self):
return "Auto"
def consumo(self, km):
return self.consumo_per_100 * km / 100
class Moto(Veicolo):
def __init__(self, marca, modello, consumo_per_100):
super().__init__(marca, modello)
self.consumo_per_100 = consumo_per_100
def tipo(self):
return "Moto"
def consumo(self, km):
return self.consumo_per_100 * km / 100
class Camion(Veicolo):
def __init__(self, marca, modello, consumo_per_100):
super().__init__(marca, modello)
self.consumo_per_100 = consumo_per_100
def tipo(self):
return "Camion"
def consumo(self, km):
return self.consumo_per_100 * km / 100
# -------------------------------------------
if __name__ == "__main__":
veicoli = [
Auto("Fiat", "Panda", 5.5),
Moto("Honda", "CB500", 4.0),
Camion("Iveco", "Daily", 14.0)
]
distanza = 300
prezzo = 1.85
for v in veicoli:
print(v)
print(f" consumo per {distanza} km: {v.consumo(distanza):.1f} L")
print(f" costo viaggio: {v.costo_viaggio(distanza, prezzo):.2f} €")
Output:
Auto: Fiat Panda
consumo per 300 km: 16.5 L
costo viaggio: 30.52 €
Moto: Honda CB500
consumo per 300 km: 12.0 L
costo viaggio: 22.20 €
Camion: Iveco Daily
consumo per 300 km: 42.0 L
costo viaggio: 77.70 €
Notate che costo_viaggio() è un metodo concreto definito in Veicolo che usa consumo(), il quale è astratto.
Ogni classe derivata implementa consumo() a modo suo, e costo_viaggio() funziona correttamente per tutte senza doverla reimplementare.
Esercizi di comprensione
Esercizio 1501
Definire la classe astratta Forma con i metodi astratti calcolaArea() e calcolaPerimetro().
Derivare da essa le classi Cerchio, Quadrato e TriangoloRettangolo come mostrato nel capitolo.
Definire una lista con almeno un oggetto di ogni classe e, per ognuno, visualizzare area e perimetro.
Verificare che tentare di istanziare direttamente Forma produca un TypeError.
Esercizio 1502
Aggiungere alla gerarchia dell’esercizio precedente le classi Rettangolo e Rombo
(il rombo ha come parametri le due diagonali).
Creare una lista eterogenea con almeno 6 forme diverse e calcolare l’area totale di tutte le forme presenti.
Esercizio 1503
Definire la classe astratta Animale con:
- attributi
nomeespecie(forniti in__init__); - metodo astratto
verso()che ritorna la stringa con il verso dell’animale; - metodo astratto
muovi()che ritorna una stringa che descrive come si muove l’animale (es. “corre”, “vola”, “nuota”); - metodo concreto
presentati()che ritorna la stringa"Sono {nome}, un {specie}. Faccio {verso()} e {muovi()}".
Derivare da Animale almeno 4 classi concrete a scelta (es. Cane, Gatto, Aquila, Delfino).
Creare una lista eterogenea di animali e chiamare presentati() su ciascuno.
Esercizio 1504
Definire la classe astratta Dipendente con:
- attributi
nomeeore_lavorate(inizialmente zero); - metodo astratto
stipendio_mensile()che calcola lo stipendio; - metodo concreto
lavora(ore)che aggiunge ore alle ore lavorate del mese; - metodo concreto
fine_mese()che visualizza il riepilogo (ore lavorate e stipendio) e azzera le ore lavorate.
Derivare da Dipendente:
DipendenteOre: pagato a ore (riceve la tariffa oraria in__init__);DipendenteStipendioFisso: riceve uno stipendio fisso mensile indipendentemente dalle ore lavorate.
Definire 2 dipendenti per tipo, far lavorare ciascuno un numero diverso di ore e chiamare fine_mese() su tutti.
Esercizio 1505: DIFFICILE
Riprendere la gerarchia Veicolo dell’esercizio svolto e aggiungere:
- la classe
Bicicletta, che non consuma carburante (consumo()ritorna sempre 0) e cometipo()ritorna"Bicicletta"; - il metodo astratto
velocita_massima()inVeicolo, che ritorna la velocità massima in km/h; - il metodo concreto
tempo_viaggio(km)inVeicolo, che ritorna il tempo minimo stimato in ore per percorrerekmchilometri alla velocità massima.
Implementare velocita_massima() in tutte le classi con valori realistici a scelta.
Creare una lista con tutti i tipi di veicoli e, per un viaggio di 500 km, visualizzare per ognuno: consumo, costo (con prezzo a scelta) e tempo stimato.
Composizione
Nei capitoli precedenti abbiamo visto come l’ereditarietà permetta di costruire nuove classi a partire da classi esistenti. È uno strumento potente, ma non è l’unico modo di mettere in relazione le classi fra loro.
In questo capitolo vediamo un secondo modo, altrettanto importante: la composizione.
È un… oppure Ha un…?
La differenza fra ereditarietà e composizione si capisce meglio partendo dal linguaggio naturale.
Riprendiamo le classi che abbiamo già incontrato:
- Uno
Studenteè unaPersona→ ha senso usare l’ereditarietà:Studentederiva daPersona. - Un
Cerchioè unaForma→ ha senso usare l’ereditarietà:Cerchioderiva daForma. - Un’
Automobileha unMotore→ ha senso usare la composizione:Automobilecontiene un oggettoMotore. - Una
Rubricaha una lista diContatti→ ha senso usare la composizione:Rubricacontiene oggettiContatto.
La regola è semplice:
Note
Is-a vs Has-a
- Se fra due classi vale la relazione “è un” (is-a), si usa l’ereditarietà.
- Se fra due classi vale la relazione “ha un” (has-a), si usa la composizione.
La composizione si realizza semplicemente creando un oggetto come attributo di un altro oggetto. Non c’è nessuna sintassi speciale: è la stessa cosa che abbiamo fatto dall’inizio del corso con gli attributi, solo che stavolta il valore dell’attributo non è un intero o una stringa, ma un oggetto di un’altra classe.
Un primo esempio
Definiamo la classe Motore e la classe Automobile. Un’automobile ha un motore, quindi Automobile conterrà un oggetto Motore:
class Motore:
def __init__(self, cilindrata, cavalli):
self.cilindrata = cilindrata
self.cavalli = cavalli
def __str__(self):
return f"Motore {self.cilindrata}cc - {self.cavalli}cv"
def avvia(self):
return "Vroom!"
class Automobile:
def __init__(self, marca, modello, cilindrata, cavalli):
self.marca = marca
self.modello = modello
self.motore = Motore(cilindrata, cavalli) # composizione: Motore è un attributo
def __str__(self):
return f"{self.marca} {self.modello} ({self.motore})"
def parti(self):
return f"{self.marca} {self.modello}: {self.motore.avvia()}"
# -------------------------------------------
if __name__ == "__main__":
auto = Automobile("Fiat", "Panda", 1200, 69)
print(auto)
print(auto.parti())
print("Cavalli:", auto.motore.cavalli)
Output:
Fiat Panda (Motore 1200cc - 69cv)
Fiat Panda: Vroom!
Cavalli: 69
L’oggetto Motore vive dentro l’oggetto Automobile come attributo self.motore.
Per accedere ai suoi metodi e attributi si usa la notazione con il punto: auto.motore.avvia(), auto.motore.cavalli.
Composizione 1-a-molti
La composizione non riguarda solo il caso in cui un oggetto ne contiene uno solo: spessissimo un oggetto contiene
una lista di altri oggetti. È il caso, ad esempio, di una Libreria che contiene molti Libro, o di un Ordine
che contiene molte Voce.
Riprendiamo l’esercizio della Rubrica svolto precedentemente: a ben guardare, essa è già un esempio di composizione!
Una rubrica ha molti contatti. Riscriviamola esplicitando questa struttura:
class Contatto:
def __init__(self, nome, numero):
self.nome = nome
self.numero = numero
def __str__(self):
return f"{self.nome}: {self.numero}"
class Rubrica:
def __init__(self):
self.__contatti = [] # lista di oggetti Contatto
def __str__(self):
if not self.__contatti:
return "Rubrica vuota"
righe = ""
for c in self.__contatti:
righe += str(c) + "\n"
return righe
def inserisciContatto(self, nome, numero):
self.__contatti.append( Contatto(nome, numero) )
def trovaNumero(self, nome):
for c in self.__contatti:
if c.nome == nome:
return c.numero
return "NESSUN CONTATTO"
def rimuoviContatto(self, nome):
for c in self.__contatti:
if c.nome == nome:
self.__contatti.remove(c)
return True
return False
# -------------------------------------------
if __name__ == "__main__":
r = Rubrica()
r.inserisciContatto("Giacomo", "333-1234567")
r.inserisciContatto("Francesca", "347-9876543")
r.inserisciContatto("Prof", "555-0000000")
print(r)
print("Numero di Giacomo:", r.trovaNumero("Giacomo"))
r.rimuoviContatto("Prof")
print("--- dopo rimozione ---")
print(r)
Rispetto all’esercizio già svolto, l’unica differenza sostanziale è che la lista non contiene stringhe o dizionari,
ma oggetti Contatto. Il vantaggio è che ogni contatto porta con sé la propria logica e può essere esteso
(ad esempio aggiungendo mail, foto, note) senza toccare la classe Rubrica.
Quando si sbaglia: usare l’ereditarietà al posto della composizione
Un errore comune fra chi inizia è usare l’ereditarietà anche quando la relazione fra due classi è has-a, non is-a. Vediamo un esempio di cosa succede:
# SBAGLIATO: Automobile "è un" Motore? No! Ha un motore.
class Automobile(Motore):
def __init__(self, marca, modello, cilindrata, cavalli):
super().__init__(cilindrata, cavalli)
self.marca = marca
self.modello = modello
Questo codice funziona, ma è concettualmente sbagliato. La conseguenza pratica è che Automobile eredita tutto
quello che appartiene a Motore, comprese cose che non hanno senso per un’automobile (es. se Motore avesse
un metodo sostituisciPistoni()). Col tempo, questo tipo di errore rende il codice difficile da capire e da mantenere.
Tip
Prima di usare l’ereditarietà, fatevi sempre questa domanda: “Ha senso dire che un oggetto di questa classe è un oggetto dell’altra classe?”
Se la risposta è no, usate la composizione.
Composizione ed ereditarietà insieme
Composizione ed ereditarietà non si escludono: spesso una buona progettazione le usa entrambe.
from abc import ABC, abstractmethod
class Motore(ABC):
def __init__(self, cavalli):
self.cavalli = cavalli
@abstractmethod
def tipo_carburante(self):
pass
def __str__(self):
return f"Motore {self.tipo_carburante()} - {self.cavalli}cv"
class MotoreBenzina(Motore):
def tipo_carburante(self):
return "benzina"
class MotoreDiesel(Motore):
def tipo_carburante(self):
return "diesel"
class MotoreElettrico(Motore):
def tipo_carburante(self):
return "elettrico"
class Automobile:
def __init__(self, marca, modello, motore):
self.marca = marca
self.modello = modello
self.motore = motore # composizione: riceve un oggetto Motore già costruito
def __str__(self):
return f"{self.marca} {self.modello} ({self.motore})"
# -------------------------------------------
if __name__ == "__main__":
auto1 = Automobile("Fiat", "Panda", MotoreBenzina(69))
auto2 = Automobile("Volkswagen", "Golf", MotoreDiesel(115))
auto3 = Automobile("Tesla", "Model 3", MotoreElettrico(283))
parco = [auto1, auto2, auto3]
for auto in parco:
print(auto)
Output:
Fiat Panda (Motore benzina - 69cv)
Volkswagen Golf (Motore diesel - 115cv)
Tesla Model 3 (Motore elettrico - 283cv)
Qui Motore è una classe astratta con sottoclassi concrete (ereditarietà), e Automobile ha un Motore (composizione).
Le due tecniche lavorano insieme in modo naturale.
Esercizio svolto: Computer
Un Computer ha una CPU e ha una SchedaVideo. Definiamo le tre classi separatamente e le componiamo:
class CPU:
def __init__(self, produttore, modello, ghz):
self.produttore = produttore
self.modello = modello
self.ghz = ghz
def __str__(self):
return f"CPU {self.produttore} {self.modello} @ {self.ghz}GHz"
class SchedaVideo:
def __init__(self, produttore, modello, vram_gb):
self.produttore = produttore
self.modello = modello
self.vram_gb = vram_gb
def __str__(self):
return f"GPU {self.produttore} {self.modello} ({self.vram_gb}GB VRAM)"
class Computer:
def __init__(self, nome, cpu, scheda_video, ram_gb):
self.nome = nome
self.cpu = cpu # composizione
self.scheda_video = scheda_video # composizione
self.ram_gb = ram_gb
def __str__(self):
s = f"Computer: {self.nome}\n"
s += f" {self.cpu}\n"
s += f" {self.scheda_video}\n"
s += f" RAM: {self.ram_gb}GB"
return s
def adatto_al_gaming(self):
return self.scheda_video.vram_gb >= 8 and self.ram_gb >= 16
# -------------------------------------------
if __name__ == "__main__":
pc1 = Computer(
"PC Studio",
CPU("Intel", "Core i7-13700K", 5.4),
SchedaVideo("NVIDIA", "RTX 4070", 12),
32
)
pc2 = Computer(
"PC Ufficio",
CPU("AMD", "Ryzen 5 5600G", 4.4),
SchedaVideo("AMD", "Radeon RX 6400", 4),
16
)
for pc in [pc1, pc2]:
print(pc)
print(f" Adatto al gaming: {pc.adatto_al_gaming()}")
print()
Output:
Computer: PC Studio
CPU Intel Core i7-13700K @ 5.4GHz
GPU NVIDIA RTX 4070 (12GB VRAM)
RAM: 32GB
Adatto al gaming: True
Computer: PC Ufficio
CPU AMD Ryzen 5 5600G @ 4.4GHz
GPU AMD Radeon RX 6400 (4GB VRAM)
RAM: 16GB
Adatto al gaming: False
Esercizi di comprensione
Esercizio 1601
Definire la classe Indirizzo con attributi via, numero civico, città e CAP.
Definire la classe Persona con attributi nome, cognome e un oggetto Indirizzo come attributo residenza.
Definire 3 persone con indirizzi diversi e visualizzarle.
Esercizio 1602
Definire la classe Libro con attributi titolo, anno di pubblicazione e numero di pagine.
Definire la classe Libreria che contiene una lista di oggetti Libro.
Implementare i seguenti metodi:
aggiungiLibro(libro): aggiunge un libro alla libreria;rimuoviLibro(titolo): rimuove il libro con quel titolo, ritorna True o False;cercaLibro(titolo): ritorna l’oggettoLibrocon quel titolo, o None se non trovato;libriDiPiu(n): ritorna la lista dei libri con più dinpagine.
Definire una libreria, inserire almeno 5 libri e testare tutti i metodi.
Esercizio 1603
Riprendere la classe Contatto e Rubrica mostrate nel capitolo.
Aggiungere alla classe Contatto un terzo attributo mail, inizialmente stringa vuota.
Aggiungere alla classe Rubrica il metodo modificaNumero(nome, nuovoNumero) e trovaMail(nome).
Testare la rubrica con almeno 4 contatti.
Esercizio 1604
Definire la classe Squadra con attributi nome e una lista di oggetti Giocatore
(riprendere la classe Giocatore dell’esercizio 1111).
Implementare i seguenti metodi nella classe Squadra:
aggiungiGiocatore(giocatore): aggiunge un giocatore alla rosa;rimuoviGiocatore(nome): rimuove il giocatore con quel nome, ritorna True o False;giocatoriPerRuolo(ruolo): ritorna la lista dei giocatori con quel ruolo;rosa(): visualizza tutti i giocatori con nome, numero e ruolo.
Definire una squadra, inserire almeno 8 giocatori con ruoli diversi e testare tutti i metodi.
Esercizio 1605: DIFFICILE
Definire la classe Motore con attributi cilindrata e cavalli e metodo avvia().
Definire la classe astratta Veicolo (con i metodi astratti tipo() e descrivi())
che in __init__ riceve marca, modello e un oggetto Motore.
Derivare da Veicolo le classi Auto, Moto e Camion, ciascuna con la propria implementazione di tipo() e descrivi().
Creare almeno 2 oggetti per ogni tipo di veicolo con motori diversi e visualizzarli tutti.
Nota: questo esercizio combina composizione (il veicolo ha un motore) ed ereditarietà (Auto, Moto, Camion sono veicoli).
OOP: Caratteristiche avanzate
Vengono descritte qua di seguito alcune caratteristiche avanzate tipiche della OOP che ritroviamo anche in Python.
Funzioni operatori
La classe object introduce tutta una serie di funzioni (che ovviamente, tutte le classi ereditano) per standardizzare una serie di comportamenti Partiamo da un esempio per chiarire l’utilità del concetto: definiamo la classe Punto2D che descrive un punto nel piano cartesiano
class Punto2D:
def __init__ (self, x, y):
self.x = x
self.y = y
Definiamo due oggetti della classe Punto2D e proviamo a sommarli
a = Punto2D (2, 3)
b = Punto2D (1, 1)
c = a + b # ERRORE!!!
Per poter definire l’operazione di addizione all’interno della nuova
classe basta reimplementare la funzione abbinata __add__ : faremo in
modo che la somma di due punti crei un nuovo punto con le coordinate
uguali alla somma delle coordinate dei punti sommati.
# nella classe Punto2D
def __add__ (self, other):
xS = self.x + other.x
yS = self.y + other.y
return Punto2D( xS, yS )
Quindi da ora in poi…
# c diventa un Punto2D di coordinate x = 2 + 1 = 3 e y = 3 + 1 = 4
c = a + b
Data una generica classe Pippo e due oggetti qualunque, diciamo a e b
della classe Pippo:
| Se vuoi usare: | e scrivere | Implementa nella classe la funzione: | che ritorna |
|---|---|---|---|
| print ( a ) | def __str__(self) | una stringa | |
| Addizione | a + b | def __add__(self,other) | un oggetto della classe |
| Sottrazione | a - b | def __sub__(self,other) | un oggetto della classe |
| Moltiplicazione | a * b | def __mul__(self,other) | un oggetto della classe |
| Divisione | a / b | def __truediv__(self,other) | un oggetto della classe |
| Divisione Intera | a // b | def __floordiv__(self,other) | un oggetto della classe |
| Potenza | a ** b | def __pow__(self,other) | un oggetto della classe |
| Modulo | a % b | def __mod__(self,other) | un oggetto della classe |
| Minore | a < b | def __lt__(self,other) | un bool (True o False) |
| Minore o uguale | a <= b | def __le__(self,other) | un bool (True o False) |
| Uguale | a == b | def __eq__(self,other) | un bool (True o False) |
| Diverso | a != b | def __ne__(self,other) | un bool (True o False) |
| Maggiore | a > b | def __gt__(self,other) | un bool (True o False) |
| Maggiore o uguale | a >= b | def __ge__(self,other) | un bool (True o False) |
La funzione __str__ permette ad un oggetto della classe di essere
utilizzato nella funzione print(). Questa cosa dovremmo già averla capita…
Le funzioni aritmetiche (addizione, sottrazione, etc…) prendono come
parametro 2 oggetti di una classe (tipicamente coi 2 parametri self , other)
e ritornano sempre un oggetto della classe stessa!
Ripropongo la funzione __add__ della classe Punto2D implementata poche righe fa:
# come vedete la funzione prende 2 parametri, che rappresentano
# altrettanti oggetti
def __add__ (self, other):
xS = self.x + other.x
yS = self.y + other.y
# poiché questa funzione implementa la somma fra 2 Punto2D, ritorna
# un oggetto Punto2D che rappresenta la somma dei 2 punti identificati
# da self e other
return Punto2D( xS, yS )
Le funzioni di confronto (minore, maggiore, diverso, etc…) prendono come parametro i 2 oggetti da confrontare e ritornano un booleano.
Ad esempio per implementare l’operatore minore bisogna definire la funzione __lt__.
Essa prende i 2 parametri self, other che rappresentano i 2 oggetti.
Se la funzione ritorna True significa che il primo oggetto è minore del secondo,
altrimenti, se ritorna False significa che il primo oggetto NON è minore del secondo.
# dati 2 punti, uno è minore dell’altro
# se la sua distanza dal centro è minore.
def __lt__ ( self , other ):
if self.distanzaDalCentro() < other.distanzaDalCentro():
return True
return False
Tip
Liste e ordinamenti di oggetti
Se create una lista di Punto2D o di oggetti in genere, per far funzionare correttamente la funzione
sortvi basta implementare la funzione__lt__!!!
Spero sia chiaro! Come al solito… per capire meglio ci sono gli esercizi :)
Esercizi sulle funzioni operatori
Esercizio 1701
Definire la classe Frazione, con parametri numeratore e denominatore.
Definire in essa le funzioni:
-
valuta(): ritorna il valore reale che la frazione rappresenta. Ad esempio se il numeratore è 39 e il denominatore è 10, allora la funzione valuta() ritorna 3.9. -
semplifica(): riduce ai minimi termini i generatori (numeratore e denominatore) della frazione. Ad esempio se il numeratore è 25 e il denominatore è 30, allora si possono entrambe dividere per 5 (il MCD fra 25 e 30) ottenendo 5 e 6.
Definire inoltre le funzioni operatori per somma, moltiplicazione, diverso e minore o uguale.
Dichiarare almeno 3 frazioni con cui testare le funzioni implementate.
Esercizio 1702
Definire la classe Rettangolo, con attributi base e altezza e metodi area e perimetro. Implementare in essa le funzioni aritmetiche, che operano sugli attributi della classe e le funzioni di confronto, che confrontano i rettangoli in base alle aree. In pratica la somma di 2 rettangoli crea un rettangolo che ha base la somma delle basi e altezza la somma delle altezze. Per quanto riguarda il confronto un rettangolo è minore di un altro se la sua area è minore. Dichiarare 2 rettangoli e procedete a testare le operazioni aritmetiche e di confronto implementate.
Esercizio 1703
Definire la classe Reale (che rappresenta un numero reale) con l’unico attributo float che rappresenta il suo valore. Definire in essa le funzioni operatore per somma, sottrazione, divisione, maggiore, uguale.
Derivare da essa la classe Complesso, che rappresenta un numero
complesso. Reimplementare le funzioni ereditate secondo necessità.
Definire inoltre la funzione modulo(), che calcola il modulo del numero
e la funzione coniugato() che ritorna un oggetto Complesso che
rappresenta il complesso coniugato del numero iniziale.
Definire i numeri reali: 4.5 , -7.2 , 9.1 Testare le funzioni implementate. Definire i numeri complessi: 4 + 5i , -7 + 3i Testare le funzioni implementate.
Esercizio 1704
Definire la classe DataSemplice, con i due attributi interi che rappresentano i giorni e i mesi. Nella classe DataSemplice i mesi hanno la lunghezza normale (Gennaio ne ha 31, Febbraio 28, etc…) ma non ci sono gli anni e quindi non esistono gli anni bisestili.
Presenta una funzione isValid() che ritorna True se la data
rappresentata è valida, ovvero la coppia di numeri rappresenta una
combinazione giorno/mese esistente, False altrimenti.
Presenta una funzione contaGiorni() che restituisce il numero di
giorni trascorsi dal 1 Gennaio alla data rappresentata, se valida. -1
altrimenti. Implementare le funzioni operatori per l’addizione, la
sottrazione, il minore e il diverso. Due date si sommano sommando il
numero di giorni trascorsi dall’inizio dell’anno (e analogamente si
sottraggono). Ad esempio “01 gen” + “02 gen” fa “03 gen”.
Una data è minore di un’altra se è precedente all’interno di un anno. Diverso è facile…
Definire una data per il 3 marzo e una per il 45 settembre. Verificare che la prima è valida e la seconda no. Utilizzare la funzione contaGiorni. La seconda dovrebbe restituire -1.
Testare le seguenti operazioni:
- 03 mar + 05 lug
- 03 mar – 2 feb
- 03 mar < 2 feb
- 05 lug != 2 feb
Procedere dichiarando una lista e inserendo al suo interno fino a 10 DataSemplice
con valori casuali (validi) per giorno e mese. Visualizzare la lista così generata.
Testare l’ordinamento tramite funzione sort e visualizzare la lista ordinata.
La libreria grafica wxPython
Introduzione
wxPython (https://wxpython.org/) è una libreria grafica open source, adatta alla creazione di GUI (interfacce grafiche) per sistemi Windows, Mac e Linux.
WxPython deriva dal toolkit wxWidgets (https://wxwidgets.org), libreria grafica C++ con una tradizione più che ventennale. Con la versione 4.x che noi utilizzeremo (codenamed Phoenix) ha subìto una profonda revisione e modernizzazione, fino a diventare la libreria grafica preferita dallo stesso von Rossum (l’inventore di Python). E chissà che presto non diventi la libreria grafica predefinita!
Si presenta come una libreria Python 3.x completamente orientata agli oggetti e quindi sarà di immediato utilizzo per tutti noi, non appena avremo introdotto la sua struttura e gli oggetti principali.
Installazione
La libreria wxPython è una libreria OOP Python in tutto e per tutto. La trovate come tantissime altre sul Python Package Index (pypi.org) e per installarla seguite questi semplici passaggi.
Aprite Thonny e dal menù strumenti selezionate Apri SHELL di sistema:

Da lì digitate il comando pip install wxpython (oppure su Mac pip3 install wxpython) e premete Enter:

Quando il download e l’installazione sono finiti, passiamo a verificare il funzionamento del tutto con un Hello Wordl!
Hello World!
Vediamo innanzitutto un primo assaggio di codice, il famoso programma Hello World! per la libreria wxPython:
# si importa la libreria wx
import wx
# si crea un oggetto Applicazione
app = wx.App()
# si crea una finestra con titolo "Hello, World!"
window = wx.Frame(parent=None, title="Hello, World!")
window.Show()
# si avvia il "Main Event Loop"
app.MainLoop()
Tip
Hello World Programs
Gli Hello World Programs sono programmi che scrivono (o mostrano) semplicemente la scritta “Hello, World!”.
Sono tipicamente i primi programmi che si vuole scrivere in un qualsiasi linguaggio di programmazione e servono soprattutto a vedere la struttura e la sintassi del linguaggio, oltre che ad assicurarsi che tutto funzioni.
Il risultato dell’esecuzione di quel codice è questo!

Se avete visto anche voi una finestra grigia vuota con il titolo “Hello, World!” significa che l’installazione e il primo programma sono andati a buon fine e siete pronti per studiare la libreria wxPython (a partire dalla prossima pagina!).
Primi concetti
wxPython è un toolkit Python cross-platform per la creazione di applicazioni con GUI e che espone una lunga lista di widgets per i compiti più disparati. Le sue classi sono organizzate in moduli che pian piano andremo a studiare: per adesso ci basterà sapere che le classi principali sono disponibili nel modulo wx, lo stesso che abbiamo importato nell’esempio Hello, World!.
Capisco che arrivato a questo punto, prima di andare avanti, è necessario che io faccia una pausa e spieghi una serie di termini che ho utilizzato qua e là e che probabilmente ancora non conoscete.
-
toolkit
Letteralmente “cassetta degli attrezzi”, rappresenta l’insieme di strumenti necessari per eseguire un compito. Il compito del toolkit wxPython è la creazione di applicazioni grafiche, gli strumenti che utilizza sono classi Python. -
GUI
Acronimo di Graphical User Interface, indica la capacità di alcune applicazioni di interagire con gli utenti tramite la modalità WIMP (e adesso spiego WIMP…) -
cross-platform
Rappresenta la capacità di alcuni software di essere utilizzati su vari sistemi operativi. Per quanto riguarda wxPython essa è disponibile su tutti i sistemi operativi desktop, in particolare è sicuramente disponibile su Windows, Mac OS e Linux. -
Widget
Si tratta di un generico oggetto grafico. Nel contesto wxPython si tratta di una classe wxPython che implementa un oggetto grafico, ad esempio la classe che implementa un pulsante (e quindi il pulsante), la classe che implementa una finestra, una checkbox, etc…
Benissimo! Detto questo passiamo a reimplementare l’unico esempio che abbiamo finora visto e ragioniamoci un pò sù
Widget Derivata
Riguardiamo l’esempio iniziale Hello, World! con la differenza che, invece di istanziare direttamente la classe wx.Frame
verrà creata una classe derivata a partire da questa.
Questa sarà la modalità operativa con cui lavoreremo fino a nuovo ordine! Per meglio comprendere quanto da ora in poi eseguirete con obbedienza e rassegnazione, in questo esempio banale troverete tantissimi commenti:
# IMPORTA LA LIBRERIA wxPython (modulo wx)
import wx
# --------------------------------------------------------
# QUI SI DEFINISCE LA CLASSE LaMiaPrimaFinestra (derivata dalla classe wx.Frame)
class LaMiaPrimaFinestra(wx.Frame):
def __init__(self):
super().__init__(parent=None, title="La mia prima finestra")
# ... (altro codice)...
# --------------------------------------------------------
# QUI SI UTILIZZA LA CLASSE LaMiaPrimaFinestra DEFINITA SOPRA
if __name__ == "__main__":
# si crea un oggetto Applicazione.
# Ce ne sarà sempre uno (e solo uno) in ogni applicazione.
# Gestisce l'interazione con l'esterno (l'utente e il sistema operativo)
app = wx.App()
# si definisce un oggetto della classe LaMiaPrimaFinestra
# Gi oggetti grafici principali (le finestre) vengono creati nascosti e quindi devono essere mostrati
window = LaMiaPrimaFinestra()
window.Show()
# si avvia il "Main Event Loop"
app.MainLoop()
Note
Main Event Loop
Il Main Event Loop o ciclo principale degli eventi è uno stato di grazia in cui si pone ogni applicazione grafica dopo aver disegnato le proprie widgets.
In questo particolare stato di colloquio perenne tra il sistema operativo, l’utente e l’applicazione stessa, quest’ultima diventa in grado di intercettare gli eventi che accadono nel sistema (un click su un pulsante, un movimento del mouse, la carta della stampante che finisce, la rete che si sconnette, etc…) e di rispondere (eventualmente) eseguendo una funzione tra quelle disponibili fra gli oggetti che la compongono.
In questo modo impareremo a strutturare ogni finestra in una classe e se necessario a strutturare i nostri progetti dividendo ogni classe in un file diverso, in modo da favorire al massimo l’organizzazione fortemente orientata agli oggetti e tutti le buone cose che ne derivano (organizzazione del codice, chiara divisione dei compiti fra le classi, semplicità nel riutilizzare il codice, etc..)
Dimensione e posizionamento
Se vogliamo modificare la dimensione di una finestra possiamo farlo in 2 modi:
o fornendo una dimensione iniziale nel costruttore della stessa, oppure utilizzando la funzione SetSize(width, height).
Faccio notare che tutte le misure sono espresse in pixel.
import wx
app = wx.App()
window = wx.Frame(None, title="Finestra 800x600", size=(800,600))
window.Show()
app.MainLoop()
…oppure…
import wx
app = wx.App()
window = wx.Frame(None, title="Finestra 800x600")
window.SetSize(800,600)
window.Show()
app.MainLoop()
Entrambi i metodi sono semplici ed efficaci. Dal punto di vista operativo, il metodo SetSize() può essere chiamato all’interno della funzione
__init__ quando si definisce una finestra derivata.
In maniera analoga, se vogliamo specificare il posizionamento della finestra all’interno dello schermo possiamo
specificare la posizione iniziale nel costruttore o eseguire successivamente la funzione Move(x,y)
import wx
app = wx.App()
window = wx.Frame(None, title="Finestra al punto (5,5)", pos=(5,5))
window.Show()
app.MainLoop()
…oppure…
import wx
app = wx.App()
window = wx.Frame(None, title="Finestra 800x600")
window.Move(5,5)
window.Show()
app.MainLoop()
Anche qui, dal punto di vista operativo, eseguiremo questo codice nel costruttore della classe grafica derivata o
all’interno della funzione __init__.
Un’ultima cosa, semplice e molto utile può essere quella relativa al posizionamento automatico con la funzione Centre(),
che posiziona automaticamente la finestra al centro dello schermo:
import wx
app = wx.App()
window = wx.Frame(None, title="Finestra Centrata")
# esegui questo codice, poi commenta la riga qui sotto e rieseguilo
window.Centre()
window.Show()
app.MainLoop()
Adesso avanti! Il prossimo step è quello di interagire un pò con l’applicazione! Inserire un pulsante e fargli fare qualcosa!
Prima però… una doverosa precisazione!!!
Documentazione Ufficiale
La libreria wxPython contiene circa 1000 classi grafiche, molte delle quali sono collegate fra loro tramite ereditarietà.
Questa informazione è decisiva anche dal punto di vista della documentazione: ad esempio, le funzioni di posizionamento e ridimensionamento viste sopra, vengono
dalla classe wx.Window da cui la classe wx.Frame deriva, così come ogni oggetto grafico della libreria wxPython (avete letto bene,,, tutti!!!!).
Questo significa che tutte le classi hanno questi metodi e che voi già da qualche minuto sapete spostare e ridimensionare ogni oggetto grafico!!!
Da un altro punto di vista, in questa dispensa introdurremo (non studieremo approfonditamente… introdurremo) una cinquantina circa fra gli oggetti grafici più importanti. Qualsiasi funzionalità o caratteristica interessante che non viene descritta nella dispensa sarà da ricercare nella documentazione ufficiale!!!
La documentazione ufficiale wxPython si trova sul sito https://docs.wxpython.org/.
Da lì, dovrete imparare a cercare le classi che vi interessano, capire per ognuna da quali classi deriva, quali sono i suoi metodi, le sue proprietà, le sue caratteristiche principali.
Per comodità, metto qui i link diretti alle due classi che abbiamo studiato finora:
-
wx.App: Gestisce le applicazioni con GUI implementate tramite wxPython.
-
wx.Frame: Finestra vuota generica, con barra del titolo, bordi e ridimensionamento funzionanti.
Provate a curiosare un pò e a capire come ricercare informazioni.
Buon lavoro!!
Gestione degli eventi
Una volta iniziato il Main Event Loop, le applicazioni wxPython sono in grado di intercettare gli eventi utente, come ad esempio un click su un pulsante o sulla tastiera o un movimento del mouse.
Tutte questi eventi sono catalogati negli oggetti, ovvero ogni widget sa quali eventi intercettare: ad esempio un pulsante saprà intercettare un click su di esso, una linea di testo sarà in grado di capire quando ci scrivi dentro, etc…
Deciso l’evento a cui rispondere (ad esempio, un click su pulsante) è possibile abbinare a questo una funzione, in modo che quando l’evento accade, la funzione venga eseguita!
Per farlo dobbiamo utilizzare la funzione Bind:
widget.Bind ( EVENTO, FUNZIONE_EVENTO )
Vediamo un esempio di codice di una Finestra con un grosso pulsante dentro: al click sul pulsante viene eseguita una funzione e… provate a copiare il codice e ad eseguirlo per vedere cosa succede!
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Cliccami")
pulsante = wx.Button(self, label="Chiudi tutto")
pulsante.Bind(wx.EVT_BUTTON, self.funzioneEvento)
def funzioneEvento(self, evt):
self.Close()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Note
Funzioni Evento
Le funzioni che possono essere collegate con un Bind ad un evento si dicono
funzioni evento. Questo tipo di funzioni ha sempre (e solo) due parametri:
self, poiché sono sempre funzioni membro di una classeevt, che rappresenta l’evento che le ha scatenate.
Provate a sostituire la funzione self.Close() con altre funzioni tipo:
self.Maximize(): massimizza la finestraself.Iconize(): riduce a icona la finestraself.Move(0,0): sposta la finestra nel punto (0,0)self.SetSize(400,300): ridimensiona la finestra- etc…
Ormai avete capito tutto… io nel dubbio scrivo un pò di deduzioni a partire da questi esempi…
- L’oggetto che rappresenta un pulsante si chiama
wx.Button. - L’evento predefinito per un pulsante è il click su di esso: questo evento, per un pulsante, si chiama
wx.EVT_BUTTON. - Probabilmente (ed è proprio così) se una widget si chiamasse
wx.Pippo, il suo evento predefinito si chiamerebbewx.EVT_PIPPO - Le funzioni che rispondono agli eventi hanno tutte la struttura
nomeFunzione (self, event).
Tip
Come si fa a sapere quali (altri) eventi può gestire un pulsante (wx.Button)?
Leggi la documentazione ufficiale della classe wx.Button
Identificare le widgets
La libreria wxPython mette a disposizione una serie di funzionalità utili per identificare una widget. Vediamo le principali soluzioni offerte:
Indentificare la widget che ha scatenato un evento
La funzione evento prende come parametro l’evento che l’ha scatenata. Questo può ritornare l’evento che l’ha generato tramite la funzione evt.GetEventObject().
Vediamo:
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Cliccami")
pulsante = wx.Button(self, label="Fai Qualcosa")
pulsante.Bind(wx.EVT_BUTTON, self.funzioneEvento)
def funzioneEvento(self, evt):
# btn è il pulsante che ha generato l'evento
btn = evt.GetEventObject()
btn.SetLabel("Fatto!")
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Identificare una widget tramite un Id
La libreria wxPython identifica in maniera automatica tutte le widget con un ID sempre diverso e sempre negativo
È comunque possibile impostare un identificativo a scelta, con una importante raccomandazione: scegliete ID positivi e crescenti. Partite da 1 e andate avanti: in questo modo sarete sicuri di non fare confusione con gli ID assegnati automaticamente alle widget da wxPython e di non sbagliarvi da soli mettendo due volte lo stesso ID!
Nel prossimo esempio abbiamo due pulsanti (con ID impostato) collegati alla stessa funzione. L’id permette di distinguere quale dei due ha scatenato l’evento.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="2 pulsanti, 1 funzione")
# i due pulsanti sono identificati con ID 1 e 2
pulsante1 = wx.Button(self, label="pulsante 1", pos=(5,5), size=(100,30), id=1)
pulsante2 = wx.Button(self, label="pulsante 2", pos=(120,5), size=(100,30), id=2)
pulsante1.Bind(wx.EVT_BUTTON, self.faiQualcosa)
pulsante2.Bind(wx.EVT_BUTTON, self.faiQualcosa)
def faiQualcosa(self, evt):
# la funzione GetId ci dice l'ID della widget che ha scatenato l'evento
id = evt.GetId()
print("Hai cliccato il pulsante con ID =", id)
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Recuperare una widget conoscendo il suo Id
Vi risparmio qualunque esempio, ma immaginate di avere assegnato gli Id da 1 a 10 ad altrettante widgets. Per riottenere
una qualsiasi delle widgets in oggetto, basta eseguire la funzione FindWindow(id)
# per ottenere la widget con ID = 5
widget = self.FindWindow(5)
Gestire l’evento Close()
La funzione Close(), che abbiamo già precedentemente incontrato, provoca la chiusura
di una finestra. In realtà però, essa genera un evento di chiusura per la finestra (CloseEvent),
che poi questa potrà gestire per programmare la sua chiusura.
Il caso tipico in programmazione per questa problematica è quando l’utente prova a chiudere un editor con il file non ancora salvato! In quel caso l’applicazione blocca la chiusura suggerendo di salvare prima il file!
Nell’esempio che segue la finestra che appare è chiudibile dall’utente (con scorciatoia, cliccando sulla x in alto, etc..) solo se massimizzata.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Massimizza per chiudere")
self.Bind(wx.EVT_CLOSE, self.chiudi)
def chiudi(self, evt):
if (self.IsMaximized()):
self.Destroy()
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Come al solito… copiate e provate!
Warning
Quando ci si connette all’evento wx.EVT_CLOSE (e tipicamente… solo in questo caso) è necessario chiudere la finestra utilizzando la funzione
Destroy()invece della funzioneClose().Infatti: funzione
Close()—> eventowx.EVT_CLOSE—> bind alla funzione chiudi —> funzioneClose()—> CICLO INFINITO!
Ok, definiti gli eventi più semplici e capito come collegarli alle widget, vediamo le widgets e i layout per creare delle applicazioni con un look consistente.
Le widgets
Warning
Negli esempi che seguono, scritti con l’unico obiettivo di presentare le widget, andremo a posizionare più oggetti nella stessa finestra tramite il cosiddetto posizionamento assoluto, inserendo tutte le widget in un
wx.Panele indicando posizione (pos) e dimensione (size) degli oggetti.Questo modo di fare, pur semplice e intuitivo, è comunque assolutamente sconsigliato e sarà sostituito nel prossimo capitolo da un concetto molto più avanzato e funzionale: i layout!.
Per adesso… cerchiamo di capire bene come funzionano le widgets!!!
In questa sezione andremo ad inserire le Widgets più semplici, per capire come sono fatte e come funzionano. Per ognuna di esse sarà proposta una descrizione sommaria delle sue caratteristiche, un’immagine esplicativa, un semplice esempio di codice per testare il funzionamento di base e alcuni esercizi basilari per prendere confidenza con il codice.
Tip
Documentazione Ufficiale
Ricordo che la documentazione completa di ognuna di queste widget è disponibile nel sito ufficiale
All’inizio di ognuna delle classi che vado ad introdurre metterò invece un link diretto alla documentazione ufficiale della classe!
wx.Frame
Documentazione ufficiale wx.Frame
Finestra vuota generica, con barra del titolo, bordi e ridimensionamento funzionanti.
La classe wx.Frame rappresenta una finestra vuota, con bordo e aspetto
nativo nel sistema operativo ospite, con le funzionalità tipiche delle
finestre già implementate e funzionanti senza fare nulla: trascinamento,
ridimensionamento, barra del titolo, riduzione ad icona,
massimizzazione, etc…

Non c’è molto da spiegare su questo che non abbiamo già visto… provate a copiare l’esempio sotto e a giocare un pò con la finestra. Poi magari date un occhio alla documentazione.
import wx
app = wx.App()
win = wx.Frame(None, title="Un Esempio con wx.Frame")
win.Show()
app.MainLoop()
wx.Panel
Documentazione ufficiale wx.Panel
Pannello utile per organizzare il layout.
La classe wx.Panel rappresenta una widget vuota, adatta ad essere
utilizzata come contenitore delle altre che vedremo.
La sua utilità sta nel fatto che fornisce nativamente interazione con la tastiera (Tab Traversal, lo spostamento fra una widget e l’altra premendo Tab) per sè e per tutte le widget al suo interno.
Da ora in poi la utilizzeremo in tutti i nostri progetti!. Anche dal punto di vista visivo, un Frame senza un Panel sembra vuoto, mentre un frame con dentro un panel sembra normalmente pieno.
Tip
Da ora in poi utilizzeremo il
wx.Panelin tutti i nostri progetti. Esso costituirà anche un livello intermedio di parentela fra le widgets in maniera che:
- il Frame nasce senza
parent- il Panel nasce con
parentil Frame (tipicamenteself)- Tutte le widget inserite nella finestra, che giacciono sopra il Panel, hanno il panel stesso come
parentTutti gli esempi da qui in poi sono strutturati in questo modo: vi basterà osservarli un minimo :)

wx.Button
Documentazione ufficiale wx.Button
Pulsante cliccabile.
La classe wx.Button rappresenta una widget che implementa un pulsante cliccabile con una etichetta di testo dentro.

Nell’esempio proposto quando si clicca il pulsante (evento wx.EVT_BUTTON) si riduce ad icona l’applicazione.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Iconificami")
panel = wx.Panel(self)
pulsante = wx.Button(panel, label="Riduci ad icona", pos=(5,5), size=(150,40))
pulsante.Bind(wx.EVT_BUTTON, self.iconifica)
def iconifica(self, evt):
self.Iconize()
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2111 (Button)
Creare una applicazione con un pulsante CHIUDI. Quando lo clicchi, l’applicazione si chiude.
Esercizio 2112 (Button)
Creare una applicazione con un pulsante MASSIMIZZA. Quando lo clicchi, l’applicazione si massimizza.
Esercizio 2113 (Button)
Creare una applicazione con un pulsante NUOVA FINESTRA. Quando lo clicchi, si apre una nuova finestra, identica alla precedente.
wx.ToggleButton
Documentazione ufficiale wx.ToggleButton
Pulsante con due stati: cliccato e non cliccato.
La classe wx.ToggleButton rappresenta una widget che implementa un pulsante con due stati: cliccato e non cliccato.

Nell’esempio proposto quando si clicca il ToggleButton (evento wx.EVT_TOGGLEBUTTON) si mette a schermo intero
la finestra e si cambia la scritta sul pulsante (che rimane cliccato).
Cliccandolo di nuovo si ritorna alla situazione iniziale.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Fullscreen(er)")
panel = wx.Panel(self)
pulsanteToggle = wx.ToggleButton(panel, label="Metti fullscreen", pos=(5,5), size=(150,40))
pulsanteToggle.Bind(wx.EVT_TOGGLEBUTTON, self.mettiFullScreen)
def mettiFullScreen(self, evt):
pulsanteCliccato = evt.GetEventObject()
if pulsanteCliccato.Value:
self.ShowFullScreen(True)
pulsanteCliccato.SetLabel("Esci dal fullscreen")
else:
self.ShowFullScreen(False)
pulsanteCliccato.SetLabel("Metti fullscreen")
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2115 (ToggleButton)
Creare una applicazione con una finestra con titolo Ciccio che contiene un pulsante “Toggle”. Quando clicchi il pulsante, il titolo diventa Pippo, quando lo clicchi di nuovo ritorna ad essere Ciccio e così via.
Esercizio 2116 (ToggleButton)
Creare una applicazione con una finestra che contiene un pulsante “Toggle”. Quando clicchi il pulsante, appare una nuova finestra vuota di fianco a questa; quando lo clicchi di nuovo la finestra scompare.
wx.StaticText
Documentazione ufficiale wx.StaticText
Etichetta dove il programmatore può inserire un testo da visualizzare all’utente.
La classe wx.StaticText rappresenta una widget che implementa una etichetta dove il programmatore
può inserire un testo da visualizzare all’utente.

Nel semplicissimo esempio proposto, viene utilizzata la widget wx.StaticText per visualizzare una poesia in più righe.
La widget è inserita a “tutta finestra”.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Soldati")
panel = wx.Panel(self)
etichetta = wx.StaticText(panel, label = "Si sta come\nd'autunno\nsugli alberi\nle foglie\n")
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
wx.StaticLine
Documentazione ufficiale wx.StaticLine
Linea decorativa, orizzontale o verticale.
La classe wx.StaticLine rappresenta una widget che implementa una linea decorativa, orizzontale o verticale.

Inserire una wx.StaticLine è veramente semplice e serve solo a scopo decorativo.
Approfitto di questa per farvi vedere come modificare il font di una wx.StaticText e realizzare una widget con
titolo, linea e testo, come vedete nella figura.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Poesia")
panel = wx.Panel(self)
# Font grassetto di 20pt
bigBoldFont = wx.Font( wx.FontInfo(20).Bold() )
titolo = wx.StaticText(panel, label="Soldati",pos=(10,10), size=(200,30))
titolo.SetFont(bigBoldFont)
linea = wx.StaticLine(panel, pos=(10,50), size=(200,3))
testo = "\nSi sta come\nd'autunno\nsugli alberi\nle foglie\n"
etichetta = wx.StaticText(panel, label=testo, pos=(10,60), size=(200,150))
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
wx.TextCtrl
Documentazione ufficiale wx.TextCtrl
Casella di testo.
La classe wx.TextCtrl rappresenta una widget che implementa una casella di testo, con vari stili supportati,
definibili in fase di inizializzazione:
casella = wx.TextCtrl( window , style=OPTIONS )
Cito qui i più importanti:
| Stile | Descrizione |
|---|---|
| (default) | Linea singola di testo, digitabile dall’utente |
| wx.TE_MULTILINE | Permette alla TextCtrl di utilizzare più righe |
| wx.TE_PASSWORD | Ogni carattere viene visualizzato con un asterisco |
| wx.TE_READONLY | Testo non modificabile dall’utente |
| wx.TE_PROCESS_ENTER | Gestisce il tasto Enter come evento separato |
Note
Per inserire 2 o più stili nella stessa TextCtrl utilizza il
pipe(|) in questo modo:# TextCtrl multilinea con gestione del pulsante INVIO text = wx.TextCtrl(panel, style = wx.TE_PASSWORD | wx.TE_PROCESS_ENTER
la TextCtrl non ha un evento di default, ma ben due utilizzabili distintamente:
wx.EVT_TEXT: viene scatenato ogni volta che cambia il testo contenuto nella widgetwx.EVT_TEXT_ENTER: viene scatenato quando hai impostato lo stilewx.TE_PROCESS_ENTERe si preme INVIO nella widget
Nell’esempio proposto ho inserito una TextCtrl con caratteri oscurati (password). Man mano che si digita su di essa, il testo appare nella StaticText a fianco. Provate a testare l’esempio autonomamente e a modificare il codice per visualizzare ciò che è stato digitato solo alla fine, quando si preme INVIO nella TextCtrl.

import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="TextCtrl Examples")
panel = wx.Panel(self)
self.text1 = wx.TextCtrl(panel, style=wx.TE_PASSWORD, pos=(5,5), size=(200,50))
self.static1 = wx.StaticText(panel, label="", pos=(5,70))
self.text1.Bind(wx.EVT_TEXT, self.aggiornaTesto)
def aggiornaTesto(self, evt):
self.static1.SetLabel( self.text1.GetValue() )
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2131 (Button, StaticText, TextCtrl)
Creare una applicazione con una finestra che contiene una StaticText, inizialmente vuota, una TextCtrl e un pulsante. Quando l’utente clicca sul pulsante, il testo contenuto nella TextCtrl viene copiato nella StaticText e la TextCtrl ripulita.
Esercizio 2132 (TextCtrl, Button)
Salvate questo esercizio come es132.py in una cartella e nella stessa creerete un file
chiamato testo.txt contenente un testo a piacere.
Create una Finestra con una TextCtrl multilinea vuota e un pulsante. Quando
l’utente clicca il pulsante, il programma carica dal file testo.txt il contenuto
e lo visualizza nella TextCtrl.
wx.ComboBox
Documentazione ufficiale wx.ComboBox
Menù a tendina
La classe wx.ComboBox rappresenta una widget che implementa un menù a tendina

La ComboBox supporta i seguenti stili di lavoro:
| Stile | Descrizione |
|---|---|
| (default) | Linea singola di testo editabile + menù a tendina |
| wx.CB_READONLY | Linea di testo NON editabile. |
| wx.CB_SORT | Ordina automaticamente le voci |
| wx.TE_PROCESS_ENTER | Gestisce il tasto Enter come evento separato |
Nell’esempio proposto vediamo una etichetta e un menù a tendina.
Selezionando una voce del menù a tendina (evento wx.EVT_COMBOBOX) si modifica il contenuto dell’etichetta.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Cambia l'etichetta")
panel = wx.Panel(self)
self.etichetta = wx.StaticText(panel, label="seleziona una voce", pos=(5,5), size=(200,30))
frutta = ["pere", "mele", "arance", "banane"]
self.combo = wx.ComboBox(panel, choices=frutta, style=wx.CB_READONLY, pos=(5,40), size=(200,30))
self.combo.Bind(wx.EVT_COMBOBOX, self.visualizzaSelezione)
def visualizzaSelezione(self, evt):
f = self.combo.GetValue()
self.etichetta.SetLabel("Hai selezionato: " + f)
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2136 (TextCtrl, Button, ComboBox)
Inserite nella finestra una ComboBox inizialmente vuota, una TextCtrl e un Button. L’utente digita qualcosa nella TextCtrl e quando clicca il pulsante, se la TextCtrl non è vuota, aggiunge la parola alla ComboBox e pulisce la TextCtrl.
Esercizio 2137 (Frame, ComboBox)
la finestra presenta una ComboBox con opzioni ‘600x400’, ‘800x600’, ‘massimizza’. Quando l’utente seleziona una delle opzioni la finestra si ridimensiona secondo quanto indicato.
Esercizio 2138 (StaticText, ComboBox, TextCtrl, Button)
Inserite nella finestra una StaticText con scritto “nome” e accanto una TextCtrl per l’inserimento.
Nella riga sotto, una nuova StaticText con scritto “cognome” e accanto una TextCtrl per l’inserimento.
Nella riga sotto una StaticText con scritto “sesso” e accanto una ComboBox contenente le opzioni “M” e “F”.
Nella riga sotto un pulsante STAMPA.
Quando si clicca il pulsante viene creato il file “dati.txt” nella stessa cartella dello script python con scritto “Signor Nome Cognome” oppure “Signora Nome Cognome” a seconda del sesso selezionato.
PS: Ovviamente sul file non dovete scrivere veramente Nome e Cognome…
wx.CheckBox
Documentazione ufficiale wx.CheckBox
Casella di spunta.
La classe wx.CheckBox rappresenta una widget che implementa una casella di spunta, tipicamente di forma squadrata.

Nell’esempio proposto abbiamo una sola casella di spunta che quando viene abilitata imposta lo sfondo rosso della finestra, riportando quello originale se deselezionata.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Cambia il colore di sfondo")
self.panel = wx.Panel(self)
self.color = self.panel.GetBackgroundColour()
self.check = wx.CheckBox(self.panel, label="sfondo rosso", pos=(5,5))
self.check.Bind(wx.EVT_CHECKBOX, self.cambiaSfondo)
def cambiaSfondo(self, evt):
if self.check.GetValue():
self.panel.SetBackgroundColour("red")
self.check.SetForegroundColour("white")
else:
self.panel.SetBackgroundColour(self.color)
self.check.SetForegroundColour("black")
self.Refresh()
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2141 (StaticText, CheckBox)
Serie di 5 CheckBox con una selezione di cibi (es: pasta, pizza, etc…). Man mano che l’utente seleziona cibi la StaticText sotto alle CheckBox si aggiorna mostrando tutti i cibi selezionati.
Esercizio 2142 (StaticText, CheckBox)
Identico all’esercizio precedente, ma la StaticText non visualizza il testo delle CheckBox selezionate ma il numero di queste. Ad esempio all’inizio vi sarà scritto “0 CheckBox selezionate” e il numero crescerà o diminuirà a seconda che vengono attivate o no le CheckBox.
Esercizio 2143 (Button, CheckBox)
la finestra contiene 3 pulsanti: il primo massimizza la finestra, il secondo la iconizza sulla barra delle applicazioni, il terzo la chiude. Sotto i pulsanti ci sono 3 CheckBox che corrispondono ognuna ad un pulsante: se la CheckBox è spuntata, il pulsante corrispondente è abilitato, altrimenti è disabilitato. Fate attenzione a sincronizzare la spunta sulla CheckBox con l’abilitazione del pulsante corrispondente e viceversa.
Esercizio 2144 (Frame, ComboBox, CheckBox)
Dichiarate una tupla di valori qualsiasi e con essa create sia una ComboBox che elenca tutti gli elementi della tupla, sia una serie di CheckBox, una per ogni valore della tupla. Quando l’utente seleziona uno dei valori della ComboBox, la CheckBox corrispondente cambia stato, venendo spuntata oppure no a seconda del suo stato precedente.
PS: se non avete pensato ad usare un dizionario… beh… ricominciate a pensare!
wx.RadioButton
Documentazione ufficiale wx.RadioButton
Casella selezionabile in maniera mutualmente esclusiva.
La classe wx.RadioButton rappresenta una widget che implementa una casella, tipicamente rotonda, selezionabile in maniera mutualmente esclusiva.

Per definire il comportamento tipico dei RadioButton della mutua esclusività si deve dichiarare nella prima widget lo stile
wx.RB_GROUP: tutte le RadioButton seguenti faranno parte del gruppo del primo. Se si vuole iniziare un nuovo gruppo,
basterà inserire di nuovo lo stile necessario.
Nell’esempio proposto saranno inserite 2 radio buttons per selezionare il sesso (M/F).
Al click su una di loro (evento wx.EVT_RADIOBUTTON) l’altra si deselezionerà automaticamente e l’etichetta sotto verrà
aggiornata.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Seleziona una opzione")
panel = wx.Panel(self)
self.rbM = wx.RadioButton(panel, label="Maschio", style=wx.RB_GROUP, pos=(5,5))
self.rbF = wx.RadioButton(panel, label="Femmina", pos=(5,35))
self.testo = wx.StaticText(panel, label="Voce selezionata: Maschio", pos=(5,65))
self.rbM.Bind(wx.EVT_RADIOBUTTON, self.impostaSesso)
self.rbF.Bind(wx.EVT_RADIOBUTTON, self.impostaSesso)
def impostaSesso(self, evt):
if self.rbM.GetValue():
self.testo.SetLabel("Hai selezionato: Maschio")
else:
self.testo.SetLabel("Hai selezionato: Femmina")
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2146 (RadioButton, StaticText)
Implementare un testo con domanda: Dimmi come vieni a scuola e un elenco di opzioni a vostra scelta, tra cui ad esempio auto, moto, bici, bus, etc.. Sotto a questo, un’altra StaticText, che alla selezione di un mezzo di trasporto selezionerà la scritta: vieni a scuola in MEZZODITRASPORTO.
Esercizio 2147 (TextCtrl, RadioButton, Button, StaticText)
Applicazione con una TextCtrl per inserire il nome e un RadioButton per selezionare il sesso (opzioni Maschio, Femmina) più un pulsante e una StaticText. Al click sul pulsante va visualizzata nella StaticText la scritta Buongiorno signor NOME oppure Buongiorno signora NOME a seconda del sesso selezionato.
Come ulteriore difficoltà si può aggiungere il controllo dell’ora: se il pulsante viene cliccato fra le 6 e le 14, si utilizza Buongiorno, fra le 14 e le 22 si scrive Buonasera, fra le 22 e le 6 si scrive Buonanotte.
Esercizio 2148 (TextCtrl, RadioButton, Button)
Applicazione con una TextCtrl e un pulsante. L’utente scrive una serie di parole separate da virgola nella TextCtrl e quando preme il pulsante appare un RadioButton con le opzioni indicate nella TextCtrl e separate da virgole.
wx.Slider
Documentazione ufficiale wx.Slider
Barra di scorrimento lineare.
La classe wx.Slider rappresenta una widget che implementa un cursore ad
avanzamento lineare.

Nell’esempio proposto ho implementato uno slider che va da 0 a 10 (il range predefinito è 0-100) e ho impostato il valore iniziale a 5.
Sotto c’è una etichetta che si aggiorna automaticamente quando si muove lo slider (evento wx.EVT_SLIDER)
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Muovi lo slider")
panel = wx.Panel(self)
self.slide = wx.Slider(panel, pos=(5,5), size=(250,-1))
self.slide.SetRange(0,10)
self.slide.SetValue(5)
self.testo = wx.StaticText(panel, label="Valore: 5", pos=(5,35))
self.slide.Bind(wx.EVT_SLIDER, self.aggiornaValore)
def aggiornaValore(self, evt):
v = self.slide.GetValue()
self.testo.SetLabel("Valore: " + str(v))
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2151 (Slider, StaticText)
Inserite nella finestra uno Slider che va da 1 a 10 e aggiungete una StaticText che visualizza il suo valore aggiornato in tempo reale.
Esercizio 2152 (TextCtrl, Button, Slider)
Inserite nella finestra due coppie TextCtrl/Button. La prima coppia decide il valore minimo dello Slider, la seconda coppia il valore massimo. I pulsanti aggiornano lo stato dello Slider.
Esercizio 2153 (Slider, Button)
Inserite nella finestra due Slider, uno orizzontale che va da 200 a 800 per la larghezza e uno verticale che va da 100 a 500 per l’altezza. L’utente muove gli slider a piacimento e quando clicca il pulsante si modifica la dimensione della finestra.
wx.SpinCtrl
Documentazione ufficiale wx.SpinCtrl
Selettore numerico con pulsanti di avanzamento.
La classe wx.SpinCtrl rappresenta una widget che implementa un selettore numerico con pulsanti di avanzamento.

Nell’esempio proposto quando muovo il controllo della wx.SpinCtrl l’etichetta si aggiorna automaticamente (evento wx.EVT_SPINCTRL).
Come ormai tradizione in questi esempi, approfitto di una widget facile per introdurre una piccola novità: Il pulsante a due stati
serve per abilitare e disabilitare la widget. Provate!
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Seleziona numero")
panel = wx.Panel(self)
self.spin = wx.SpinCtrl(panel, value="0", pos=(5,5), size=(150,30))
self.spin.SetRange(-10,10)
self.testo = wx.StaticText(panel, label="Valore: 0", pos=(5,45), size=(150,30))
self.pulsante = wx.ToggleButton(panel, label="Blocca spin", pos=(5,85), size=(150,30))
self.spin.Bind(wx.EVT_SPINCTRL, self.aggiornaValore)
self.pulsante.Bind(wx.EVT_TOGGLEBUTTON, self.bloccaSpinCtrl)
def aggiornaValore(self, evt):
v = self.spin.GetValue()
self.testo.SetLabel("Valore: " + str(v))
return
def bloccaSpinCtrl(self, evt):
if self.pulsante.GetValue():
self.spin.Enable(False)
self.pulsante.SetLabel("Sblocca")
else:
self.spin.Enable(True)
self.pulsante.SetLabel("Blocca spin")
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2156 (SpinCtrl, Button, StaticText)
Inserite nella finestra una SpinCtrl per inserire un intero fra 1 e 10. Quando si clicca il pulsante nella StaticText sotto appare il countdown dal numero selezionato fino a zero.
Esercizio 2157 (SpinCtrl…)
Un altro esercizio con la SpinCtrl
wx.StaticBox
Documentazione ufficiale wx.StaticBox
Contenitore decorativo con bordo.
La classe wx.StaticBox rappresenta una widget che implementa una decorazione per raggruppare le widget.
Utile per organizzare il layout.

Nell’esempio proposto vediamo come la StaticBox, con etichetta “Informazioni Personali” serva a raggruppare logicamente le widget che permettono all’utente di inserirli. Non sapendo ancora organizzare un layout (lo vedremo a breve!!!) voglio farvi notare che gli oggetti sono dentro la StaticBox solo perché a livello di dimensione essa riesce a contenerli tutti tranne il pulsante in fondo!!!
Provate a giocare un pò con i valori del parametro size per rendervene conto.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Esempio con StaticBox")
panel = wx.Panel(self)
self.box = wx.StaticBox(panel, label='Informazioni personali', pos=(5, 5), size=(240, 170))
self.sex = wx.CheckBox(panel, label='Maschio', pos=(15, 30))
self.married = wx.CheckBox(panel, label='Sposato', pos=(15, 55))
self.text = wx.StaticText(panel, label='Età', pos=(15, 95))
self.age = wx.SpinCtrl(panel, value='1', pos=(55, 90), size=(120, -1), min=1, max=120)
self.button = wx.Button(panel, label="Chiudi", pos=(90, 185), size=(120, -1))
self.button.Bind(wx.EVT_BUTTON, self.chiudi)
def chiudi(self, evt):
self.Close()
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
wx.ListBox
Documentazione ufficiale wx.ListBox
Lista verticale di elementi di testo.
La classe wx.ListBox rappresenta una widget che implementa una lista di voci selezionabili in maniera singola o multipla.

Questa widget supporta 2 eventi considerabili di default:
wx.EVT_LISTBOX: scatenato quando si seleziona un elemento della listawx.EVT_LISTBOX_DCLICK: scatenato quando si fa doppio click su un elemento della lista
Come al solito vediamo il codice che genera l’immagine che vedete sopra:
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="ListBox (ce l'ho con la frutta...)")
panel = wx.Panel(self)
frutta = ["pere", "mele", "arance", "banane"]
self.list = wx.ListBox(panel, choices=frutta, pos=(10,10), size=(200,300))
self.etichetta = wx.StaticText(panel, pos=(250,10), size=(200,50))
self.list.Bind(wx.EVT_LISTBOX, self.updateLabel)
def updateLabel(self, evt):
index = self.list.GetSelection()
self.etichetta.SetLabel( self.list.GetString(index) )
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Provate a cambiare l’evento nel Bind per vedere la differenza di funzionamento.
Esercizi
Esercizio 2166 (ListBox, Button, StaticText)
Inserite nella finestra una ListBox con una sequenza di voci e la possibilità di selezionarne contemporaneamente più di una. Quando l’utente clicca il pulsante nella StaticText vengono visualizzate tutte le voci selezionate, separate da virgola.
Esercizio 2167 (ListBox, TextCtrl, Button)
Inserite nella finestra una ListBox inizialmente vuota, una TextCtrl e un pulsante. L’utente digita qualcosa nella TextCtrl e quando clicca il pulsante, se la TextCtrl non è vuota, aggiunge la parola alla ListBox e pulisce la TextCtrl.
Esercizio 2168 (ListBox, Button)
Inserite nella finestra una ListBox vuota e un pulsante. Quando l’utente clicca il pulsante, il programma carica dal file dati.txt presente nella stessa cartella (dovete crearlo voi, con una parola ogni riga: ogni riga del file diventerà una voce nella ListBox).
Gestione del Layout
Il layout management, ovvero la gestione del posizionamento delle widget all’interno della finestra è l’arte di collocare gli oggetti tramite strumenti che permettano un posizionamento semplice e allo stesso tempo all’applicazione di avere elasticità nei movimenti e nei ridimensionamenti.
Invece di chiacchierare oltre, partiamo con un esempio: ricordate la solita finestrella con pulsante chiudi collegato in un certo punto con una certa dimensione? Bene! Vi ripropongo il codice che abbiamo già visto un paio di volte:
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Chiudi")
pannello = wx.Panel(self)
self.pulsante = wx.Button(pannello, label="Chiudi", pos=(100,100), size=(150,40))
self.pulsante.Bind(wx.EVT_BUTTON, self.chiudiFinestra)
def chiudiFinestra(self, evt):
self.Close()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Ok… questa è l’immagine della finestra creata con questo codice:

Bene, adesso invece di cliccare sul pulsante provate a ridimensionare la finestra… visto cosa succede? Il pulsante rimane fermo nella sua posizione senza ridimensionarsi o spostarsi minimamente e viene presto coperto dalla finestra contenitrice, anche se avrebbe spazio a sinistra per rendersi visualizzabile.

Infatti con il posizionamento assoluto, ovvero inserendo le widget con pos e size, il contenitore (ovvero la finestra con bordo) non ha voce in capitolo sul posizionamento delle widget contenute. Immaginate una finestra con parecchie widget, un disastro!!
Per implementare correttamente un layout in wxPython dobbiamo ricorrere ai contenitori di ridimensionamento (sizers)!!! Questi oggetti (non grafici) possono contenere oggetti grafici secondo una disposizione a fila o a griglia e organizzano la dimensione del contenente (tipicamente la finestra o il pannello) in base alle dimensioni del contenuto (tipicamente… le widgets). Questi contenitori presentano una serie di caratteristiche comuni:
-
Una dimensione minima: che è tipicamente identica alla dimensione iniziale delle widget contenute in esso ma che può essere esplicitata dal programmatore tramite codice.
-
Un bordo: uno spazio vuoto utile solo a distanziare minimamente le widgets dal bordo reale del contenitore (la finestra o altro). Il programmatore ha pieno controllo di esso nelle quattro direzioni.
-
Un allineamento: questo è tipicamente il compito principale del sizer. Ogni sizer induce un allineamento caratteristico. Li vedremo nel dettaglio studiandoli uno ad uno.
-
Un fattore di allungamento: una caratteristica che controlla il ridimensionamento delle widgets quando viene ridimensionato il loro contenitore. Cosa succede se aumentiamo la larghezza di una finestra in cui abbiamo oggetti gestiti da un sizer? Questo parametro regola il comportamento degli stessi in questo caso.
La libreria wxPython offre i seguenti oggetti per gestire il layout:
| Sizer | Descrizione |
|---|---|
wx.BoxSizer | Contenitore per una riga (o una colonna) di widget |
wx.StaticBoxSizer | Come il Box Sizer ma con un contorno evidenziato (una StaticBox) |
| - - - - - - - - - - | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
wx.GridSizer | Contenitore a tabella per le widget |
wx.FlexGridSizer | Contenitore a tabella per le widget con dimensioni variabili |
wx.GridBagSizer | Contenitore a tabella con spanning |
Prima di iniziare a preoccuparvi, sappiate che i contenitori sono solo di 2 tipi:
- Contenitori a fila, come il Sizer
wx.BoxSizerche può essere impostato in senso orizzontale oppure verticale o comewx.StaticBoxSizer, che funziona esattamente come il precedente con la differenza di avere un contorno evidenziato intorno: un abbellimento e nulla più. - Contenitori a griglia, che determinano una organizzazione tabellare (pensate ad una calcolatrice, o a un form da compilare).
Il primo,
wx.GridSizerè quello più semplice; gli altri due aggiungono alcune caratteristiche avanzate di cui ci renderemo conto negli esempi sotto.
Ok, proviamo a vedere come funziona ognuno di questi, partendo dalle loro caratteristiche e vedendoli all’opera con qualche esempio di codice.
BoxSizer
Documentazione ufficiale wx.BoxSizer
Contenitore per una riga (o una colonna) di widget
La classe wx.BoxSizer può essere utilizzata per creare un layout orizzontale oppure verticale.
# se vuoi un layout orizzontale
hBox = wx.BoxSizer( wx.HORIZONTAL )
# se vuoi un layout verticale
vBox = wx.BoxSizer( wx.VERTICAL )
E fino a qui è molto semplice.
Per aggiungere widget al layout bisogna utilizzare la funzione Add che presenta una serie di opzioni che determinano come le widget si allargheranno nello spazio disponibile e metteranno bordo fra di loro. Vediamo la sintassi:
box.Add(widget, proportion = 0, flag = 0, border = 0)
Vediamo i parametri:
widget
è l’oggetto grafico (o un altro layout, nel caso di layuot più complessi) da inserire
proportion
indica la capacità di ridimensionamento della widget all’interno del layout. Può essere 0,1,2:
0(valore di default) significa che la widget non si ridimensionerà1significa che la widget si ridimensionerà nella direzione del layout proporzionalmente al suo contenitore2significa che la widget cercherà di occupare il doppio del posto di quelle con valore 1
flag
permette di decidere come la widget deve comportarsi all’interno del layout. Esistono 3 tipi di flag: il flag di espansione, i flag di allineamento e i flag di bordo.
Il flag di espansione è alternativo ai flag di allineamento: entrambi governano il posizionamento dell’oggetto nella direzione ortogonale al layout.
Il flag di espansione dice alla widget di occupare tutto lo spazio; i flag di allineamento la posizionano senza espansione ortogonale. I flag di bordo attivano
il bordo (specificato nel parametro border), nella direzione identificata dal flag.
| Flag di espansione | Layout di utilizzo | Widget |
|---|---|---|
wx.EXPAND | Tutti i layout | espansa su tutto lo spazio disponibile (ortogonalmente nel caso di un sizer lineare, nelle due direzioni per un sizer a griglia) |
I flag di allineamento impostano un allineamento nella direzione ortogonale al layout, nel caso di layout lineare; nella direzione indicata nel caso di un layout a griglia.
| Flag di allineamento | Layout di utilizzo | Widget |
|---|---|---|
wx.ALIGN_TOP | linea in orizzontale, griglia | incolonnata in alto |
wx.ALIGN_BOTTOM | linea in orizzontale, griglia | incolonnata in basso |
wx.ALIGN_CENTER_VERTICAL | linea in orizzontale, griglia | allineamento verticale al centro |
wx.ALIGN_LEFT | linea in verticale, griglia | allineata a sinistra |
wx.ALIGN_RIGHT | linea in verticale, griglia | allineata a destra |
wx.ALIGN_CENTER_HORIZONTAL | linea in verticale, griglia | allineamento orizzontale al centro |
wx.ALIGN_CENTER | griglia | allineamento centrato orizzontalmente e verticalmente |
I flag di bordo possono decidere la direzione del bordo fra una widget e l’altra. Essi sono:
| Flag di bordo | Layout di utilizzo | Widget |
|---|---|---|
wx.TOP | Tutti i layout | bordo verso l’alto |
wx.RIGHT | Tutti i layout | bordo verso destra |
wx.BOTTOM | Tutti i layout | bordo verso il basso |
wx.LEFT | Tutti i layout | bordo verso sinistra |
wx.ALL | Tutti i layout | bordo in tutte le direzioni |
Va detto che è possibile combinare 2 o più flag con il simbolo | (la barra verticale, si chiama pipe). Vediamo qualche esempio:
# bordo in alto e a sinistra con la widget in espansione
... flag = wx.TOP | wx.LEFT | wx.EXPAND ...
# widget allineata a destra con 10 pixel di bordo
... flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 10 )
border
rappresenta la dimensione in pixel del bordo della widget, nelle direzioni indicate dai flag di bordo (se non ci sono flag di bordo, è un parametro inutile).
Quando hai finito di lavorare con layout e widget devi applicare il layout al suo contenitore, che nei nostri esempi sarà sempre un pannello, quindi dovrai fare una cosa del genere:
# ...
# inserisci un sizer come layout del Panel
panel.SetSizer(box)
Caution
Questa cosa qui sopra di collegare il layout principale al pannello contenitore è molto molto molto molto molto molto importante. Del tipo… o ti ricordi, o non funziona nulla.
Fai tu…
Sono tante piccole cose insieme… necessitano di qualche esempio e qualche esercizio per chiarirci le idee.
Il prossimo codice crea un layout orizzontale con 2 pulsanti che vogliamo ridimensionarsi con la finestra (quindi proportion = 1)
e con un pochino di bordo in tutte le direzioni.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="BoxSizer Orizzontale")
panel = wx.Panel(self)
box = wx.BoxSizer(wx.HORIZONTAL)
p1 = wx.Button(panel, label="testo1")
p2 = wx.Button(panel, label="testo2")
# i 2 pulsanti si espandono allo stesso modo
# e hanno 5 pixel di bordo (fisso) in tutte le direzioni.
box.Add(p1, proportion=1, flag=wx.ALL, border=5)
box.Add(p2, proportion=1, flag=wx.ALL, border=5)
panel.SetSizer(box)
self.Centre()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Il risultato del codice precedente è questo:

Warning
Prima di andare avanti, proviamo a capire quello che abbiamo visto e fatto fino ad ora! Prova a fare le seguenti modifiche al codice precedente:
- metti a
0il valore di proportion di uno dei due pulsanti- metti a
2il valore di proportion di uno dei due pulsanti- aggiungi
wx.EXPANDai flag dei pulsanti- modifica il valore del bordo per uno dei pulsanti.
- togli
wx.ALLdai flag e metti qualcuno degli altri flag disponibiliFatte queste prove potrai apprezzare meglio quanto appreso finora ;)
Il prossimo esempio è leggermente più complicato (soprattutto perché è più lungo): mette insieme vari layout orizzontali e verticali combinandoli assieme fino ad ottenere ciò che vedete nella prossima immagine.
Si parte da un pannello e un layout verticale. Poi si crea man mano un layout orizzontale, si mettono le widget in esso e infine si inserisce il layout orizzontale in quello verticale principale. Ad un certo punto ho aggiunto anche degli spazi, che ho spiegato con un commento.

import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Prova layout con BoxSizer")
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
static1 = wx.StaticText(panel, label="Cerca: ")
text1 = wx.TextCtrl(panel)
hbox1.Add(static1, proportion = 0 , flag = wx.ALL, border = 5)
hbox1.Add(text1, proportion = 1, flag = wx.ALL, border = 5)
vbox.Add(hbox1, proportion = 0 , flag = wx.ALL | wx.EXPAND, border = 5)
hbox2 = wx.BoxSizer(wx.HORIZONTAL)
static2 = wx.StaticText(panel, label="Risultati della ricerca")
hbox2.Add(static2, proportion = 0, flag = wx.ALL | wx.ALIGN_BOTTOM, border = 5)
vbox.Add(hbox2, proportion = 0, flag = wx.ALL | wx.EXPAND, border = 5)
hbox3 = wx.BoxSizer(wx.HORIZONTAL)
text3 = wx.TextCtrl(panel, style=wx.TE_MULTILINE)
hbox3.Add(text3, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
vbox.Add(hbox3, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
hbox4 = wx.BoxSizer(wx.HORIZONTAL)
pulsanteOk = wx.Button(panel, label="OK")
pulsanteCancel = wx.Button(panel, label="CANCEL")
hbox4.Add(pulsanteOk, proportion = 0, flag = wx.ALL, border = 5)
hbox4.Add(pulsanteCancel, proportion = 0, flag = wx.ALL, border = 5)
vbox.Add(hbox4, proportion = 0, flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
panel.SetSizer(vbox)
self.Centre()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Tip
Capisco perfettamente che non vi sentiate ancora in grado di implementare layout così complicati! Scrivo però alcuni suggerimenti banali:
- scrivete sempre tutti i parametri nella funzione Add:
sizer.Add(widget, proportion = 0, flag = wx.ALL, border = 5)- partiamo da una situazione semplice: proporzione ZERO, e bordo 5 da tutti i lati.
- se qualcosa non quadra, fate una modifica per volta e guardate subito cosa succede!
StaticBoxSizer
Documentazione ufficiale wx.StaticBoxSizer
Contenitore per una riga (o una colonna) di widget con contorno evidenziato
Lo StaticBoxSizer è esattamente identico al BoxSizer se non per il fatto
che contiene al suo interno già una StaticBox per decorare ed
evidenziare il layout creato.
A livello operativo bisogna dunque fornire, in fase di definizione,
anche un genitore (per la StaticBox, solitamente un pannello) e il testo
della stessa.
# se vuoi una BoxSizer
box = wx.BoxSizer( wx.HORIZONTAL )
# se vuoi una StaticBoxSizer
sbox = wx.StaticBoxSizer( wx.HORIZONTAL, panel, "titolo" )
Negli esempi relativi al BoxSizer provate a cambiarne qualcuno con uno
StaticBoxSizer per apprezzare la differenza. E poi passate al prossimo
layout!
Esercizi *BoxSizer
Esercizio 2201 (line)

Esercizio 2202 (line)

GridSizer
Documentazione ufficiale wx.GridSizer
Contenitore a tabella per le widget
La classe wx.GridSizer può essere utilizzata per creare un layout a griglia uniforme, ovvero con lo stesso spazio (più o meno)
per tutte le caselle della griglia. Quando si definisce, è possibile anche specificare un margine orizzontale e verticale
fra gli elementi della griglia.
wx.GridSizer(rows = 1, cols = 0, vgap = 0, hgap = 0)
# esempio di layout a griglia con 4 righe e 3 colonne e 5 pixel di margine orizzontale e verticale
grid = wx.GridSizer( rows = 4 , cols = 3, vgap = 5 , hgap = 5 )
Note
Applicazioni della OOP
Ok, adesso proviamo a guadagnare punti a favore della OOP. Come si fa ad aggiungere widgets ad un
GridSizer? Esattamente con la stessa identica funzione dellaBoxSizer. Perchè? Beh… perché derivano entrambe dalla stessa classe (la classewx.Sizer, che non studieremo) ed ereditano entrambe la funzione Add.L’unica cosa da sapere, oltre alla OOP, è il fatto che le widget vengono aggiunte alla
GridSizertramite la funzioneAddin fila, a partire da in alto a sinistra, poi si procede in riga e terminata la riga in alto si continua sotto.
Alla luce delle nuove conoscenze acquisite, facciamo subito una prova semplice semplice:
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="GridSizer")
panel = wx.Panel(self)
grid = wx.GridSizer(rows = 2, cols = 2, vgap = 10, hgap = 10)
label = wx.StaticText(panel, label="Sono qui")
pulsante = wx.Button(panel, label="pulsante")
checkbox = wx.CheckBox(panel, label="Non toccarmi")
radio = wx.RadioButton(panel, label="Ho capito")
grid.Add(label, proportion = 0, flag=wx.ALIGN_TOP, border = 0)
grid.Add(pulsante, proportion = 0, flag=wx.ALIGN_CENTER, border = 0)
grid.Add(checkbox, proportion = 0, flag=wx.ALIGN_LEFT, border = 0)
grid.Add(radio, proportion = 0, flag=wx.ALIGN_BOTTOM, border = 0)
panel.SetSizer(grid)
self.Centre()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Risultato:

Warning
Anche qui, prima di andare avanti, copia e incolla il primo esempio e fai i seguenti tentativi:
- metti
proportion = 1sui 4 oggetti (nella GridSizer il concetto della proporzione non funziona! Non cambia nulla!)- sostituisci il flag del pulsante con
wx.EXPAND- aggiungi un bordo a tutti gli oggetti
- cambia gli allineamenti per tutti gli oggetti!
Come già sperimentato precedentemente, passiamo a visionare il codice e il risultato relativo ad un esempio un pochino più complicato, in cui il GridSizer viene inserito dentro un layout verticale, fino a formare una pseudo calcolatrice.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="GridSizer")
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
self.line = wx.TextCtrl(panel)
hbox1.Add(self.line, proportion = 1, flag = wx.ALL, border = 5)
vbox.Add(hbox1, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
grid = wx.GridSizer(rows = 4, cols = 4, vgap = 5, hgap = 5)
self.labels = "789/456*123-.0=+"
for lab in self.labels:
btn = wx.Button(panel, label=lab)
grid.Add(btn, proportion = 0, flag = wx.ALL | wx.EXPAND, border = 5)
vbox.Add(grid, proportion = 0, flag = wx.ALL | wx.EXPAND, border = 5)
panel.SetSizer(vbox)
self.Centre()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Copiate il codice, eseguite, dovreste vedere una window tipo questa.

Adesso però tornate su e ricontrollate il codice che avete copiato cercando di comprendere l’organizzazione del layout.
FlexGridSizer
Documentazione ufficiale wx.FlexGridSizer
Contenitore a tabella per le widget con dimensioni variabili
La classe wx.FlexGridSizer può essere utilizzata per creare un layout a
griglia flessibile, ovvero con righe o colonne di dimensione diversa.
Questo può ritornare utile soprattutto in alcuni casi specifici, che
vedremo fra un attimo.
La definizione di un FlexGridSizer è identica a quella di un GridSizer:
wx.FlexGridSizer(rows = 1, cols = 0, vgap = 0, hgap = 0)
# esempio di layout con FlexGridSizer con 4 righe e 3 colonne e 5 pixel di margine orizzontale e verticale
grid = wx.FlexGridSizer( rows = 4 , cols = 3, vgap = 5 , hgap = 5 )
La differenza fondamentale fra un GridSizer e un FlexGridSizer è che il
GridSizer subisce un espansione in quei campi in cui le widget vengono
inserite con flag wx.EXPAND, mentre il FlexGridSizer all’inizio non è
per niente allungabile: inutile mettere wx.EXPAND!
Per permettere ad un FlexGridSizer di espandere una riga o una colonna
bisogna indicarlo esplicitamente con una delle seguenti funzioni:
# Permette di allungare una riga
AddGrowableRow(rowNumber)
# Permette di allungare una colonne
AddGrowableCol(colNumber)
Nell’esempio che segue viene utilizzato un FlexGridSizer per permettere
di allungare la seconda colonna e la terza riga (ricordate che si inizia
a contare da ZERO!!!)

import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="FlexGridSizer")
panel = wx.Panel(self)
flex = wx.FlexGridSizer(rows=4, cols=2, vgap=5, hgap=5)
mailText = wx.StaticText(panel, label="Mail")
flex.Add(mailText, proportion=0, flag=wx.ALL, border=5)
mailTextControl = wx.TextCtrl(panel)
flex.Add(mailTextControl, proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
titolo = wx.StaticText(panel, label="Titolo")
flex.Add(titolo, proportion=0, flag=wx.ALL, border=5)
titoloTextControl = wx.TextCtrl(panel)
flex.Add(titoloTextControl, proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
testo = wx.StaticText(panel, label="Testo")
flex.Add(testo, proportion=0, flag=wx.ALL, border=5)
testoTextControl = wx.TextCtrl(panel, style=wx.TE_MULTILINE)
flex.Add(testoTextControl, proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
vuota = wx.StaticText(panel, label="")
flex.Add(vuota,proportion=0, flag=wx.ALL, border=5)
pulsante = wx.Button(panel, label="INVIA")
flex.Add(pulsante,proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
flex.AddGrowableRow(2)
flex.AddGrowableCol(1)
self.SetMinSize( (200,300) )
panel.SetSizer(flex)
self.Centre()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
GridBagSizer
Documentazione ufficiale wx.GridBagSizer
Contenitore a tabella con spanning
La classe wx.GridBagSizer implementa il contenitore più flessibile in
wxPython e un concetto analogo risulta presente in molti altri toolkit
grafici: in questo Sizer infatti, le widget possono occupare qualunque
posizione e comprendere anche più di una riga o una colonna.
Si definisce senza precisare il numero di righe e colonne, ma solo lo spazio fra queste:
wx.GridBagSizer( vgap , hgap )
In questo Sizer è stato reimplementato il metodo Add, l’unico utilizzabile per inserirvi elementi
bag = wx.GridBagSizer( vgap=5, hgap=5 )
bag.Add ( widget , pos = (row,column) , span = wx.DefaultSpan, flag = 0, border = 0)
Vediamo i parametri:
- widget è la widget da inserire
- pos è la posizione della widget nella griglia. Ricordate che si inizia a contare da ZERO.
- span indica di quanto la widget si deve allargare. Di default
occupa solo la sua casella. Prende una tupla 2D. Ad esempio
inserendo
span = (2,3)dite che la widget deve allargarsi di 2 righe e 3 colonne. - flag e border sono gli stessi del solito.
Di default le caselle della griglia mantengono la loro proporzione se la
finestra viene ridimensionata. Se volete invece fare in modo che una
riga o una colonna si comporti come nel FlexGridSizer, usate i seguenti
metodi:
bag = wx.GridBagSizer( vgap=5, hgap=5 )
# ...
# numero di riga
bag.AddGrowableRow (row)
# ...
# numero di colonna
bag.AddGrowableCol (col)
Per concludere arriviamo finalmente all’ultimo esempio con un layout
GridBagSizer. Stavolta parto dall’immagine del layout da disegnare:

L’idea con cui realizzarlo è abbastanza semplice: provate a dividere con qualche linea immaginaria il layout in questione e a formulare un piano d’azione. In questo primo esempio ho provato io a darvi un’idea di come potete organizzare.

Concludo con qualche appunto prima di farvi vedere il codice (che a questo punto dovreste già immaginare):
- gli oggetti che non hanno span o che non capitano in righe o colonne growable non hanno bisogno di
wx.EXPAND - gli oggetti che hanno span o che capitano in righe o colonne growable, per essere loro stessi espandibili, hanno bisogno di
wx.EXPAND - la riga e la colonna growable (almeno una per dimensione) rendono il layout fluido: per verificarlo, provate a commentare quelle righe
Ecco il codice che implementa quest’ultimo esempio:
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="GridBagSizer")
panel = wx.Panel(self)
bag = wx.GridBagSizer(vgap=5, hgap=5)
text = wx.StaticText(panel, label="Inserisci nome: ")
bag.Add(text, pos=(0,0), flag=wx.ALL, border=5)
tc = wx.TextCtrl(panel)
bag.Add(tc, pos=(1,0), span=(1,4), flag=wx.ALL | wx.EXPAND, border=5)
buttonOk = wx.Button(panel, label="Ok")
buttonClose = wx.Button(panel, label="Close")
bag.Add(buttonOk, pos=(3,2), flag=wx.ALL, border=5)
bag.Add(buttonClose, pos=(3,3), flag=wx.ALL, border=5)
# commenta via una di queste per volta per vedere la differenza!!!
bag.AddGrowableCol(1)
bag.AddGrowableRow(2)
panel.SetSizer(bag)
self.Centre()
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Tip
Suggerimenti per la creazione del layout
Ok, state provando a creare un layout, partendo da una idea che avete e non state riuscendo… Ci sono due suggerimenti che posso darvi.
Il primo è quello di disegnare prima su carta il layout che volete ottenere, in modo da poter ragionare su quale Sizers possono realizzarlo più correttamente.
In questo modo dovreste riuscire ad avere l’idea giusta su come realizzare il layout. Poi scrivete il codice, lo testate… e non funziona ancora :(
Il secondo suggerimento è quello di utilizzare uno strumento di debug, fornito direttamente da wxPython: The Widget Inspection Tool (WIT). Ne parlo un pochino in questa pagina
Buona lettura!
Esercizi *GridSizer
Esercizio 2251 (grid)

Esercizio 2252 (grid)

Esercizio 2253 (grid)

Layout Sizing
Per concludere il discorso del layout non posso non dedicare qualche riga al (ri)dimensionamento delle finestre. Niente di complicato, per carità… il ridimensionamento iniziale di una widget dovrebbe essere gestito automaticamente (cioè… senza fare nulla) se il layout viene implementato correttamente. Anzi… quando la dimensione iniziale non ci soddisfa, il più delle volte significa che abbiamo sbagliato qualche parametro nel layout!
Ci sono però alcune scelte (poche… tranquilli) che possiamo indurre tramite codice e che possono migliorare il comportamento della nostra finestra. Vediamole una per una!
- wx.Window.SetMinSize && wx.Window.SetMaxSize
-
Imposta la dimensione minima/massima della finestra.
Esempio banale:
# ...
self.SetMinSize( (400,250) )
panel.SetSizer(vbox)
# ...
wx.Sizer.Fit(self, window) && wx.Window.SetSizerAndFit(wx.Sizer)
La funzione Fit, disponibile in tutti i sizer, dice al sizer di
ridimensionare la finestra per aderire alla dimensione minima del
sizer. Va ovviamente chiamata dopo aver impostato un sizer in una
finestra con SetSizer.
È possibile ovviare alla chiamata delle due funzioni consecutive
inserendo il sizer nella finestra con SetSizerAndFit.
Esempi banali:
# ...
# utilizza prima SetSizer e dopo Fit...
panel.SetSizer(vbox)
vbox.Fit(self)
# ...
# oppure insieme, con SetSizerAndFit
panel.SetSizerAndFit(vbox)
# ...
Volendo approfondire il comportamento dei sizer nel ridimensionamento delle finestre vi consiglio di dare un occhio alle funzioni wx.Window.ClientToWindowSize e wx.Window.WindowToClientSize.
In generale, c’è una parte specifica della documentazione a proposito di questo problema: https://docs.wxpython.org/window_sizing_overview.html
Se invece pensate sia sufficiente quello che avete visto qui… passate agli esercizi del prossimo capitolo!!!
Esercizi strutturati
Negli esercizi che seguono dovete implementare layout e funzionalità: nessuno di questi è un esercizio banale, ma sono tutti più che fattibili con un minimo di organizzazione: pensate prima (magari su un foglio di carta) a come strutturare la vostra soluzione, poi dedicatevi esclusivamente a “disegnare” l’interfaccia, infine implementate le funzionalità richieste.
Buon lavoro!
Esercizio 2301 (Sommatore)

In questa applicazione bisogna implementare una widget per fare le somme. Sarà l’occasione per ragionare un pò sui layout e per curiosare un pochino nella documentazione degli oggetti utilizzati.
Come vedete, si parte con un layout verticale in cui sono inseriti una serie di layout orizzontali contenenti gli oggetti… Per quanto riguarda l’implementazione, vi voglio far notare che i dati nelle TextCtrl sono comunque stringhe, quindi quando li prendete dovete convertirli in int (oppure in float), poi potete sommarli e infine, per visualizzarli dovete ritrasformare il risultato in una stringa.
Esercizio 2302 (Calcolatrice)

Evoluzione del programma sommatore. Nella calcolatrice è presente anche un layout a griglia. La TextCtrl è impostata in modalità READONLY e ogni pulsante ha la sua ovvia funzione. Copiate il funzionamento da una calcolatrice tradizionale. La nostra dovrà comportarsi in maniera esattamente identica!!
Esercizio 2303 (Lista spesa)

La lista della spesa è poco più di un semplice esercizio sulla ListBox. Menzione particolare al layout, nel mio caso implementato con un’unica BagSizer. Attenzione alle righe e alle colonne da far allargare!
- pulsante INSERISCI: aggiunge la voce presente nella TextCtrl (se non vuota) e la pulisce.
- pulsante CANCELLA: elimina la voce selezionata della ListBox (oppure nulla, se nulla è selezionato)
- pulsante CHIUDI: chiude l’applicazione
Aggiungo 2 livelli di difficoltà (opzionali):
- il pulsante CANCELLA disabilitato all’inizio e abilitato solo se c’è una voce selezionata nella ListBox.
- Invece di CHIUDI, implementare SALVA E CHIUDI che prima di chiudere l’applicazione salva i dati nel file lista_della_spesa.txt sul Desktop dell’utente. I dati vanno salvati uno per riga.
Esercizio 2304 (Tic Tac Toe)

Il Tic Tac Toe non è nient’altro che il gioco del Tris! Implementate una griglia di 9 pulsanti con etichetta nulla. Cliccando su un pulsante con etichetta nulla appare la X o la O. Se scegliete un font importante sarà molto carino!
Per implementare la vittoria, disabilitate tutti i pulsanti e scrivete sul titolo “HAI VINTO” oppure “HAI PERSO”.
Potete implementarne due versioni (in due programmi diversi):
-
User vs User (più facile): in questa versione si gioca in due. Il primo che clicca mette una “X”, quello dopo una “O”. Ogni volta il programma valuta chi vince!
-
User vs CPU (più complicatino…): in questa versione l’utente inizia sempre a giocare; quando clicca su un pulsante appare la “X” e subito dopo il programma valuta se l’utente ha vinto e se no fa giocare la CPU, aggiungendo una “O” in una casella libera. Più implementate la CPU in maniera intelligente, più sarà divertente giocare :)
Esercizio 2305 (Fifteen puzzle)

Il Fifteen Puzzle o gioco del 15 si implementa con una semplice griglia in cui inserire 15 pulsanti. Per quanto riguarda le regole del gioco… faccio finta che tutti sappiate giocare e abbiate giocato almeno una volta…
L’implementazione di ognuno può essere diversa e comunque corretta, ma io voglio darvi un suggerimento: inserite non 15 ma 16 pulsanti nella griglia, riempiendola completamente! Il 16-esimo pulsante non ha etichetta e va nascosto. Ogni volta che si clicca sul pulsante vicino, quello in realtà non si sposta, ma lo si nasconde, si fa apparire il pulsante nascosto e si copia l’etichetta dal primo sul secondo.
Dovete implementare una funzione mischia che all’inizio prepara il gioco, far muovere i pulsanti al click e una funzione controllaSeHaiVinto che appunto ti dice se sei riuscito a riordinare il puzzle!
Dialogs
Le finestre di dialogo sono strumenti indispensabili della programmazione con GUI attuale. Esse permettono una comunicazione bidirezionale fra l’applicazione e l’utente con la prima che tramite esse mostra all’ultimo informazioni ritenute importanti o richiede scelte senza le quali non si può procedere oltre.
Tip
La caratteristica delle finestre che le porta ad essere considerate finestre di dialogo è quella di essere finestre modali, ovvero finestre che si sovrappongono alle finestre principali e non possono in alcun modo essere ignorate.
Questa condizione si ottiene utilizzando in una finestra la funzione ShowModal() al posto della classica funzione Show().
Esempi banali di finestre di dialogo sono la finestra di errore, la finestra di selezione file, quella per selezionare i font oppure i colori. Vediamole una per una, con un piccolo esempio per comprenderne al meglio il funzionamento.
Message Dialogs
Documentazione ufficiale wx.MessageDialog
Finesta di dialogo per un messaggio, un avvertimento o una domanda da porre all’utente.
Sevono per inviare un messaggio esclamativo, di avvertimento, di richiesta o di errore. Tramite la selezione di pulsanti sotto, si può ottenere un feedback dall’utente per assicurarsi abbia recepito il messaggio.
MessageDialog( parent , message , caption=TITOLO , style=STILE )
I pulsanti di una MessageDialog possono essere i seguenti:
| Valore | Descrizione | Valore di ritorno |
|---|---|---|
| wx.OK | Pulsante OK. Combinabile con CANCEL | wx.ID_OK |
| wx.CANCEL | Pulsante CANCEL. Combinabile con OK o YES_NO | wx.ID_CANCEL |
| wx.YES_NO | Pulsanti Sì e No. Combinabile con CANCEL | wx.ID_YES oppure wx.ID_NO |
| wx.HELP | Mostra il pulsante AIUTO | wx.ID_HELP |
Se non si preme nessun pulsante, ad esempio uscendo dalla finestra di
dialogo con ESC, oppure con l’icona di chiusura, essa ritorna il
valore wx.ID_NONE.
Lo stile può contenere uno dei seguenti valori. Essi sono sovrapponibili
ai pulsanti con la tecnica del pipe (il simbolo |), come per i flag
dei Sizer.
| Stile | Descrizione |
|---|---|
| wx.ICON_EXCLAMATION | Mostra un icona di allerta |
| wx.ICON_ERROR | Mostra una icona di errore |
| wx.ICON_HAND | Come wx.ICON_ERROR |
| wx.ICON_INFORMATION | Mostra una icona informativa |
| wx.ICON_QUESTION | Mostra una icona a un punto di domanda |
Vediamo qualche esempio semplice semplice:
# Messaggio informativo
dial = wx.MessageDialog(None, "Ora della merenda", "Info", wx.OK | wx.CANCEL)
if dial.ShowModal() == wx.ID_OK:
# fai merenda
# Messaggio di errore
dial = wx.MessageDialog(None, "Biscotti non disponibili", "Errore", wx.OK | wx.ICON_ERROR)
dial.ShowModal()
# Messaggio di domanda
dial = wx.MessageDialog(None, "Ti andrebbe un mandarino?", "Domanda", wx.YES_NO | wx.ICON_QUESTION)
if dial.ShowModal() == wx.ID_YES:
# mangia il mandarino
# Messaggio di avvertimento
dial = wx.MessageDialog(None, "Troppa nutella all'orizzonte", "Esclamazione", wx.OK | wx.ICON_EXCLAMATION)
dial.ShowModal()
Warning
Ovviamente va individuato il giusto momento per visualizzare una MessageDialog: abusare di finestre modali è considerato fastidioso e maleducato.
Esercizio 2401
Visualizzate una finestra con un pulsante che quando premuto visualizza un messaggio informativo a vostro piacere.
Esercizio 2402
Visualizzate una finestra vuota. Quando si clicca per chiuderla appare il messaggio di domanda “Sicuro di voler chiudere?”. Se l’utente risponde No, la finestra non si chiude.
Esercizio 2403
Visualizzare una finestra con una SpinCtrl con valori da -10 a +10. Se l’utente seleziona 0, appare un messaggio di avvertimento.
Dir Dialogs
Documentazione ufficiale wx.DirDialog
Finesta di dialogo per selezionare una cartella.
Servono per selezionare una cartella (presente o no) nel proprio computer.
# seleziona una dir e poi visualizza la scelta
dlg = wx.DirDialog(None, 'Seleziona la cartella delle immagini')
if dlg.ShowModal() != wx.ID_OK:
return
# la stringa che contiene il percorso della cartella selezionata
percorso = dlg.GetPath()
# ...
Esercizio 2411
Visualizzare una finestra con un pulsante ed una etichetta di testo inizialmente vuota. Cliccando il pulsante si apre la DirDialog che permette di selezionare la cartella. Se l’utente preme OK nella etichetta di testo si visualizzi il percorso selezionato.
Esercizio 2412
Visualizzare una finestra con un pulsante. Cliccando il pulsante si apre una DirDialog con la possibilità di selezionare cartelle non esistenti. Se l’utente ne seleziona una non esistente, il programma la crea (Sugg: ricordate il modulo Pathlib???)
File Dialogs
Documentazione ufficiale wx.FileDialog
Finesta di dialogo per la selezione file, in apertura o salvataggio.
La classe FileDialog crea una finestra di selezione file. Analogamente alle DirDialog, servono per selezionare un file (esistente o no) nel proprio computer.
dlg = wx.FileDialog(None, "Apri File", style=wx.FD_OPEN)
if dlg.ShowModal() == wx.ID_CANCEL:
return
# la stringa che contiene il percorso del file da aprire
percorso = dlg.GetPath()
dlg = wx.FileDialog(None, "Salva File", style=wx.FD_SAVE)
if dlg.ShowModal() == wx.ID_CANCEL:
return
# la stringa che contiene il percorso del file su cui fare salva
percorso = dlg.GetPath()
Esercizio 2421
Visualizzare una finestra con un pulsante ed una etichetta di testo inizialmente vuota. Cliccando il pulsante si apre la FileDialog che permette di selezionare un file per l’apertura. Se l’utente preme OK nella etichetta di testo si visualizzi il percorso del file selezionato.
Esercizio 2422
Visualizzare una finestra con un pulsante. Cliccando il pulsante si apre una FileDialog in modalità salva. Se l’utente ne seleziona uno non esistente, il programma lo crea vuoto (Sugg: ricordate il modulo Pathlib???)
Esercizio 2423
Visualizzare una finestra con un pulsante ed una etichetta di testo inizialmente vuota. Cliccando il pulsante si apre una FileDialog in modalità apri. Se l’utente seleziona un file di testo e preme OK, il contenuto del file viene visualizzato nell’etichetta.
Colour Dialogs
Documentazione ufficiale classi per la gestione dei colori:
wx.ColourDialog: Finesta di dialogo per selezionare un colore dalla tavolozza nativa del SO.
wx.ColourData: Informazioni sui colori in utilizzo su una applicazione.
Le finestre di dialogo per la selezione dei colori si utilizzano tramite
la loro classe ausiliaria wx.ColourData che mantiene le
informazioni iniziali necessarie per la selezione dei colori e (dopo
l’esecuzione della dialog) il colore selezionato dall’utente.
# ...
datiIniziali = wx.ColourData()
dialog = wx.ColourDialog(self, datiIniziali)
if dialog.ShowModal() != wx.ID_OK:
return
datiFinali = dialog.GetColourData()
coloreSelezionato = datiFinali.GetColour()
# ...
Esercizio 2431
Visualizzare una finestra con un pulsante ed una etichetta di testo inizialmente vuota. Cliccando il pulsante si apre una ColourDialog che permette di selezionare un colore. Visualizzarlo come stringa nella etichetta.
Esercizio 2432
Visualizzare una finestra con un pulsante ed una etichetta di testo inizialmente vuota. Cliccando il pulsante si apre una ColourDialog che permette di selezionare un colore. Colorare lo sfondo dell’etichetta del colore selezionato.
Esercizio 2433
Visualizzare una finestra con un pulsante ed una etichetta di testo con la scritta “Colore selezionato”. Cliccando il pulsante si apre una ColourDialog che permette di selezionare un colore. Colorare il testo dell’etichetta con il colore selezionato.
Font Dialogs
Documentazione ufficiale classi per i font:
wx.FontDialog: Finesta di dialogo per selezionare un font dalla finestra nativa del SO.
wx.FontData: Informazioni sui Font in utilizzo su una applicazione.
Le finestre di dialogo per la selezione dei font si utilizzano tramite
la loro classe ausiliaria wx.FontData che mantiene le
informazioni iniziali necessarie per la selezione dei font e (dopo
l’esecuzione della dialog) il font selezionato dall’utente. (Questa
frase mi sembra di averla già sentita…)
# ...
datiIniziali = wx.FontData()
dialog = wx.FontDialog(self, datiIniziali)
if dialog.ShowModal() != wx.ID_OK:
return
datiFinali = dialog.GetFontData()
fontSelezionato = datiFinali.GetChosenFont()
# ...
Esercizio 2441
Visualizzare una finestra con un pulsante ed una etichetta di testo inizialmente vuota. Cliccando il pulsante si apre una FontDialog che permette di selezionare un font. Visualizzarlo come stringa nella etichetta.
Esercizio 2442
Visualizzare una finestra con un pulsante ed una etichetta di testo con la scritta “Font selezionato”. Cliccando il pulsante si apre una FontDialog che permette di selezionare un font. Utilizzarlo come font dell’etichetta.
Tip
Se volete una presentazione generica di tutte le finestre di dialogo comuni presenti nella libreria wxPython ecco il link che fa per voi: https://docs.wxpython.org/common_dialogs_overview.html.
Immagini & Icone
wxPython gestisce le immagini a vari livelli a seconda di quello che ci vuoi fare:
-
Se vuoi visualizzare una o più immagini, magari con piccole funzionalità tipo la scala di grigi o il copia e incolla… il capitolo Immagini è per te!!!
-
Se vuoi visualizzare una icona da abbinare ad un pulsante, un menù o una toolbar… vai al capitolo Icone!!!
-
Se vuoi realizzare un software per l’elaborazione grafica delle immagini (tipo PhotoShop)… sei nel posto sbagliato!!!
Warning
Per l’elaborazione delle immagini, wxPython si appoggia sulla libreria
pillow(Python Image Library).Verifica che sia correttamente installata, ad esempio tramite l’interfaccia di gestione dei pacchetti di Thonny.
Immagini
Documentazione ufficiale classi per le immagini:
wx.Bitmap: Classe per la gestione delle immagini (OOP).
wx.Image: Classe per la manipolazione delle immagini (OOP).
wx.StaticBitmap: Classe grafica per la visualizzazione delle immagini.
wxPython gestisce l’interazione con le immagini tramite l’ausilio di 3 classi principali.
La prima classe si chiama wx.Bitmap e una sua istanza rappresenta il cosiddetto oggetto immagine. Questa classe si occupa
della relazione fra il file che contiene l’immagine, da qualche parte nel filesystem e un oggetto immagine della classe wx.
Se vogliamo manipolare questa immagine dobbiamo generare, a parte da una Bitmap, il corrispondente oggetto wx.Image.
Un oggetto di questa classe permette di manipolare un oggetto immagine (non il file… l’oggetto! La rappresentazione logica della stessa).
Possiamo modificare cose come la dimensione, il colore, renderla in scala di grigi, abilitarla o disabilitarla, modificare il livello di trasparenza, etc…
Fatto questo possiamo ricreare una Bitmap a partire da una Image modificata!
L’ultimo oggetto, l’unico con capacità grafiche, si chiama wx.StaticBitmap: è l’oggetto grafico da inserire in uno dei nostri layout e da abbinare alla Bitmap
da visualizzare. Facile!!!
Partiamo da qualche esempio ben commentato per prendere confidenza con il codice!
# ...
panel = wx.Panel(self)
self.widgetImageViewer = wx.StaticBitmap(panel)
oggettoImmagine = wx.Bitmap("foto.jpg")
self.widgetImageViewer.SetBitmap(oggettoImmagine)
# ...
# ...
panel = wx.Panel(self)
self.widgetImageViewer = wx.StaticBitmap(panel)
oggettoImmagine = wx.Bitmap("foto.jpg")
immagine = oggettoImmagine.ConvertToImage()
immagineBN = immagine.ConvertToGreyscale()
oggettoImmagineBN = wx.Bitmap(immagineBN)
self.widgetImageViewer.SetBitmap(oggettoImmagineBN)
# ...
La base ce l’avete… adesso occorre buttarsi un pò con qualche esercizio!!!
Tip
Se volete applicare un colore di sfondo alla vostra StaticBitmap, potete farlo con il seguente codice:
# wx.Bitmap.FromRGBA(width, height, R, G, B, opacity) # dim: 800x600, colore: rossa ( RGB = (255,0,0) ), opaca: sì (ultimo valore, sempre 255)! sfondoRosso = wx.Bitmap.FromRGBA(800,600,255,0,0,255) self.widgetImageViewer.SetBitmap(sfondoRosso) # ...
Esercizi
Esercizio 2501
Crea una semplice interfaccia con un pannello e un StaticBitmap per visualizzare una immagine presente nella stessa cartella ove salverete il codice dell’esercizio.
Esercizio 2502
Crea una applicazione con 2 pulsanti allineati sopra e una StaticBitmap sotto. Con il primo pulsante si apre una finestra di selezione file per selezionare una immagine che andrà mostrata nella StaticBitmap. Il secondo pulsante è un ToggleButton che abilita/disabilita l’immagine selezionata.
Icone
Documentazione ufficiale wx.ArtProvider
Classe per la gestione dei file multimediali predefiniti del SO (immagini, icone, video, suoni, etc..)
Se la necessità relativa alle immagini è quella di visualizzare le icone
nei pulsanti (e nei menù e nelle barre degli strumenti, come impareremo
fra breve) oltre a caricare le immagini direttamente tramite le Bitmap,
come abbiamo appena visto, c’è un’altra interessante soluzione: la
classe wx.ArtProvider.
Lungi da me tediarvi sull’idea dietro la realizzazione di una tale classe e come essa possa essere derivata per creare il proprio personale set di icone… sappiate semplicemente quello che serve: per ogni icona abbinata ad una azione comune abbiamo un ID e tramite quello possiamo creare automaticamente una Bitmap.
# la variabile "bitmap" è un oggetto della classe wx.Bitmap
bitmap = wx.ArtProvider.GetBitmap( id = wx.UN_ID_FRA_QUELLI_ELENCATI_SOTTO , client=wx.UN_VALORE_PER_ILPOSIZIONAMENTO, size = wx.DefaultSize )
Vediamo l’elenco delle icone automaticamente supportate (in rigoroso ordine sparso):
| Elenco | Art Provider ID | Disponibili |
|---|---|---|
| wx.ART_LIST_VIEW | wx.ART_NEW | wx.ART_TICK_MARK |
| wx.ART_REPORT_VIEW | wx.ART_NEW_DIR | wx.ART_CROSS_MARK |
| wx.ART_FIND | wx.ART_MINUS | wx.ART_FOLDER |
| wx.ART_FIND_AND_REPLACE | wx.ART_PLUS | wx.ART_FOLDER_OPEN |
| wx.ART_CDROM | wx.ART_EXECUTABLE_FILE | wx.ART_FILE_OPEN |
| wx.ART_FLOPPY | wx.ART_MISSING_IMAGE | wx.ART_FILE_SAVE |
| wx.ART_HARDDISK | wx.ART_NORMAL_FILE | wx.ART_FILE_SAVE_AS |
| wx.ART_HELP | wx.ART_ERROR | wx.ART_UNDO |
| wx.ART_HELP_BOOK | wx.ART_QUESTION | wx.ART_REDO |
| wx.ART_HELP_FOLDER | wx.ART_WARNING | wx.ART_CUT |
| wx.ART_HELP_PAGE | wx.ART_INFORMATION | wx.ART_COPY |
| wx.ART_HELP_SETTINGS | wx.ART_TIP | wx.ART_PASTE |
| wx.ART_HELP_SIDE_PANEL | ||
| wx.ART_GOTO_FIRST | wx.ART_PRINT | |
| wx.ART_GOTO_LAST | ||
| wx.ART_GO_TO_PARENT | wx.ART_ADD_BOOKMARK | |
| wx.ART_GO_UP | wx.ART_DEL_BOOKMARK | |
| wx.ART_GO_DOWN | ||
| wx.ART_GO_BACK | wx.ART_CLOSE | |
| wx.ART_GO_FORWARD | wx.ART_QUIT | |
| wx.ART_GO_HOME | ||
| wx.ART_GO_DIR_UP |
E’ inoltre possibile aggiungere una descrizione della posizione che l’icona dovrà andare ad occupare. Questa scelta determinerà anche la dimensione iniziale.
Vediamo i valori possibili:
| Tipi ArtProvider ID | Descrizione |
|---|---|
| wx.ART_TOOLBAR | Icone per le toolbar (media dimensione) |
| wx.ART_MENU | Icone per i menu (piccole) |
| wx.ART_BUTTON | icone per i pulsanti (grandine) |
| wx.ART_FRAME_ICON | Icone per … Boh! |
| wx.ART_CMN_DIALOG | Icone per le finestre di dialogo (medie) |
| wx.ART_HELP_BROWSER | Icone per i messaggi di aiuto (grandine) |
| wx.ART_MESSAGE_BOX | Icone per le message box (medie) |
| wx.ART_OTHER | Altre icone (Default) |
Per finire, vediamo un pezzettino di codice in cui inseriamo un icona su un pulsante.
# ...
pulsanteIndietro = wx.Button(panel)
pulsanteIndietro.SetLabel("Precedente")
pulsanteIndietro.SetBitmap( wx.ArtProvider.GetBitmap( wx.ART_GO_BACK , wx.ART_MENU ) )
# ...
Per ottenere questo:

Spero sia tutto chiaro!!!
Chiedetemi pure… sarò lieto di farvi fare qualche esercizio di comprensione ☺️
Timer(s)
Documentazione ufficiale wx.Timer
Classe per gestire i timer (con cui implementare semplici temporizzazioni)
I Timer sono oggetti molto comuni in ogni libreria OOP. Per la scansione del tempo si appoggiano tipicamente al clock di sistema, che teoricamente scandisce il milionesimo di secondo, ma che per motivi tecnici che non sto a spiegarvi (a lezione me lo chiederete, lo so: il bello dell’inutile) è affidabile appena al millesimo di secondo. Per quello che serve a noi, basta e avanza…
Un Timer è un oggetto della classe wx.Timer che gestisce appunto una
scansione del tempo in millisecondi. Poiché ha già il suo bel daffare
con questa questione del tempo vive sospeso rispetto al Main Event
Loop e quindi non gestisce direttamente i suoi eventi, ma si appoggia
alla sua widget parent. Detto così sembra complicato… vediamo un
esempio commentato e capirete subito come funziona.
# dichiariamo un oggetto della classe wx.Timer
# con la nostra widget come parent
# Manteniamo un riferimento ad esso con una variabile membro (una del tipo self.var)
# Se nella vostra app avete 2 timer, inserite un ID
timerBellissimo = wx.Timer(self)
# facciamo il BIND tramite la parent widget: BIND(evento, funzione, oggetto)
self.Bind(wx.EVT_TIMER, self.funzioneDaEseguire, timerBellissimo)
Ecco qua! Come avete visto non è particolarmente complicato. Adesso il timer è pronto e collegato alla funzione da eseguire al timeout. Ma come si fa a far partire il timer? E a fermarlo? E…
Come vediamo fra un attimo, per attivare un timer, basta invocare la
funzione Start(millisecondi). Se ad esempio invochiamo Start(100),
ogni 100 millisecondi il timer genererà un evento wx.EVT_TIMER che nel
nostro esempio, scatenerà la funzione funzioneDaEseguire.
Se avete necessità di fermare questa ripetizione infinita, usate la
funzione Stop(). Ultima, ma non meno importante, se avete bisogno di
eseguire una sola volta la funzione dilazionata nel tempo, ad esempio
una volta dopo 3 secondi e poi basta, avviate il timer con la funzione StartOnce(3000).
# AVVIA il TIMER
# Il parametro è in millisecondi.
# Significa che questo timer genererà un evento EVT_TIMER ogni secondo finchè non verrà fermato
timerBellissimo.Start(1000)
# FERMA IL TIMER
timerBellissimo.Stop()
# AVVIA IL TIMER per un'unica esecuzione
# come Start() ma termina il timer al primo timeout
timerBellissimo.StartOnce(millisecondi)
Spero sia tutto chiaro :)
wx.Gauge
Documentazione ufficiale wx.Gauge
Classe grafica per implementare una barra di avanzamento (orizzontale o verticale)
La classe wx.Gauge rappresenta una widget che implementa una barra di avanzamento.

La barra di avanzamento va impostata indicando il range predefinito. In
questo esempio ho messo range=10 e questo significa che la barra si
caricherà come fosse divisa in 10 parti, 10 stati di avanzamento. Nell’esempio presentato
abbiamo un timer per far avanzare la barra di uno step automaticamente
ogni secondo e 2 pulsanti per fare OK e STOP.
Nell’esempio proposto quando si clicca OK la barra comincia a caricarsi e può essere fermata cliccando STOP. Quando la barra è carica il programma si chiude.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Quando la barra è carica, il programma si chiude")
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.conta = 0
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.barra = wx.Gauge(panel, range=10)
vbox.Add(self.barra, proportion=0, flag=wx.ALL|wx.EXPAND, border=10)
hbox = wx.BoxSizer(wx.HORIZONTAL)
pulsanteStart = wx.Button(panel, label="START")
pulsanteStart.Bind(wx.EVT_BUTTON, self.OnOk)
hbox.Add(pulsanteStart, proportion=1, flag=wx.ALL|wx.EXPAND, border=5)
pulsanteStop = wx.Button(panel, label="STOP")
pulsanteStop.Bind(wx.EVT_BUTTON, self.OnStop)
hbox.Add(pulsanteStop, proportion=1, flag=wx.ALL|wx.EXPAND, border=5)
vbox.Add(hbox, proportion=1, flag=wx.ALL|wx.EXPAND, border=5)
panel.SetSizer(vbox)
self.SetMinSize( (600,200) )
def OnTimer(self, event):
self.conta += 1
if self.conta > 10:
self.Close(True)
return
self.barra.SetValue(self.conta)
return
def OnOk(self, event):
self.timer.Start(1000)
return
def OnStop(self, event):
self.timer.Stop()
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Esercizi
Esercizio 2601 (orologio)
Implementare una semplice finestra con un’unica etichetta che visualizza un orologio funzionante (che scorre) con ore, minuti, secondi.
Esercizio 2602 (cronometro)
Implementare un cronometro con una etichetta con valore iniziale “00:00.00” rappresentanti rispettivamente minuti, secondi e centesimi di secondo e tre pulsanti per le funzioni START, STOP, RESET.
- START fa (ri)partire il cronometro
- STOP lo ferma.
- RESET lo (ri)imposta a ZERO
Esercizio 2603 (semaforo)
Applicazione con 4 immagini per 3 caselle: luce rossa, luce gialla, luce verde, luce grigia (luce spenta). Il semaforo inizia con la luce verde attiva e le altre due grigie. Ogni 5 secondi si passa allo stadio successivo (luce gialla, poi luce rossa, poi si ritorna a luce verde, all’infinito).
GUI complete
In questo capitolo cercheremo di introdurre tutti quegli elementi grafici che implementano una GUI moderna:
- le azioni
- la barra dei menù
- la barra degli strumenti
- la barra di stato
- il menù contestuale
- le impostazioni delle applicazioni
Skeleton Full GUI
Lo Skeleton è un file che contiene la struttura di base dalla quale partire per costruire qualcosa.
Lo skeleton qui sotto descrive chiaramente una struttura da cui partire per alloggiare in maniera chiara e ben strutturata tutti gli elementi di una app Full GUI, ovvero comprensiva di azioni, menù, barre degli strumenti, etc…
import wx
APP_NAME = "Il nome della vostra App"
class Finestra(wx.Frame):
def __init__(self):
super().__init__(None, title=APP_NAME)
# La MainView
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.mainview = wx.StaticText(panel, label = "cambiami con il tuo oggetto principale!!!")
vbox.Add(self.mainview, proportion=1, flag=wx.EXPAND)
panel.SetSizer(vbox)
# Spazio per le variabili membro della classe
# Chiamata alle funzioni che generano la UI
self.creaMenubar()
self.creaToolbar()
# la statusbar è semplice...
self.bar = self.CreateStatusBar()
# le ultime cose, ad esempio, i Bind, le impostazioni iniziali, etc...
self.Bind(wx.EVT_CLOSE, self.funzioneEsci)
# chiamata alla funzione di caricamento impostazioni
self.loadConfig()
return
# in questa funzione andremo a creare e popolare la menubar
def creaMenubar(self):
return
# in questa funzione andremo a creare e popolare la toolbar
def creaToolbar(self):
return
def loadConfig(self):
config = wx.FileConfig(APP_NAME)
# dimensione
w = int(config.Read("width", "800")) # 678 è la larghezza INIZIALE
h = int(config.Read("height", "600")) # 432 è la altezza INIZIALE
self.SetSize(w,h)
# posizione
x = int(config.Read("x", "-1"))
y = int(config.Read("y", "-1"))
if (x,y) == (-1,-1): # se la posizione è (-1,-1) lo metto al centro!
self.Centre()
else:
self.Move(x,y)
return
def saveConfig(self):
config = wx.FileConfig(APP_NAME)
# dimensione
(w,h) = self.GetSize()
config.Write("width", str(w))
config.Write("height", str(h))
# posizione
(x,y) = self.GetPosition()
config.Write("x", str(x))
config.Write("y", str(y))
return
def funzioneEsci(self,evt):
self.saveConfig()
self.Destroy()
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
app.SetAppName(APP_NAME)
window = Finestra()
window.Show()
app.MainLoop()
Ok… abbiamo una struttura di base come ottimo punto di partenza per creare una applicazione con tutti gli oggetti grafici di base!
Vediamo pezzo per pezzo come integrare cose in essa!
Azioni predefinite
In quanti modi diversi si può fare copia e incolla?
Con la combinazione CTRL + C… con l’icona copia sulla barra degli strumenti, con il menù del tasto destro, con la barra in alto… Mi vengono in mente almeno 4 metodi. Probabilmente ne esistono anche altri.
Adesso pensate un momento come foste veri programmatori:
Ma… per ognuna di queste modalità occorre reimplementare un oggetto grafico e una funzione evento???
Fortunatamente la risposta a quest’ultima domanda è no!
In questo contesto rientra un concetto tipico della programmazione grafica: il concetto di azione. E cosa è un’azione?
Una azione è un’astrazione di una funzionalità che il nostro programma vuole offrire all’utente.
Viene identificata univocamente tramite:
- un nome (ad esempio: COPIA)
- una descrizione (ad esempio, COPIA il CONTENUTO NEGLI APPUNTI)
- un’icona (l’icona COPIA)
- una scorciatoia (CTRL + C)
La libreria wxPython per assicurare uniformità nelle azioni più comuni ha pensato bene di identificarle tramite degli ID fissi:
ad esempio l’ID per l’azione SALVA si chiama wx.ID_SAVE.
Ecco l’elenco completo degli ID delle azioni comuni in wxPython:
| Elenco | ACTION ID | Disponibili | ||
|---|---|---|---|---|
| wx.ID_ABOUT | wx.ID_EDIT | wx.ID_JUMP_TO | wx.ID_PRINT | wx.ID_STRIKETHROUGH |
| wx.ID_ADD | wx.ID_EXECUTE | wx.ID_JUSTIFY_CENTER | wx.ID_PROPERTIES | |
| wx.ID_APPLY | wx.ID_EXIT | wx.ID_JUSTIFY_FILL | wx.ID_TOP | |
| wx.ID_JUSTIFY_LEFT | wx.ID_REDO | |||
| wx.ID_BACKWARD | wx.ID_FILE | wx.ID_JUSTIFY_RIGHT | wx.ID_REFRESH | wx.ID_UNDELETE |
| wx.ID_BOLD | wx.ID_FIND | wx.ID_REMOVE | wx.ID_UNDERLINE | |
| wx.ID_BOTTOM | wx.ID_FIRST | wx.ID_LAST | wx.ID_REPLACE | wx.ID_UNDO |
| wx.ID_FLOPPY | wx.ID_REVERT_TO_SAVED | wx.ID_UNINDENT | ||
| wx.ID_CANCEL | wx.ID_FORWARD | wx.ID_NETWORK | wx.ID_UP | |
| wx.ID_CDROM | wx.ID_NEW | wx.ID_SAVE | ||
| wx.ID_CLEAR | wx.ID_HARDDISK | wx.ID_NO | wx.ID_SAVEAS | wx.ID_YES |
| wx.ID_CLOSE | wx.ID_HELP | wx.ID_SELECTALL | ||
| wx.ID_CONVERT | wx.ID_HOME | wx.ID_OK | wx.ID_SELECT_COLOR | wx.ID_ZOOM_100 |
| wx.ID_COPY | wx.ID_OPEN | wx.ID_SELECT_FONT | wx.ID_ZOOM_FIT | |
| wx.ID_CUT | wx.ID_INDENT | wx.ID_SORT_ASCENDING | wx.ID_ZOOM_IN | |
| wx.ID_INDEX | wx.ID_PASTE | wx.ID_SORT_DESCENDING | wx.ID_ZOOM_OUT | |
| wx.ID_DELETE | wx.ID_INFO | wx.ID_PREFERENCES | wx.ID_SPELL_CHECK | |
| wx.ID_DOWN | wx.ID_ITALIC | wx.ID_PREVIEW | wx.ID_STOP |
A breve vedremo anche come utilizzarle in maniera più che semplice!!!
Menubar
Documentazione ufficiale classi MenuBar, Menu, MenuItem
wx.MenuBar: Classe per implementare la barra dei Menù.
wx.Menu: Classe per implementare un Menù.
wx.MenuItem: Classe per implementare un elemento di Menù
I menù sono oggetti grafici che tutti conosciamo e a cui tutti siamo abituati, non c’è bisogno di fare grandi introduzioni!
Per aggiungere una menubar:
- lavoriamo nella funzione
creaMenubardello skeleton. - prima creiamo un oggetto Menubar
- poi creiamo ogni menù che andremo ad aggiungervi (es: File, Modifica, Visualizza, etc…)
- aggiungiamo le azioni necessarie ai nostri menù
- aggiungiamo i menù creati alla menubar
- applichiamo la menubar alla nostra applicazione
Vediamo degli esempi di codice:
# creiamo un oggetto Menubar
mb = wx.MenuBar()
# crea un menù...
fileMenu = wx.Menu()
# POI aggiungi alcune azioni...
fileMenu.Append(wx.ID_NEW)
fileMenu.Append(wx.ID_OPEN)
# altro esempio: inserimento DIRETTO di azione predefinita con TESTO e DESCRIZIONE personalizzati
fileMenu.Append(wx.ID_SAVE, "Salva bene :)", "Salva il documento corrente")
# riga di separazione: serve solo come abbellimento
fileMenu.AppendSeparator()
# creazione di un menuItem da azione predefinita, inserimento icona, aggiunta al menù
closeItem = wx.MenuItem(fileMenu, wx.ID_CLOSE)
closeItem.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_CLOSE))
fileMenu.Append(closeItem)
# altro menù...
toolsMenu = wx.Menu()
# creazione di una azione personalizzata con ID=35
customItem = wx.MenuItem(toolsMenu, 35, "Fai qualcosa")
toolsMenu.Append(customItem)
# CHECK ITEM
fullScreenItem = wx.MenuItem(toolsMenu, id=100, text="FullScreen", kind=wx.ITEM_CHECK)
toolsMenu.Append(fullScreenItem)
# PENULTIMA COSA: aggiungi i menù alla menubar
mb.Append(fileMenu, '&File') # (La & prima della F di File attiva la scorciatoia ALT + F)
mb.Append(toolsMenu, '&Tools')
# INFINE!!!
self.SetMenuBar(mb)
Se avete sistemato tutto al posto giusto, dovreste ottenere qualcosa di simile a questo:

Per collegare le azioni create ad una funzione (Binding) va intercettato
l’evento wx.EVT_MENU abbinato all’ID della voce di menù in questione:
# per fare Bind dell'azione con ID = wx.ID_EXIT ad una funzione chiamata funzioneEsci
self.Bind(wx.EVT_MENU, self.funzioneEsci, id=wx.ID_EXIT)
# per fare Bind dell'azione con ID = 35 ad una funzione chiamata faiQualcosa
self.Bind(wx.EVT_MENU, self.faiQualcosa, id=35)
Toolbar
Documentazione ufficiale wx.ToolBar
Classe grafica per implementare una barra degli strumenti
Se le barre dei menù dovrebbero permettere l’accesso a tutte le funzioni disponibili in una applicazione, le barre degli strumenti dovrebbero permettere l’accesso alle azioni veloci, ovvero a quelle di maggior utilizzo per gli utenti.
Per aggiungere una Toolbar alla nostra Frame Widget dobbiamo utilizzare
la funzione CreateToolBar a cui poi potremo aggiungere
le azioni che ci interessano.
toolbar = self.CreateToolBar()
# toolbar.AddTool(...e qui si aggiungono le azioni una ad una...)
toolbar.AddTool(wx.ID_OPEN, "Apri", wx.ArtProvider.GetBitmap(wx.ART_FOLDER_OPEN))
toolbar.AddTool(wx.ID_SAVE, "Salva", wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE))
# un separatore: come abbellimento e per vedere come si fa :)
toolbar.AddSeparator()
toolbar.AddTool(wx.ID_EXIT, "Esci", wx.ArtProvider.GetBitmap(wx.ART_QUIT) )
# riempita la toobar, va eseguito il metodo Realize()
toolbar.Realize()
A questo punto, se volete collegare i pulsanti della Toolbar ad una funzione,
basta eseguire il solito Bind() con l’evento wx.EVT_TOOL.
self.Bind(wx.EVT_TOOL, self.funzioneEsci, id = wx.ID_EXIT )
self.Bind(wx.EVT_TOOL, self.funzioneApri, id = wx.ID_OPEN )
Warning
L’evento
wx.EVT_TOOLè automaticamente abbinato ad un eventowx.EVT_MENUe viceversa. Questo significa che se, ad esempio, avete già implementato tutte le vostre azioni nella MenuBar e fatto il Binding con i loro ID con le opportune funzioni, tutte le azioni che aggiungerete alla toolbar con ID già utilizzati funzioneranno automaticamente senza bisogno di un ulteriore binding!!!
Vediamo una semplicissima dimostrazione di ciò con una finestra avente una sola azione, presente sia nella menubar che nella toolbar.
# Esempio banale con una sola azione su menu e toolbar, UN UNICO BINDING
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Stessa azione: menu & toolbar")
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fileItem = fileMenu.Append(wx.ID_EXIT)
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.esci, id=wx.ID_EXIT)
toolbar = self.CreateToolBar()
toolbar.AddTool( wx.ID_EXIT, "ESCI", wx.ArtProvider.GetBitmap(wx.ART_QUIT) )
toolbar.Realize()
def esci(self, event):
self.Close(True)
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
wx.StatusBar
Documentazione ufficiale wx.StatusBar
Classe grafica per implementare una barra di stato
La classe wx.StatusBar rappresenta una widget che implementa la barra di
stato delle applicazioni.

E’ possibile creare una barra di stato in due modi: o dichiarando un oggetto di tipo wx.StatusBar e poi inserendolo nella finestra tramite il metodo SetStatusBar() oppure chiamando direttamente dalla finestra il metodo CreateStatusBar(). Se dovete solo visualizzare informazioni il secondo metodo è una bomba! Se dovete modificare la StatusBar aggiungendovi widget e icone serve il primo metodo, eventualmente creando una classe derivata da wx.StatusBar.
Nell’esempio proposto si crea automaticamente una StatusBar e si visualizza la posizione del puntatore non appena questo entra nella finestra.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Muovi il mouse sopra la finestra")
self.bar = self.CreateStatusBar()
self.Bind(wx.EVT_MOTION, self.controllaMouse)
def controllaMouse(self, event):
pos = event.GetPosition()
info = "x: " + str(pos[0]) + " y: " + str(pos[1])
self.bar.SetStatusText(info)
return
# ----------------------------------------
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Context Menu
I Context Menus ovvero i menù contestuali sono quei menù che appaiono quando si fa click con il tasto destro in determinate posizione della nostra applicazione. Risulta chiaro a mio avviso che i Context Menu dipendono dalla widget sopra la quale si fa click con il tasto destro. Un immagine vale più di 1000 parole:

Per capire come si può implementare un Context Menu tramite la libreria wxPython proviamo ad implementarne uno su una widget con dentro un check item che abilita o disabilita la toolbar, rendendo possibile in quest’ultimo caso utilizzare i suoi strumenti.
import wx
class Esempio(wx.Frame):
def __init__(self):
super().__init__(None, title="Context Menu")
self.toolbar = self.CreateToolBar()
exitTool = self.toolbar.AddTool( wx.ID_EXIT, "ESCI", wx.ArtProvider.GetBitmap(wx.ART_QUIT) )
self.toolbar.Realize()
self.Bind(wx.EVT_TOOL, self.esci, exitTool)
# serve ad attivare il Context Menu
self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
# Crea il menù, pronto per essere attivato!
self.contextMenu = wx.Menu()
self.fsItem = wx.MenuItem(self.contextMenu, id=100, text="Toolbar abilitata", kind=wx.ITEM_CHECK)
self.contextMenu.Append(self.fsItem)
self.contextMenu.Check(100, True)
self.Bind(wx.EVT_MENU, self.ManageToolBar, self.fsItem)
def esci(self, event):
self.Close(True)
return
def OnRightDown(self, event):
self.PopupMenu(self.contextMenu, event.GetPosition())
def ManageToolBar(self,event):
if self.fsItem.IsChecked():
self.toolbar.Enable(True)
else:
self.toolbar.Enable(False)
# ----------------------------------------
app = wx.App()
window = Esempio()
window.Show()
app.MainLoop()
Il risultato di questo codice è il seguente:

Spero che osservare e riprodurre l’esempio sia sufficiente per capire il funzionamento :)
Impostazioni
Documentazione ufficiale wx.FileConfig
Classe per la gestione delle configurazioni utente di una applicazione
Può essere interessante e/o addirittura necessario per una applicazione salvare da qualche parte alcune impostazioni da ricaricare al prossimo riavvio. Ad esempio potrebbe essere interessante tracciare la posizione e la dimensione della finestra principale nello schermo, per riproporla identica… oppure nel caso di una applicazione che permette ad esempio di cambiare la dimensione del font, di memorizzare questa impostazione e riproporla anche nelle successive esecuzioni, senza dover ogni volta costringere l’utente a cambiarla di nuovo.
Certo siete già in grado di fare questo tipo di lavoro salvando ad esempio su file di testo queste informazioni… ma dove lo mettiamo questo file per essere ragionevolmente sicuri di ritrovarlo alla prossima esecuzione? A tutte queste domande risponde con semplicità disarmante la classe wx.FileConfig che deriva dalla classe astratta wx.ConfigBase (uhm… forse non sapete cosa significa astratta… pazienza!!!).
La classe wx.FileConfig richiede come informazioni solo il nome dell’applicazione, poi per ogni sistema operativo sceglie il posto riservato alle impostazioni delle applicazioni e scrive lì dentro un file di testo con una sequenza di coppie VARIABILE = VALORE.
Quindi riassumendo:
- STEP 1
-
Dare un nome alla propria App. Questo si fa nella classe App, prima di creare una finestra, con la funzione SetAppName. Ad esempio:
app = wx.App() app.SetAppName("Ciccio") window = Esempio() window.Show() app.MainLoop() - STEP 2
-
Dove ti serve di salvare alcune impostazioni, crea un oggetto della classe wx.FileConfig e salva i tuoi dati con la funzione
Write("variabile", valore). Nell’esempio sotto proviamo a salvare la variabile “colore” che contiene il valore “red” e la variabile “dimensioneFont” che contiene il valore 18. Attenzione ad assicurarsi di salvare sempre e solo stringhe:config = wx.FileConfig("Ciccio") config.Write( "colore" , "red" ) # l'int va convertito in stringa config.Write( "dimensioneFont" , str(18) ) # basta... tutto qui! - STEP 3
-
Quando ti serve di sapere i valori abbinati alle variabili “colore” e “dimensioneFont”, che evidentemente per il tuo programma sono importanti, ti basterà andare a controllarne il valore con le funzioni Read (che restituisce sempre stringhe, quindi converti i valori se ne hai bisogno).
config = wx.FileConfig("Ciccio") # "black" è il valore di default, viene usato se non è stato salvato nulla colore = config.Read( "colore" , "black" ) # trasformo in int. 12 default. dimensioneFont = int( config.Read( "dimensioneFont" , "12" ) )
Come vedete salvare le configurazioni della propria App diventa semplicissimo, a patto di saper scegliere bene i valori da salvare come impostazioni e di fare in modo che i dati da salvare siano semplicemente convertibili in stringhe.
Widgets avanzate
In questo capitolo andiamo ad introdurre alcune widgets molto potenti per realizzare programmi specializzati in determinati ambiti!
Iniziamo a gustare la potenza della OOP e delle librerie integrate!!!
Griglia per foglio di calcolo
Modulo wx.grid
Esercizio: Foglio Elettronico
blah blah
Esercizio: Battleship
blah blah
Editor di documenti stiloso
Modulo wx.stc
Esercizio: Blocco Note con wx.stc
blah blah
Browser per la navigazione internet
Modulo wx.html2
import wx
import wx.html2
APP_NAME = "Mini Browser"
class Finestra(wx.Frame):
def __init__(self):
super().__init__(None, title=APP_NAME)
panel = wx.Panel(self)
mainLayout = wx.BoxSizer(wx.VERTICAL)
# La WEBVIEW
self.browser = wx.html2.WebView.New(panel)
self.browser.Bind(wx.html2.EVT_WEBVIEW_LOADED, self.hoCaricato)
mainLayout.Add(self.browser, proportion=1, flag=wx.ALL|wx.EXPAND, border=5)
# la pagina iniziale
self.browser.LoadURL("https://www.adjam.org/")
panel.SetSizer(mainLayout)
self.SetSize(1200,800)
self.Centre()
def hoCaricato(self, evt):
self.SetTitle(APP_NAME + " - " + self.browser.GetCurrentTitle())
return
# ----------------------------------------
if __name__ == "__main__":
app = wx.App()
app.SetAppName(APP_NAME)
window = Finestra()
window.Show()
app.MainLoop()
Esercizio: Browser completo, con cronologia e bookmarks
blah blah
Esercizio: PDFViewer
blah blah
Media Player
Modulo wx.media
Esercizio: Video Player
blah blah
Esercizio: Audio Player
blah blah
Rich Text Ctrl
Modulo wx.richtext
Esercizio: Word :)
blah blah
DA SISTEMARE
-
wx.ListCtrl
- File manager ??
-
DateTime Widgets
- calendario
- orologio
- agenda
-
Visualizzatore Immagini
- GIF Viewer (con play/pause/stop)
- SlideShow (temporizzato)
- SlideShow (con pulsanti AVANTI/INDIETRO)
Printing support
esempio già preparato
Programmazione di rete
Esempi di software con supporto per la rete:
- Fortune
- Downloader
- Chat
- tris on line
- battaglia navale on line
wx tools
La libreria wxPython fornisce alcuni semplici strumenti di supporto alla programmazione. Nell’ordine andremo a vedere i seguenti:
- wxdocs: un semplice eseguibile in grado di scaricare una copia locale della documentazione wxPython, disponibile online qui.
- wxdemo: una applicazione che fornisce una lunga serie di esempi di codice per prendere spunto e/o approfondire le proprie conoscenze
- widget inspection tool: un debugger grafico in grado di darci una piccola mano con i problemi di layout e malfunzionamento grafici.
Vediamo adesso uno per uno!
wxdocs
La libreria wxPython fornisce alcuni semplici strumenti di supporto alla programmazione. Il primo che andiamo ad incontrare è il tool denominato wxdocs: esso permette di scaricare sul proprio dispositivo una copia della documentazione ufficiale, per la consultazione off-line.
Se avete già installato wxPython potete scaricare una copia locale della documentazione intera senza faticare tramite lo strumento chiamato wxdocs. Aprite un terminale ed eseguitelo:
$ wxdocs
Questo aprirà sul vostro browser di default la copia locale della documentazione, scaricandola prima se non la trova disponibile.

Così potete consultarla comodamente offline in qualsiasi momento. Analoga soluzione sarà utilizzata durante le verifiche di laboratorio!
wxdemo
La libreria wxPython fornisce una serie incredibile di esempi di codice ben documentato per imparare dagli esempi come si implementano determinate soluzioni: due semplici strumenti per reperire localmente la documentazione e gli esempi principali della libreria stessa. Utilizzandoli avrete la comodità di avere tutta la documentazione disponibile offline, oltre che la possibilità di accedere ad una miriade di esempi di codice già scritti e pronti all’uso e alla consultazione.
In maniera analoga, se volete vedere esempi di codice e vedere il risultato di quello che riescono a produrre potete scaricare tutti quelli disponibili nella documentazione tramite lo strumento wxdemo. Aprite un terminale ed eseguitelo:
$ wxdemo
Questo aprirà una applicazione wxPython (come vedete nella figura sotto) con tutti gli esempi catalogati per gruppi di interesse, scaricandoli da Internet se essi non sono presenti nel vostro sistema.

Cliccate su uno dei gruppi che vi interessano, date un occhio al codice e assolutamente eseguitelo per vederne il risultato!
widget inspection tool
La libreria wxPython fornisce uno strumento molto interessante per l’analisi delle widget in fase di creazione: lo Widget Inspection Tool. Questo strumento può essere utilizzato per analizzare il layout definito in una widget e le funzionalità grafiche implementate in essa (eventi, main loop, etc…).
Per utilizzarlo è necessario aggiungere semplicemente 2 righe di codice al main di una applicazione e rieseguire il suo codice:
import wx.lib.inspection
wx.lib.inspection.InspectionTool().Show()
Come vedete non è nulla di complicato. Cerchiamo però di capire quale può essere l’utilità di utilizzare uno strumento del genere, come al solito partendo da un esempio.
Questo stupido esempio qui sotto non funziona bene. Perché? Il Widget Inspection Tool potrebbe aiutarci a capirlo…
import wx
class Prova(wx.Frame):
def __init__(self, title):
super().__init__(None,title=title)
panel = wx.Panel(self)
testo = wx.StaticText(panel,label="Scrivi:")
self.riga = wx.TextCtrl(panel)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(testo, flag=wx.ALL, border=10)
hbox.Add(self.riga, proportion=1)
box = wx.BoxSizer(wx.VERTICAL)
box.Add(hbox)
panel.SetSizer(box)
app = wx.App()
w = Prova("prova WIT")
w.Show()
app.MainLoop()
Se analizzate un pochino l’esempio sopra, vi accorgerete che ho inserito un testo statico e una linea di testo in un sizer orizzontale. Ho messo un pò di bordo nel primo per allinearlo al meglio, ho messo proportion=1 al secondo per farlo ingrandire con la finestra… insomma tutto ok. Poi ho inserito il layout in un sizer verticale che ho applicato al pannello. In questo modo potrò semplicemente aggiungere righe di oggetti alla mia applicazione.
Il risultato che ottengo è visualizzato qui sotto:

Come si vede non ho ottenuto un layout bello esteso ma piccolo e non ridimensionabile. L’esempio è semplice e i più intuituivi avranno già capito il problema. Per tutti gli altri si può utilizzare uno strumento in più: il Widget Inspection Tool.
Come detto sopra, per utilizzarlo basta modificare il codice sopra aggiungendo le due righe indicate prima di far partire il mainloop. Io ho fatto così:
# ... modifico il codice vicino alle chiamate principali ...
app = wx.App()
w = Prova("prova WIT")
w.Show()
import wx.lib.inspection
wx.lib.inspection.InspectionTool().Show()
app.MainLoop()
ottenendo questo:

Come vedete lo strumento chiamato Widget Inspection Tool fa quello che promette: permette di analizzare le finestre a cui viene abbinato, elencando tutti gli oggetti con i relativi attributi presenti.
Come vedete però, non ci sono i sizer(s): se li volete visualizzare, vanno abilitati cliccando sull’opportuno pulsante Sizers sullo strumento:

Adesso che li vedete sullo strumento e volete controllare come si comportano nella widget, vi basta cliccare sul pulsante Highlight per farlo.

Questo ci fa capire immediatamente che il problema del nostro layout è il sizer orizzontale che non va ad occupare tutto lo spazio disponibile nel sizer verticale. Si risolve modificando il codice come segue:
# ...
box.Add(hbox, proportion=1)
# ...
Un ultima cosa molto importante del Widget Inspection Tool è che può aiutarci a tracciare il comportamento anche degli eventi che accadono alle finestre che implementiamo. Per vedere questa sua funzionalità bisogna prima selezionare la widget che ci interessa, poi premere il pulsante Events che mostrerà una nuova finestra con un elenco degli eventi disponibili per quell’oggetto.
A questo punto ci basta provare ad utilizzare la nostra finestra:

Adesso il segreto non tanto segreto è provare ad utilizzare questo strumento ogni volta che qualcosa non quadra per provare a ispezionare i nostri capolavori :)
Il capitolo degli approfondimenti
Gli Insights sono un capitolo dedicato a degli approfondimenti che ho voluto inserire nel mio materiale.
Più che un capitolo, qui troverete una raccolta di argomenti inseriti in ordine sparso e tipicamente scollegati fra di loro. Che si trovano qui significa almeno una volta ognuno di questi argomenti ha suscitato il mio o il vostro interesse ad una delle nostre lezioni. E quindi io ho cercato di documentarmi al meglio e di scriverne in maniera accessibile agli studenti.
Almeno questo è quello che ho cercato di fare.
L’obiettivo di queste pagine è quella di farvi perdere un pomeriggio, di farvi spendere tempo (e soldi) dietro alla programmazione, di… farvi venire fame.
Chissà…
uv
uv è un package and project manager scritto in Rust per i progetti Python. uv è pensato per essere una soluzione all-in-one
ovvero essere l’unico strumento da utilizzare per:
- scaricare pacchetti da PyPi (sostituisce
pip) - gestire ambienti virtuali (sostituisce
venv) - installare (copie) di Python stesso (sostituisce
pyenv) - gestire progetti e le loro dipendenze (sostituisce
pip-tools,poetry, etc…) - utilizzare ambienti e tools di test e di sviluppo (sostituisce
pipx, etc… ) - creare pacchetti distribuibili (sostituisce
poetry, etc…) - pubblicare pacchetti su PyPi (sostituisce
twine)
In buona sostanza, uv è l’unico strumento che dovremo imparare ad utilizzare per fare alcune o tutte queste cose.
E non mi sembra neanche uno strumento difficile da utilizzare ☺️
Installare uv
Su Windows puoi utilizzare l’installer di rete. Ti basta aprire il terminale e copiaincollare:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Analoga cosa su MacOS, apri il terminale ed esegui:
curl -LsSf https://astral.sh/uv/install.sh | sh
Su Linux puoi decidere se utilizzare lo stesso comando disponibile per MacOS oppure affidarti al pacchetto disponibile nella tua distro!!!
Per aggiornare uv, una volta che lo hai installato, ti basta ricorrere al suo sistema di auto-aggiornamento. Sul terminale di qualunque OS, digita:
uv self update
Creare un progetto
Comando semplicissimo:
uv init --opzione nomeProgetto
dove opzione è una delle seguenti (ho citato solo le 3 più comuni):
| Tipo | Opzione | Build System | Struttura | Punto di accesso | Distribuzione | Casi d’uso |
|---|---|---|---|---|---|---|
| Applicazione | –app (default) | no | flat | main.py | non intesa | Web apps, script |
| Libreria | –lib | sì | src layout | (nessuno) | PyPi | Librerie riutilizzabili |
| Pacchetto | –package | sì | src layout | console script | app installabili, PyPi | CLI, GUI apps |
Le app hanno struttura piatta ovvero creando una applicazione di nome provaApp si ottiene la seguente struttura:
provaApp
├── .git
├── .gitignore
├── main.py
├── pyproject.toml
├── .python-version
└── README.md
Librerie e pacchetti hanno una struttura con la cartella dei sorgenti, quindi creando un pacchetto di nome provaPack si ottiene la seguente struttura:
provaPack
├── .git
├── .gitignore
├── pyproject.toml
├── .python-version
├── README.md
└── src
└── provapack
└── __init__.py
Notate che in entrambi sono già presenti un repository git e un file per il controllo della versione Python in uso.
Gestire un ambiente virtuale
La gestione degli ambienti virtuali con uv è estremamente semplice e, nella maggior parte dei casi, completamente automatica!
Quando crei un nuovo progetto con uv init, l’ambiente virtuale non viene creato immediatamente. Verrà creato automaticamente al primo comando che ne ha bisogno, ad esempio:
cd provaPack
uv add requests
Questo comando:
- Crea automaticamente l’ambiente virtuale nella cartella
.venv - Installa il pacchetto
requestse le sue dipendenze - Aggiorna il file
pyproject.tomlcon la nuova dipendenza
Sincronizzare l’ambiente
Se hai scaricato un progetto già esistente (ad esempio da Git) e vuoi ricreare l’ambiente virtuale con tutte le dipendenze, usa:
uv sync
Questo comando crea l’ambiente virtuale dove installa la versione di Python indicata nel file .python-version, installa tutti i pacchetti necessari, indicati nel file pyproject.toml nelle stesse versioni indicate nel file uv.lock e rende il sistema pronto all’esecuzione identica del codice in qualunque ambiente!
Eseguire script nell’ambiente virtuale
Per eseguire il tuo codice Python usando l’ambiente virtuale del progetto:
uv run main.py
oppure, se hai creato un pacchetto (come probabilmente ti ho suggerito):
uv run nomePacchetto
Il comando uv run si occupa automaticamente di:
- eseguire
uv sync(vedi sopra) - attivare l’ambiente virtuale
- Eseguire il comando
- Disattivare l’ambiente al termine
Gestire le dipendenze
uv è potente, ma semplice da usare anche per la gestione delle dipendenze.
Immagina (ad esempio) che il pacchetto che stai creando dipenda dalla libreria nome-pacchetto.
Per aggiungerla come dipendenza al progetto:
uv add nome-pacchetto
Clamorosamente, il comando per rimuoverla invece è:
uv remove nome-pacchetto
Per vedere tutti i pacchetti installati:
uv pip list
Ricorda che uv non andrà ad installare il pacchetto nel tuo Python ma nel suo ambiente virtuale! In questo modo tutto sarà più riproducibile in qualunque ambiente
tu lavori (casa, scuola, lavoro, PC con Windows, Mac, Linux, etc…)
Lock file
uv crea automaticamente un file uv.lock che contiene le versioni esatte di tutti i pacchetti installati. Questo garantisce che chiunque scarichi il progetto avrà esattamente le stesse versioni dei pacchetti, evitando problemi di compatibilità.
Warning
uv.lock
Aggiungi il file
uv.lockal controllo di versione (Git) per condividere l’ambiente esatto con tutti quelli che collaborano con te e con tutti quelli che vogliono provare il tuo progetto!
Installare Python (per il tuo progetto)
Sì… è vero… non lo avevo ancora detto. Fino a qui potresti anche non avere Python installato nel tuo computer!
uv può scaricare e gestire versioni di Python in modo completamente indipendente dal sistema operativo. Ogni progetto può usare la sua versione specifica di Python, senza conflitti con altri progetti o con l’installazione di sistema.
Per indicare ad uv quale versione python vuoi utilizzare nel tuo progetto, puoi farlo in due modi:
o specificando in fase di creazione del progetto:
uv init --python 3.13 mio-progetto
oppure, in seguito alla creazione, con il comando:
uv python pin 3.13
I comandi indicati aggiornano semplicemente il file .python-version e da quel momento tutti i comandi uv run useranno Python 3.13.
Note
Il file .python-version
Il file
.python-versioncontiene semplicemente la versione di Python richiesta dal progetto, ad esempio:3.12
uvlegge automaticamente questo file e usa la versione corretta. Se la versione non è installata,uvla scarica automaticamente al primo utilizzo!
Per vedere tutte le versioni di Python che puoi installare:
uv python list --all-versions
Potresti infine decidere di voler provare ad eseguire uno script con una versione diversa di Python. Questa cosa, tecnicamente complicatissima fino ad ora,
diventa praticamente banale con uv. Se vuoi provare il funzionamento del tuo script con la nuova fiammante 3.14:
uv run --python 3.14 python script.py
Git
intro
github vs world
Operazioni
- init
- clone
- status
- add
- commit
- log
- branch
- checkout
- rebase
- merge
- push
- fetch
- pull
- tag
Package(s)
Elenco i punti da seguire nell’ordine (più o meno) in cui dovrebbero essere svolti.
nome pacchetto
Per prima cosa, scegli bene il nome del pacchetto. Un nome semplice, possibilmente senza spazi nè maiuscole. Nel proseguio del capitolo, farò
finta di aver scelto il nome ciccio.
Se avete pensato un nome con due o più parole (tipo distruzione dell'universo) dovete renderlo un identificatore
semplice, ovvero togliere spazi, articoli e proposizioni (rendendolo ad esempio DistruzioneUniverso oppure distruzione_universo).
creazione
Se, come ho detto prima, ho scelto per il mio pacchetto il nome ciccio, allora apro il terminale e digito:
uv init --package ciccio
rinomina
Se all’inizio non avevo pensato abbastanza bene al nome del pacchetto e adesso voglio cambiarlo, devo eseguire 2 importanti operazioni.
Immaginate che all’inizio, non sapendo bene quale nome scegliere, io abbia creato il pacchetto provaPack, di cui ho la seguente struttura:
provaPack
├── pyproject.toml
├── README.md
└── src
└── provapack
└── __init__.py
Andiamo adesso a rinominare il package in 2 mosse:
-
Modificare il nome del pacchetto nel file
pyproject.toml -
rinomina la cartella dentro
src, lasciando inalterata la cartella principale.
Alla fine la struttura sarà la seguente:
provaPack
├── pyproject.toml (modificato)
├── README.md
└── src
└── ciccio
└── __init__.py
Sistema gli import
Riordina e sistema gli import di ogni file del tuo progetto con la seguente logica:
-
prima gli import della libreria Python Standard (es: random, math, etc…), poi salta una riga
-
poi gli import aggiunti tramite
pip(es: pygame, platformdirs, etc…), poi salta una riga -
infine gli import del tuo proprio package (i files dentro
src/my_package) inclusi in forma esplicita o implicita. Meglio la forma esplicita…
Alla fine sarà una cosa del genere:
# libreria Standard
import random
import math
# librerie pip
import pygame
import platformdirs
# moduli del mio package: forma esplicita
from ciccio.nomeModulo import *
# oppure
from ciccio import nomeModulo
# moduli del mio package: forma implicita (con il punto)
from .nomeModulo import *
# oppure
from . import nomeModulo
Gestione files NON di codice
Buona norma organizzare codice e files all’interno della cartella del modulo in maniera sensata. Aggiungete una struttura simile alla seguente:
provaPack
├── pyproject.toml
├── README.md
└── src
└── ciccio
└── __init__.py
└── images
└── file1.jpg
└── (... le altre immagini...)
└── sounds
└── campanella1.mp3
└── (... gli altri suoni... )
Poi aggiungete un file chiamato resources.py al vostro progetto, in cui scriverete una cosa tipo questa:
from importlib.resources import files
from pathlib import Path
# eventualmente aggiungere tutte le funzioni relative alle cartelle che avete creato in src.
def get_sound(filename: str) -> Path:
return files(__package__) / "sounds" / filename
def get_image(filename: str) -> Path:
return files(__package__) / "images" / filename
A questo punto, nel vostro codice, quando avete bisogno di un file, scrivete:
# ricorda, ciccio è il nome del mio package
from ciccio.resources import *
fileSfondo = get_image("sfondo.png")
fileAudio = get_sound("sottofondo.mp3")
percorsi dei files con PlatformDirs
Ricordati di aggiungere PlatformDirs al tuo package con uv:
uv add platformdirs
e poi revisiona tutti i percorsi dei files che il tuo package scrive (es: impostazioni, classifica, etc…), riscrivendo il percorso con PlatformDirs.
Eventualmente riguarda il capitolo sui files dove è spiegato il modulo PlatformDirs.
Github: descrizione e README
E’ buona norma trovare un momento anche per curare un minimo il repository GitHub, soprattutto sistemando queste due semplici cose!
La descrizione del progetto si può modificare direttamente su GitHub, cliccando sulla rotellina della modifica in alto a dx del progetto (di fianco alla parola About). Scrivete una frase semplice, che descrive sommariamente il gioco ed elenca i vostri cognomi. Ad esempio una cosa tipo: “Una versione di ‘Acchiappa la Talpa’, by Pippo e Ciccio”.
Il file README.md fornisce la descrizione che compare a fondo pagina. Va scritto in markdown, un linguaggio di rappresentazione semplice e potente. Trovate tonnellate di documentazione online. Potete anche semplicemente scrivere testo semplice. Si può modificare online oppure facendo un commit sul file stesso. Sarebbe molto carino aggiungerci una pubblicità per il vostro gioco! Fate uno screenshot, aggiungetelo al progetto e al file README. Spazio alla fantasia…
File README. Descrizione Operativa!
Aggiungete una interruzione al vostro file README, ad esempio una <hr> e poi producete una descrizione operativa: raccontate come vi siete organizzati per lavorare,
chi ha fatto cosa in maniera meticolosa: per ogni file del progetto voglio sapere l’origine e gli autori!!!
Ad esempio:
- Gina ha creato sul sito www.immaginibellissime.com le immagini file1.jpg, file2.jpg.
- Pino ha scritto insieme alla AI Cancer la funzione spara_a_salve() e la funzione schioppaTutto().
- Gina e Pino hanno lavorato insieme a tutte le funzioni del file coso.py, che era stato inizialmente generato tramite l’AI OplaGQT.
Insomma… una cosa del genere…
Licenza
Ultima revisione della licenza e del contenuto del progetto. Di chi é il codice che pubblicate? Avete intestato tutti i file di codice? Dove avete trovato le immagini?? E i suoni?? Qualcuno può prendersela con voi per le cose che state per pubblicare??
Tutti i file python che avete scritto dovrebbero avere una intestazione di questo tipo:
# Nome del modulo - breve descrizione.
# (riga vuota)
# Descrizione più lunga opzionale su più righe,
# che spiega cosa fa il modulo.
# (riga vuota)
# License: See LICENSE file in the project root for details.
# (riga vuota)
# Authors:
# nome cognome <email>
# nome cognome <email>
Se usate l’inglese bene. Altrimenti va bene anche l’italiano. E’ il momento di rivalutare la scelta della licenza. Scegliete una licenza opensource, anche controllando questo sito: Choose a License. Oppure mantenete quella iniziale.
In caso, sostituite il file LICENSE. E ricominciate la valutazione di questo punto.
Oppure andiamo avanti…
Aggiornate il numero di versione!!
Se fino ad ora abbiamo lavorato alla versione 0.1.0 del nostro progetto, è ora di aggiornare almeno alla versione 0.2.0!!!
Per farlo basta modificare il file pyproject.toml (e fare il commit)!
Se proprio vi sentite fenomeni, potete taggare il commit con l’etichetta v0.2.0!
Creazione eseguibile da distribuire (su Windows)
Potrebbe essere comodo provare a distribuire il proprio codice in forma eseguibile (es: gioco.exe) per semplificare la distribuzione fra gli utenti meno esperti.
Per farlo utilizzeremo un tool chiamato pyinstaller. Posizionati nella cartella principale del nostro progetto (nel mio caso, del progetto ciccio) e digitiamo:
uv add --dev pyinstaller
Poi dalla stessa cartella, eseguite:
uv run pyinstaller --onefile --windowed --collect-all ciccio -n "ciccio" ./src/ciccio/__init__.py
Se tutto va bene dovrebbe essersi creata una cartella chiamata dist con dentro l’eseguibile!!!
Finito!!!
Turtle Crash Course
Il modulo della tartaruga :)
E’ un modulo didattico predefinito in Python (non occorre installarlo, è già presente). Tramite codice
si muove una tartaruga (o più, o qualche forma che gli assomiglia) e si realizza qualche disegno più o meno
complicato imparando a programmare in maniera naturale.
Questo che trovate sotto è un Crash Course: non ci sono spiegazioni, ma informazioni buttate là e qualche obiettivo da raggiungere…
Buon divertimento!!!
Fare cose con turtle
Cominciamo dal programma assolutamente minimo, che però in realtà contiene già parecchie cosine su cui riflettere!
# qui importi la libreria turtle e tutte le sue cose
from turtle import *
# s è una variabile che rappresenta lo sfondo dove la tartaruga si muoverà
s = Screen()
# il titolo della finestra (guarda in alto)
s.title("Prima Prova")
# imposta la dimensione (in pixel) della finestra: width x height
s.setup(800,600)
# il colore di sfondo!
# Puoi anche indicarlo con la notazione esadecimale RGB: "#00FF00"
s.bgcolor("green")
# t è una variabile che rappresenta la tartaruga sullo schermo
t = Turtle()
# la forma della tartaruga. Può essere:
# "arrow" , "turtle" , "circle" , "square" , "triangle" , "classic"
t.shape("turtle")
# la dimensione della tartaruga. Il valore di base è 1. Se metti 2 significa il doppio...
t.shapesize(1.5)
# ... qui fai cose...
# quando clicchi sulla pagina il programma si chiude
s.exitonclick()
Leggi i commenti! Prova a modificare il colore di sfondo. Prova a modificare la forma e la dimensione della tartaruga. Prova a cliccare sulla pagina. Prova a cambiare il titolo.
Hai capito, dai… passiamo avanti!!!
Note
Hai visto il commento “qui fai cose” nel codice sopra?
Da ora in poi inserirò semplici parti di codice che andranno inserite in quel punto per ogni programma. In questo modo potrò mettere in evidenza meglio il codice importante senza ripetere ogni volta le parti di base
Disegnare con Turtle
La tartaruga (o qualunque forma scegliate) può disegnare sullo schermo tramite una penna. In base alla direzione in cui essa è posizionata, quando le dici di camminare, se ha la penna abbassata scrive!
Le funzioni che supporta la tartaruga (nei nostri esempi, l’oggetto t), sono:
-
t.forward( ppp ): muove la tartaruga dippppixel in avanti (dritto al suo muso) -
t.backward( ppp ): muove la tartaruga dippppixel all’indietro -
t.left( ggg ): gira la tartaruga diggggradi verso sx (in senso antiorario) -
t.right( ggg ): gira la tartaruga diggggradi verso dx (in senso orario) -
t.setheading( dir ): orienta la tartaruga versodir:0–> destra ,90–> alto ,180–> sinistra ,270–> basso
Vediamo l’utilizzo di alcune di queste per disegnare 2 quadrati col bordo colorato.
# colore e dimensione della penna
t.pencolor("blue")
t.pensize(5) # in pixel
# disegno un quadrato blue di 100 pixel di lato
for n in range(4):
t.forward(100)
t.right(90)
# alzo la penna, così mi sposto senza scrivere
t.penup()
# mi muovo di 200 pixel, riabbasso la penna
t.forward(200)
t.pendown()
# disegno un quadrato rosso...
t.pencolor("red")
for n in range(4):
t.forward(100)
t.right(90)
Riempire le forme
Quando disegni una forma chiusa è possibile colorare il suo interno con un colore a scelto, anche diverso da quello del bordo (cioè della penna)!
Le funzioni in gioco sono le seguenti:
t.fillcolor( "color" ): imposta acoloril colore di riempimento di una forma chiusat.begin_fill(): inizia a riempire una format.end_fill(): smette di riempire una forma
La logica da applicare è molto semplice:
- Imposti il colore scelto con
fillcolor - lanci la funzione
begin_fill - disegni una forma chiusa
- lanci la funzione
end_fill
Al termine vedrai una forma chiusa colorata. Guardiamo un esempio di codice che è sempre più facile!
t.pencolor("blue")
t.pensize(2) # in pixel
t.fillcolor("yellow")
# disegno un triangolo blue, riempito di giallo
t.begin_fill()
for n in range(3):
t.forward(100)
t.left(120)
t.end_fill()
# alzo la penna, mi giro a dx, avanzo ....
t.penup()
t.setheading(0) # 0 = dx...
t.forward(200)
t.pendown()
# disegno un triangolo blue, riempito di giallo
t.begin_fill()
for n in range(3):
t.forward(100)
t.left(120)
t.end_fill()
Cose con la tartaruga
Mostra, nascondi, stampa la tartaruga!
t.showturtle(): mostra la tartarugat.hideturtle(): nasconde la tartarugat.stamp(): stampa una copia della tartaruga (o dellaforma) nel punto ove essa si trova.
Disegna cerchi, punti, parole
t.circle ( ppp ): disegna un cerchio di diametroppppixelt.dot ( ppp ,"color" ): disegna un punto (un cerchio pieno) di diametroppppixel e colorecolort.write ( "words" ): scrive le parole indicate nella posizione corrente
Muovi, velocizza, riporta a casa (al centro)
t.speed( vvv ): imposta la velocità della tartarugat.goto( x , y ): porta la tartaruga al punto(x,y)t.home(): porta la tartaruga a casa (al centro)
Disegni con Turtle
Ok… ormai avete capito tutto di Turtle… vi inserisco alcuni esercizi disegni che ho fatto io… Provate a farli uguale o a rifarli meglio!!!

PyGame
pygame è un modulo Python, disponibile su PyPi che implementa la libreria SDL.
Questa fornisce accesso cross-platform alle componenti hardware multimediali, come suono, video, mouse, tastiera, joystick, etc…
Per installare la libreria pygame su Thonny, cercate dal sistema di gestione dei pacchetti
il nome corrispondente e cliccate (incredibilmente) INSTALLA!!!
Note
PyGame: sito e documentazione ufficiale
Attualmente non ve ne frega nulla, me ne rendo conto. Ma lasciatemi scrivere qui un paio di info utili! Il sito ufficiale della libreria PyGame è https://www.pygame.org/
Il sito della documentazione ufficiale è https://www.pygame.org/docs/
Fare cose con PyGame
Cominciamo dal programma assolutamente minimo, che però in realtà contiene già parecchie cosine su cui riflettere!
# importa ed inizializza la libreria pygame
import pygame
pygame.init()
# lo screen (con titolo)
screen = pygame.display.set_mode( (800, 600) )
pygame.display.set_caption("Il mio primo gioco con PyGame!")
# facile...
running = True
while running:
# serve a gestire la X di chiusura in alto
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# colora lo schermo di verde
screen.fill("green")
# aggiorna il contenuto dello schermo
pygame.display.flip()
# Chiude pygame
pygame.quit()
Questo codice crea un rettangolo verde, di grandezza 800x600 pixel, con titolo “Il mio primo gioco con PyGame!”.
Quando clicchi sulla X rossa in alto, il gioco si chiude tranquillamente. Per verificare che sia così, potreste aggiungere una print in fondo. Oppure provare a modificare colore e dimensione della finestra principale!
Vediamo pezzo per pezzo cosa abbiamo scritto:
-
import pygame… ormai dovremmo aver capito. Importa la libreria pygame -
pygame.init(). Inizializza la libreria pygame. Permette il funzionamento di tutto il resto -
screen ...è una variabile che contiene unaPyGame Surface, una superficie (un… rettangolo) gestito da PyGame per fare le cose. Questo in particolare, sarà il nostro schermo. -
screen.fill("color"): prende come parametro una stringa o una tupla RGB. Colora lo sfondo della surface. -
pygame.display.flip(): aggiorna il contenuto dello schermo. Senza questa funzione ripetuta nel loop non funziona nulla!!!
Per adesso basta così con le spiegazioni…. proviamo ad andare avanti con il codice!!!
Uscire con il tasto Esc
Una cosa che a me piace molto è quella di dare la possibilità di uscire semplicemente premendo il tasto Esc: questa funzionalità si può ottenere aggiungendo il seguente codice!
import pygame
pygame.init()
screen = pygame.display.set_mode( (800, 600) )
pygame.display.set_caption("Il mio primo gioco con PyGame!")
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Se l'evento è la pressione di un tasto...
# ... e il tasto è il tasto ESC.. esc(i)!
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
screen.fill("green")
pygame.display.flip()
pygame.quit()
Ragionate su come modificare il codice per fare qualunque cosa alla pressione di un tasto qualsiasi!
Tip
Potrebbe essere carino impostare l’uscita dal gioco con il tasto Q (QUIT), oppure con un tasto a propria scelta!
L’elenco completo dei codici relativi ai tasti si trova qui: pygame keys
Aggiungere scritte
In questa parte del tutorial, vedremo come è possibile aggiungere una scritta (o più di una) su pygame.
Si tratta semplicemente di selezionare un oggetto font (Ad esempio “Times”, 12) e utilizzarlo per fare il render (cioè il disegno) della scritta che ci interessa.
Il risultato sarà un rettangolo di una certa dimensione e colore, che andrà disegnato (con blit) su un qualche punto dello schermo.
import pygame
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))
pygame.display.set_caption("Aggiungere scritte")
# Definizione dei font: Quello "grosso" e quello "giusto"
Titlefont = pygame.font.SysFont('Impact', 70)
Normalfont = pygame.font.SysFont('Impact', 30)
# oggetto_testo = oggetto_font.render(stringa, True, colore, sfondo (opzionale) )
game_end = Titlefont.render("Hai Perso!", True, "red")
close_tip = Normalfont.render("Click ESC to exit", True, "blue","yellow")
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
screen.fill("white")
screen.blit(game_end, (100,100))
screen.blit(close_tip, (100,300))
pygame.display.flip()
pygame.quit()
Aggiungere pulsanti
È possibile creare pulsanti semplicemente modificando il comportamento delle scritte in due modi:
- reagendo (ad esempio, cambiando colore) quando ci passi sopra con il mouse
- eseguendo una qualche operazione quando viene fatto click sopra di esse
Il codice sotto fa esattamente questo!
-
Crea una scritta bianca su sfondo rosso disegnata in un opportuno rettangolo inserita in qualche punto sullo schermo.
-
Modifica il colore di sfondo del rettangolo che rappresenta il pulsante quando il mouse ci passa sopra (nell’esempio ho scelto il blu)
-
Reagisce quando si clicca nell’area del pulsante eseguendo una qualche porzione di codice (in questo caso, semplicemente facendo ESCI!)
import pygame
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
font = pygame.font.SysFont('Arial',30)
textRect = font.render('Esci' , True , "white")
buttonRect = pygame.Rect(SCREEN_WIDTH // 2, SCREEN_HEIGHT //2, 140, 40)
running = True
while running:
# posizione del mouse
mPos = pygame.mouse.get_pos()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
# quando clicchi SOPRA il pulsante... FAI QUALCOSA!!!
if event.type == pygame.MOUSEBUTTONDOWN:
if buttonRect.collidepoint(mPos):
running = False
screen.fill("yellow")
# ANIMAZIONE DEL PULSANTE (cambia colore quando ci passi sopra)
buttonColor = "red"
if buttonRect.collidepoint(mPos):
buttonColor = "blue"
button = pygame.draw.rect(screen,buttonColor,buttonRect)
screen.blit(textRect , (SCREEN_WIDTH //2 + 50, SCREEN_HEIGHT// 2) )
pygame.display.flip()
pygame.quit()
Aggiungere forme geometriche
L’esempio di questo pezzo di codice ha un puro scopo didattico, cioè quello di mostrare come si creano e posizionano alcune figure geometriche su pygame.
Se pensate ad un gioco e avete bisogno di una figura geometrica particolare… adesso non potete fingere di non sapere come si fa!!!!
import pygame
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Aggiungere forme geometriche")
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
screen.fill("white")
# cerchio ROSSO di raggio 80 al centro dello screen
# c è il "rettangolo" che contiene il cerchio disegnato
c = pygame.draw.circle(screen, "red", (SCREEN_WIDTH//2, SCREEN_HEIGHT//2), 80)
# rettangolo BLUE che va dal punto (600,400) lungo 180, largo 100
# r è il rettangolo che contiene il rettangolo disegnato
r = pygame.draw.rect(screen, "blue", (600, 400, 180, 100))
# linea VERDE dal punto... al punto ... di spessore ...
# l è il "rettangolo" che contiene la linea disegnata
l = pygame.draw.line(screen, "green", (600, 100), (700, 300), 8)
# ellisse VIOLA contenuto nel rettangolo che parte da (100,400) lungo 60, largo 90. Di spessore...
# el è il rettangolo che contiene l'ellisse disegnata
el = pygame.draw.ellipse(screen, "purple" , (100, 400, 60, 90), 8)
# poligono GIALLO (riempito) che collega i punti...
# pol è il rettangolo che contiene il poligono disegnato
pol = pygame.draw.polygon(screen, "yellow",((146, 0), (291, 106),(236, 277), (56, 277), (0, 106)))
pygame.display.flip()
pygame.quit()
Muovere un rettangolo
In questa parte del tutorial iniziamo davvero a fare le cose sul serio!!!
In questo pezzo di codice vediamo come si muove un rettangolo dentro allo schermo comandandolo con le frecce!
Il mio rettangolo parte dal centro (guarda la posizione iniziale) ed ha un movimento di coda in quanto gira il retro quando si curva in una direzione (guarda come inverto le dimensioni w,h).
Alla fine ogni volta si ripittura lo schermo di nero e si disegna il rettangolino nella posizione ove dovrebbe trovarsi.
import pygame
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Rettangolo che si muove")
# posizione iniziale
x = SCREEN_WIDTH // 2
y = SCREEN_HEIGHT // 2
# dimensioni rettangolo
w = 40
h = 20
# velocità di spostamento
speed = 8
running = True
while running:
pygame.time.delay(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > 0:
w,h = 40,20
x -= speed
if keys[pygame.K_RIGHT] and x < SCREEN_WIDTH - w:
w,h = 40,20
x += speed
if keys[pygame.K_UP] and y > 0:
w,h = 20,40
y -= speed
if keys[pygame.K_DOWN] and y < SCREEN_HEIGHT - h:
w,h = 20,40
y += speed
screen.fill("black")
player = pygame.draw.rect(screen, "red", (x, y, w, h))
pygame.display.flip()
pygame.quit()
Se per voi è tutto chiaro quello che succede qui sopra, vi sfido ad aggiungere una funzionalità: il controllo della velocità! Con un tasto (a vostra scelta) si accelera, con un altro si rallenta!
Buon divertimento!!!
Nemici e Collisioni
Adesso è il momento di aggiungere dei nemici… o meglio… degli ostacoli! Questi appariranno ogni tanto in posti più o meno casuali e noi andremo a controllare le (eventuali) collisioni fra il giocatore e questi!
Per far funzionare una cosa del genere dobbiamo organizzare un pò di cose:
-
un evento che capiti ogni TOT ms che ci permetta, quando si scatena, di fare qualcosa (ad esempio: aggiungere un nemico)
-
una lista che ci ricordi l’elenco (soprattutto… la posizione) di tutti i nemici presenti sul campo di gioco
-
per ogni elemento nemico, il controllo della eventuale collisione con il giocatore
Osservate bene il codice qui sotto!
import pygame
import random
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Rettangolo che si muove con nemici a caso")
# crea un nuovo (tipo di) evento (da gestire nel for degli eventi sotto)
# che verrà scatenato ogni TOT ms
ADD_ENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(ADD_ENEMY, 1000)
# l'elenco dei nemici
enemies = []
# info utili per il rettangolo giocatore:
# (x,y) la posizione, (w,h) la dimensione, speed... indovina!!!!!
x = SCREEN_WIDTH // 2
y = SCREEN_HEIGHT // 2
w = 40
h = 20
speed = 8
running = True
while running:
pygame.time.delay(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
if event.type == ADD_ENEMY:
posx = random.randint(0,SCREEN_WIDTH - 20)
posy = random.randint(0,SCREEN_HEIGHT - 20)
enemies.append( (posx,posy) )
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > 0:
w,h = 40,20
x -= speed
if keys[pygame.K_RIGHT] and x < SCREEN_WIDTH - w:
w,h = 40,20
x += speed
if keys[pygame.K_UP] and y > 0:
w,h = 20,40
y -= speed
if keys[pygame.K_DOWN] and y < SCREEN_HEIGHT - h:
w,h = 20,40
y += speed
screen.fill("black")
player = pygame.draw.rect(screen, "red", (x, y, w, h))
# in questo caso i nemici sono fermi: sono ostacoli
for posx,posy in enemies:
# en è il rettangolo disegnato
en = pygame.draw.rect(screen, "white", (posx, posy, 20, 20))
# se questo rettangolo "collide" con il punto (x,y) ove si trova il giocatore...
if player.colliderect(en):
print("HAI PERSO!")
running = False
pygame.display.flip()
pygame.quit()
Tip
Collisioni!!
Puoi controllare se un punto attraversa un’area, se due rettangoli si sovrappongono, se un punto passa dentro un rettangolo… praticamente ogni tipo di incrocio fra oggetti!!!
La documentazione per ognuno di questi la trovi qui: Documentazione pygame.Rect
Lavorare con le Immagini
Questo pezzetto di codice presuppone che abbiate nella stessa cartella due immagini:
- pere.jpg, lo sfondo con le pere
- mosca.png, la mosca con bordo trasparente
import pygame
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Lavorare con le Immagini")
imgSfondo = pygame.image.load("pere.jpg")
imgSfondo = pygame.transform.scale(imgSfondo,(SCREEN_WIDTH,SCREEN_HEIGHT))
imgMosca = pygame.image.load("mosca.png")
imgMosca = pygame.transform.scale(imgMosca,(30,30))
x = SCREEN_WIDTH // 2
y = SCREEN_HEIGHT // 2
width = 30
height = 30
speed = 8
running = True
while running:
pygame.time.delay(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > 0:
x -= speed
if keys[pygame.K_RIGHT] and x < SCREEN_WIDTH - width:
x += speed
if keys[pygame.K_UP] and y > 0:
y -= speed
if keys[pygame.K_DOWN] and y < SCREEN_HEIGHT - height:
y += speed
# invece di screen.fill("white")
screen.blit(imgSfondo,(0,0) )
# invece di pygame.draw.rect(screen, "red", (x, y, width, height))
screen.blit(imgMosca,(x,y))
pygame.display.flip()
#
pygame.quit()
Note
Modificare le immagini
Sarebbe fighissimo se la mosca si girasse nella direzione opportuna di volo!!! Per farlo non serve modificare l’immagine (il file “mosca.png”) ma solo l’oggetto immagine (imgMosca).
Per sapere come, controlla tutta la documentazione qui: Documentazione pygame.transform
Aggiungere suoni
Per far vedere come fermare e far ripartire una musichetta ho implementato anche la pausa :)
import pygame
pygame.init()
pygame.mixer.init()
pygame.mixer.music.load("zanzara.mp3")
pygame.mixer.music.set_volume(0.5)
pygame.mixer.music.play()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Lavorare con le Immagini e i suoni")
imgSfondo = pygame.image.load("pere.jpg")
imgSfondo = pygame.transform.scale(imgSfondo,(SCREEN_WIDTH,SCREEN_HEIGHT))
imgMosca = pygame.image.load("mosca.png")
imgMosca = pygame.transform.scale(imgMosca,(30,30))
x = SCREEN_WIDTH // 2
y = SCREEN_HEIGHT // 2
width = 30
height = 30
speed = 8
running = True
paused = False
while running:
pygame.time.delay(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
# PREMI "P" per mettere in pausa (o uscire dalla pausa)
if event.type == pygame.KEYDOWN and event.key == pygame.K_p:
if paused:
paused = False
pygame.mixer.music.play()
else:
paused = True
pygame.mixer.music.pause()
if paused:
continue
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > 0:
x -= speed
if keys[pygame.K_RIGHT] and x < SCREEN_WIDTH - width:
x += speed
if keys[pygame.K_UP] and y > 0:
y -= speed
if keys[pygame.K_DOWN] and y < SCREEN_HEIGHT - height:
y += speed
# invece di screen.fill("white")
screen.blit(imgSfondo,(0,0) )
# invece di pygame.draw.rect(screen, "red", (x, y, width, height))
screen.blit(imgMosca,(x,y))
pygame.display.flip()
#
pygame.quit()
Python & MySQL
Per sviluppare applicazioni in Python con interazione con un DBMS servono sostanzialmente 2 cose:
- un modulo Python per il collegamento con il DBMS
- l’installazione di un DBMS per il testing dell’applicazione
Modulo Python
Il modulo necessario per le implementazioni che vedremo si chiama “MySQL Connector”. Per l’installazione dello stesso basta utilizzare pip, con il comando indicato sotto
$ pip install mysql-connector
oppure dalla linea di comando, invocando l’interprete Python
$ python -m pip install mysql-connector
I comandi elencati funzionano sicuramente su Linux e Mac. Su Windows dipende da come è stata fatta l’installazione di Python e in particolare se l’eseguibile Python è stato inserito nel PATH.
Se così non fosse bisogna spostarsi con la console della riga di comando fino alla cartella di installazione di Python ed eseguire il comando sopra da lì.
Ad esempio se l’installazione è stata fatta su C:\Python, allora il comando va
eseguito in quella cartella.
Per verificare che l’installazione del modulo è andata a buon fine basta aprire l’interprete Python e provare a importare il modulo:
>>> import mysql.connector
Se Python non si lamenta di nulla, siamo pronti per il prossimo step :)
Installazione DBMS
Per testare il codice che scriveremo dobbiamo installare un DBMS. Bene… installeremo MariaDB! Cioè… come scusa? Non installiamo MySQL? E tutti discorsi fatti fino ad ora? E… il titolo di questa dispensa???
Provo a spiegare con una frase: MariaDB è un software libero nato come fork di MySQL e studiato come Drop-In Replacement dello stesso. Credo che come spiegazione sia un po’ criptica ma sufficientemente esaustiva :)
Se volete maggiori informazioni sulle motivazioni della scelta, vi rimando alla loro pagina wikipedia: https://it.wikipedia.org/wiki/MariaDB.
Per installare MariaDB basta seguire le istruzioni che trovate nel loro sito ufficiale: https://mariadb.org/
Connessione al DB
Cominciamo stabilendo una connessione fra il nostro script Python e il database:
import mysql.connector
pydb = mysql.connector.connect(
host="localhost",
user="username",
passwd="password",
database="dbname"
)
Ovviamente se c’è un problema qualunque nella connessione questo pezzo di codice fallirà. E sarà inutile andare avanti…
DDL execution
DDL (Data Definition Language) è quella parte di codice SQL che serve
per la definizione dei dati. Per estrema semplicità dirò semplicemente
che DDL si riferisce ai comandi SQL CREATE (ad esempio: CREATE
TABLE…), ALTER, DROP.
Questo tipo di comandi non ritornano “valori” o tabelle come risultato, ma generano semplicemente una risposta del tipo “OK, tutto fatto!” oppure “Mannaggia, qualcosa è andato storto!!!”.
Per eseguire questo tipo di comandi abbiamo bisogno di un “cursore” che si occupi di attualizzare l’istruzione descritta.
pycur = pydb.cursor()
pycur.execute("CREATE TABLE if not exists tbl_seven(number int);")
DML execution
DML (Data Manipulation Language) è quella parte di codice SQL che serve
per la manipolazione dei dati. Per estrema semplicità dirò semplicemente
che DML si riferisce ai comandi SQL INSERT, UPDATE, DELETE.
Questo tipo di operazioni sulle tabelle del database non modifica in alcun modo la struttura dello stesso, ma si occupa semplicemente di andare ad aggiungere, modificare, eliminare dati dalla tabella.
Per eseguire i comandi abbiamo ancora bisogno di un cursore. Vediamo l’esempio:
Inserimento di un valore
pycur = pydb.cursor()
for n in range(1,11):
sql = 'INSERT INTO tbl_seven(number) VALUES (' + str(n*7) + ')'
pycur.execute(sql)
# per applicare l'aggiornamento dei dati, va fatto il commit sul DB
pydb.commit()
QL execution
QL (Query Language) è quella parte di codice SQL che serve per
l’interrogazione dei dati. Per estrema semplicità dirò semplicemente che
QL si riferisce al comando SQL SELECT.
Questo tipo di operazioni sulle tabelle del database genera come risultato una tabella “provvisoria” contenente il risultato dell’interrogazione.
Neanche a dirlo, per eseguire i comandi abbiamo ancora bisogno di un cursore. Vediamo l’esempio:
pycur = pydb.cursor()
sql = "SELECT number FROM tbl_seven WHERE number % 2 = 0"
pycur.execute(sql)
results = pycur.fetchall()
for r in results:
print(r)
Tutto qua!
Rimane da provare e sperimentare un po’…
Python & Arduino
Per sviluppare applicazioni in Python con interazione con un dispositivo Arduino servono sostanzialmente 4 cose:
- Python installato sul Computer :)
- La libreria pyserial, per il collegamento con Arduino tramite seriale
- Un dispositivo Arduino (dovrebbe funzionare con tutti i tipi)
- L’ambiente di sviluppo per Arduino installato sul Computer
Python & PySerial
Il modulo necessario per le implementazioni che vedremo si chiama “PySerial”. Per l’installazione dello stesso basta utilizzare pip, con il comando indicato sotto
$ pip install pyserial
oppure dalla linea di comando, invocando l’interprete Python
$ python -m pip install pyserial
I comandi elencati funzionano sicuramente su Linux e Mac. Su Windows dipende da come è stata fatta l’installazione di Python e in particolare se l’eseguibile Python è stato inserito nel PATH.
Se così non fosse bisogna spostarsi con la console della riga di comando fino alla cartella di installazione di Python ed eseguire il comando sopra da lì.
Ad esempio se l’installazione è stata fatta su “C:\Python”, allora il comando va eseguito in quella cartella.
Per verificare che l’installazione del modulo è andata a buon fine basta aprire l’interprete Python e provare a importare il modulo:
>>> import serial
Se Python non si lamenta di nulla, siamo pronti per il prossimo step :)
Ambiente Arduino
Il MicroProcessore libero Arduino si può acquistare a poco prezzo in un negozio di prodotti informatici: una scheda singola costa fra i 10 e i 30 euro, mentre uno starter kit, che comprende una breadboard e alcuni sensori semplici per le prime prove, si aggira intorno ai 40-70 euro.
Se economicamente possibile, uno starter kit è la scelta migliore. Vi consiglio di cercare il prezzo migliore controllando i vari negozi online di articoli informatici.
Il sito di riferimento ufficiale è https://www.arduino.cc. Lì si trovano riferimenti per l’acquisto e per scaricare il software di sviluppo, che si chiama Arduino IDE.
Non dovrebbe essere incredibilmente difficile riuscire nell’impresa di acquistare un arduino e installare il suo IDE.
Collegamento fra i dispositivi
I due esempi che seguono servono per verificare che sia possibile instaurare una comunicazione nei due sensi: da Arduino al dispositivo che esegue lo script Python e viceversa. I due esempi sono i più semplici che sia possibile: nonostante ciò, richiedono comunque la comprensione completa sia dei circuiti fisici che si vanno a implementare con Arduino, sia dei collegamenti che si instaurano fra i vari dispositivi.
Da Arduino a Python
L’esempio da implementare è di una semplicità estrema: Arduino ogni TOT secondi invierà un testo semplice tramite la porta seriale e Python dovrà ogni volta individuare il testo inviarlo e trascriverlo sullo schermo. Vediamo il codice:
Su Arduino:
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.print("Funziona!!!");
delay(2000);
}
Tramite l’ambiente di sviluppo, creare un nuovo progetto, copiare il codice sovrastante in esso e “passarlo” al proprio Arduino.
Sul Computer con Python:
import serial
import time
# Va individuata la porta seriale abbinata ad Arduino
# Aiutatevi con Arduino IDE
# Su Win, dovrebbe essere una "COMx" (con x che può essere 1,2,3,4)
# Su Mac/Linux, dovrebbe essere "/dev/ttyACMx" (con x che va da 0 a...)
portaSeriale = "boh"
ad = serial.Serial(portaSeriale, 9600)
while True:
print( ad.realine() )
time.sleep(2)
Eseguite questo pezzo di codice e “dovreste” vedere, ogni 2 secondi, la scritta “Funziona!!!” apparire…
Da Python ad Arduino
Per il secondo esempio le cose sono leggermente più complicate. L’idea è quella di inviare una informazione tramite Python e fare in modo che Arduino se ne accorga. Come facciamo però NOI ad essere sicuri che Arduino se ne sia accorto???

La cosa più semplice è fargli fare qualcosa, ad esempio… accendere un led! Per fare ciò, occorre implementare il seguente circuito con Arduino, una breadboard, un led e una resistenza.
Implementato il circuito come in figura, andremo a caricare sulla scheda il seguente codice per Arduino:
Codice per Arduino
#define LED 13
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
if (Serial.available()) {
char c = Serial.read();
if (c == \'S\')
digitalWrite(LED, HIGH);
if (c == \'N\')
digitalWrite(LED, LOW);
}
}
Il codice è molto semplice da capire: il LED è collegato al pin 13, che viene impostato in OUTPUT, ovvero i dati usciranno dalla scheda Arduino al LED. Arduino è in ascolto sulla porta seriale e legge un carattere per volta: se il carattere è ‘S’ imposta come acceso il LED; se il carattere è ‘N’ lo imposta come spento.
Analogamente Python deve essere in grado di inviare i due caratteri ad Arduino.
Codice Python
import serial
import time
portaSeriale = "boh" # VEDI SOPRA
arduino = serial.Serial(portaSeriale,9600)
time.sleep(2)
carattere = 'A'
while carattere != 'C':
carattere = input("S accende il LED, N lo spegne, C stop")
c = carattere.encode('latin_1')
arduino.write(c)
time.sleep(2)
arduino.close()
Tutto qua!
Rimane da provare e sperimentare un po’…
Python e i numeri reali
Python sembra fare parecchi errori per calcolare i numeri con la virgola… non sembra preciso… guardate questo esempio:
1.1 + 2.2 == 3.3
False (ma come False???)
Ho capito: Python è scemo!
La dispensa è finita :)
No… il problema non è Python, ma la rappresentazione dei numeri reali IEEE754 utilizzata dai nostri dispositivi per memorizzare un numero reale.
Forse in prima l’abbiamo accennata… difficilmente l’abbiamo studiata… vi ricorderò 2 semplici cose:
- i computer salvano i dati nelle memorie come sequenze binarie
- le memorie hanno il brutto difetto di avere uno spazio limitato, “finito”
Ora… per capire con un semplice esempio il problema, facciamo un ragionamento con i numeri decimali.
Immagina di voler memorizzare il valore della frazione 1/4 (un quarto):
viene 0.25 (facile, finito!!!)
Immagina adesso di voler memorizzare il valore della frazione 1/3
viene 0.3333333333333333 (facile, ma infinito… un problemino in un posto finito)
Certo… tu a occhio hai capito che basta mettere infiniti 3 lì
dietro… ma per quanto ti impegni quella rappresentazione non sarà mai
esattamente 1/3 !!!
A volte, nella traduzione binaria di un numero reale in decimale si creano situazioni analoghe: numeri che in decimale hanno una rappresentazione semplice hanno rappresentazione infinita in binario. Di cui noi (ulteriore problema) possiamo memorizzarne solo un pezzo!
Ecco spiegato l’arcano.
Se vogliamo rappresentazioni precise “all’infinito” (per i numeri reali razionali) possiamo usare alcuni moduli Python adatti allo scopo:
Segui i link indicati e continua tu 😉
Interi, bytes, stringhe
In questa sezione vorrei regalare alcune piccole delucidazioni su come funzionano gli interi in Python e come si confrontano con i bytes e la loro rappresentazione sotto forma di stringhe.
Questo approfondimento è necessario soprattutto quando si lavora con dati in rete, in cui il formato privilegiato per la trasmissione è chiaramente
quello in bytes.
Ma andiamo con ordine.
Interi, base 2, 8, 10, 16
Le basi numeriche più comuni sono chiaramente (in rigoroso ordine crescente) le basi 2, 8, 10, 16. A dire la verità, la base più comune è la base 10 (e questo perché abbiamo 10 dita…). Le altre sono diventate comuni soprattutto grazie all’informatica.
Vediamo come rappresentare cifre nelle diverse basi:
interoBase10 = 27 # come ovvio... è 27 in base 10. 27...
interoBase2 = 0b1101 # 1101 in base 2. Ovvero 13 in base 10.
interoBase8 = 0o13 # 13 in base 8. Ovvero 11 in base 10.
interoBase16 = 0xAF # AF in base 16. Ovvero 175 in base 10.
Come si vede Python fornisce dei prefissi semplici per indicare un numero nelle basi comuni. Facile!
Warning
Quando andate a visualizzare questi numeri, ad esempio con una print, vedrete sempre e comunque la loro rappresentazione decimale:
print(interoBase10) # scrive 27 print(interoBase2) # scrive 13 print(interoBase8) # scrive 11 print(interoBase16) # scrive 175
Se invece avete un numero decimale e lo volete rappresentare in binario, ottale, esadecimale, Python fornisce delle comodissime funzioni predefinite. Occhio che per cristallizzare la rappresentazione dei numeri in quella base, Python in realtà crea delle stringhe!
Vediamo il codice:
a = 67
b = bin(a) # b è la stringa '0b1000011'
c = oct(a) # c è la stirnga '0o103'
d = hex(a) # d è la stringa '0x43'
Se invece abbiamo delle stringhe che rappresentano dei numeri in base 2,8,10,16, lo strumento da utilizzare è la funzione int() con accortezza!
Infatti!
s1 = '89'
n1 = int(s1) # Vale l'intero 89. Questo lo sapevamo già...
s2 = '0b101'
n2 = int(s2) # ERRORE!!! Non sa come convertire la b...
n2 = int(s2, base = 2) # Vale l'intero 5, la conversione di 101(2) in decimale
s3 = '0o23'
n3 = int(s3) # ERRORE!!! Non sa come convertire la o...
n3 = int(s3, base = 8) # Vale l'intero 19, la conversione di 23(8) in decimale
s4 = '0x2E'
n4 = int(s4) # ERRORE!!! Non sa come convertire la x...
n4 = int(s4, base = 16) # Vale l'intero 47, la conversione di 2E(16) in decimale
Stringhe e codifiche
In Python il tipo stringa (str) codifica i dati in UNICODE, utilizzando il sistema di codifica UTF-8.
Tip
Codifca dei caratteri (più in breve possibile)
All’inizio c’era solo
ASCII, in cui c’era una corrispondenza 1 a 1 fra un byte e un carattere della lingua americana. Poiché con un byte puoi distinguere massimo 256 caratteri diversi,ASCIInon è sufficiente per tutte le lingue (con tutti i loro simboli) del mondo. Quindi ognuno ha pensato al proprio standard di rappresentazione dei caratteri.Un bordello!
Entra in scena
UNICODE. Un unico codice per tutti i caratteri del mondo. Per farceli stare tutti, codifica ogni carattere in due byte, per un totale di 65.536 caratteri diversi! Abbastanza…
Il problema con questa codifica è che tutto diventa grosso il doppio!!! Una parola di 4 lettere (ad esempio: “ciao”) > diventa pesante 8 byte…Bestemmiano tutti!
L’idea di usare un unico codice per TUTTI i caratteri è una figata… la teniamo! Pensiamo una codifica per risparmiare spazio: nascono le codifiche a lunghezza variabile. La migliore sembra essere
UTF-8(lo standard de facto attuale). In UTF-8 un carattere può occupare 1,2,3,4 bytes, > ma nel 98% dei casi la rappresentazione delle stringhe è più corta di quella a lunghezza fissa a 2 bytes!!Il bello è che contiene anche tutte le faccine 😉
Con le stringhe in Python dunque, mentre osservandola (o usando la funzione len) puoi sapere con certezza da quanti caratteri questa è formata, a meno di non conoscere a fondo UTF-8,
non possiamo sapere ad occhio quanti bytes occupa in memoria!
Se però sei veramente determinato a saperlo… puoi usare la funzione predefinita ord(). Questa, dato un carattere, ritorna il valore esadecimale che corrisponde alla sua rappresentazione in UTF-8. Prima di fare qualche esempio, fatemi dire solo che esiste anche la funzione che fa l’inverso, ovvero dato un intero ritorna il carattere corrispondente
in UTF-8 (o in ASCII. Ve l’ho detto che UTF-8 è stato progettato per essere identico ad ASCII nei primi 127 caratteri???).
print( ord('a') ) # scrive 97. In UTF-8, come in ASCII
print( chr(97) ) # scrive 'a'
i = 8364
print( chr(i) ) # simbolo dell'Euro €
bytes e bytearray
Per gestire i bytes originati da una conversione (ad esempio di una stringa) o da una trasmissione di rete, Python 3 mette a disposizione 2 tipi di dati:
- la classe
bytes, immutabile, paragonabile ad una tupla di byte. - la classe
bytearray, mutabile, paragonabile ad una lista di byte.
Sostanzialmente, i dati in una di queste classi possono arrivare in pochi modi:
- tramite la conversione di una stringa (encoding)
- come prodotto di una trasmissione di rete
- generando una nuova sequenza tramite le funzione predefinite
bytes()ebytearray()
Partiamo dall’ultima opzione! Possiamo usare entrambe le funzioni in due modi analoghi: l’unica differenza fra loro è che una crea un oggetto di tipo bytes e l’altra
un oggetto di tipo bytearray.
Vediamo il codice:
# bytes(size:int) oppure bytearray(size:int). Se passi un intero, crea una sequenza di "size" bytes tutti a zero!
b = bytes(3) # b vale b'\x00\x00\x00' , una sequenza IMMUTABILE di 3 bytes (8x3 = 24 bit) tutti a zero.
ba = bytearray(3) # ba vale bytearray(b'\x00\x00\x00') , una sequenza MUTABILE di 3 bytes tutti a zero.
# bytes(iterable) oppure bytearray(iterable). Prende la sequenza di dati e crea una sequenza di byte con i dati della sequenza.
tupla = (1, 2, 3)
b1 = bytes(tupla) # b1 vale b'\x01\x02\x03', una sequenza immutabile, riempita con i byte ricavati dalla conversione dei dati della tupla
ba1 = bytearray(tupla) # ba1 vale bytearray(b'\x01\x02\x03'), una sequenza mutabile, riempita con i byte ricavati dalla conversione dei dati della tupla
String encoding
Il passaggio da stringa a bytes è davvero semplice, considerando che le stringhe utilizzano la codifica UTF-8.
Per passare da stringa a bytes, usiamo la funzione str.encode(self, encoding='utf-8'):
s = "Andrea"
b = s.encode() # b è un bytes che vale b'Andrea'
Se invece hai una sequenza di bytes e vuoi ottenere una stringa, devi utilizzare la funzione bytes.decode(self, encoding='utf-8').
b = b'Andrea'
s = b.decode() # s è una stringa che vale 'Andrea'
Int conversion
Il passaggio da interi a bytes è analogo a quello delle stringhe, ma con una piccola difficoltà in più: intuire quanti bytes saranno necessari per stanziare la conversione dell’intero in questione non è banale! Nel dubbio… esagerate un pò!!
Le funzioni da utilizzare sono queste:
- da int a bytes: funzione
int.to_bytes(len:int)occorre specificare quanti bytes occuperà il numero convertito. Se ne metti troppo pochi da errore!!! Se ne metti troppi… sprechi memoria. - da bytes a int: funzione
int.from_bytes(b): una semplice conversione 1:1 del bytes in intero.
Vediamo due esempi
n = 13
b = n.to.bytes(3) # b è un bytes che vale b'\x00\x00\r'
# adesso, per riconvertirlo in int:
a = int.from_bytes(b) # a è un intero che vale 13
asyncio
Prima c’erano Python threading e Python multiprocessing… o librerie di terze parti come Twisted e Tornado… adesso
si utlizza asyncio.
La libreria asyncio è predefinita in Python sin dalla versione 3.7!!! Se volete utilizzarla (e in molti casi… fate molto bene!!)
dovete solo aver cura di avere una versione recente di Python installata.
Note
Documentazione ufficiale
La documentazione ufficiale di
asyncioè disponibile nella documentazione ufficiale di Python. Link veloce per i più pigri: Clicca qui!.
Questo piccolo tutorial serve solo come introduzione al discorso…
asyncio è un framework asincrono di I/O che permette di scrivere codice concorrente tramite la sintassi await e async.
Si basa sui concetti di event loop e di coroutines per la gestione delle operazioni asincrone.
Vediamoli:
Event Loop
L’event loop (letteralmente, il ciclo degli eventi) è il concetto alla base del modello di esecuzione di asyncio. Quando viene attivato, esso si occupa di pianificare e gestire le operazioni di I/O e le attività (asincrone) definite nelle coroutines e di alternarsi con il sistema operativo nel controllo dell’hardware: in questo modo si mantiene il sistema fluido (cioè in esecuzione di operazioni non bloccanti).
Coroutines
Le coroutine(s) non sono altro che funzioni asincrone, ovvero definite con la sintassi async def. La loro caratteristica principale, rispetto
alle normali funzioni è che hanno la facoltà di restituire il controllo all’event loop tramite l’utilizzo della keyword await, permettendo ad altre
operazioni di essere eseguite nel frattempo.
import asyncio
async def saluta(nome, delay):
""" Aspetta {delay} secondi (asincrono) e poi saluta {nome}"""
await asyncio.sleep(delay)
print(f"Ciao, {nome}!")
async def main():
""" esegue TOT operazioni 'contemporaneamente'!!! """
# asyncio.gather permette di eseguire 'insieme' un gruppo di operazioni asincrone
await asyncio.gather(
saluta("Andrea", 2),
saluta("Barbara", 1),
saluta("Cristina", 3),
)
if __name__ == "__main__":
# codice per avviare l'event loop
asyncio.run(main())
Task(s)
Un task asyncio è una coroutine in programma per l’esecuzione e gestita in maniera indipendente.
import asyncio
async def operazioneLunga(n):
print(f"Inizio operazione {n}")
await asyncio.sleep(n)
print(f"Fine operazione {n}")
async def main():
# asyncio.create_task... fa quello che dice!
task1 = asyncio.create_task(operazioneLunga(2))
task2 = asyncio.create_task(operazioneLunga(4))
# aspetta il completamento dei task
await task1
await task2
if __name__ == "__main__":
asyncio.run(main())
Gestione errori e Timeout
Con tutta la buona volontà di portare avanti e terminare più operazioni possibili contemporaneamente, è praticamente certo che prima o poi qualcosa andrà storto… bisogna rendere il codice più robusto possibile, ovvero in grado di gestire al meglio ogni eventuale problematica incontrata.
Le soluzioni offerte da Python sono tipicamente due:
- la gestione delle eccezioni tramite il codice
try... except: questa vale sempre!!! - l’impostazione di un timeout per le coroutine in esecuzione (oltre il quale non possono andare): questa vale solo per asyncio!!!
Per gestire ogni problema al meglio bisognerebbe considerare entrambi gli approcci. Vediamo un esempio di codice semplice e ben commentato!
import asyncio
async def fai_in_fretta():
""" aspetta 5 secondi e via! """
try:
await asyncio.sleep(5)
print("Fatto!")
except asyncio.CancelledError:
print("Operazione cancellata!!!")
async def main():
task = asyncio.create_task(fai_in_fretta())
try:
# concede al massimo 2 secondi di tempo per l'esecuzione del task
await asyncio.wait_for(task, timeout=2)
except asyncio.TimeoutError:
print("Time OUT")
task.cancel()
await task
if __name__ == "__main__":
asyncio.run(main())
Asyncio Networking
La libreria asyncio rende molto semplice lavorare con le operazioni I/O di rete. L’esempio sotto presenta un echo server
(una applicazione che ripete tutto quello che dici) che utilizza le classi asyncio StreamReader e StreamWriter
import asyncio
async def echo(reader, writer):
while True:
data = await reader.read(100)
if not data:
break
writer.write(data)
await writer.drain()
writer.close()
await writer.wait_closed()
async def main():
server = await asyncio.start_server(echo, "127.0.0.1", 8888)
async with server:
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(main())
Tip
Debugging
Per fare il debug di una applicazione basata su asyncio, ti basta inserire la riga
asyncio.set_debug(True)prima di eseguire l’event loop.
Fornirà all’applicazione la possibilità di essere controllata al meglio!!!
3rd party libraries
Vista la grande qualità per cui asyncio si è subita distinta, sono immediatamente state implementate librerie specifiche, basate su di essa per i casi più disparati:
aiohttp, una libreria che implementa framework HTTP client/serveraiomysql, una libreria asincrona per interagire con il database MySQL e MariaDBAsyncSSH, una libreria asincorna per implementare un client o un server SSHaiodns, un semplice DNS resolver basato su asyncioaioping, una libreria asincrona per il protocollo ICMP (su cui si basa ping)asynctest, una libreria per i test su funzioni e librerie asincrone-
Molti (ma non tutti) sono genericamente indicati su questo strano sito: Awesome AsyncIO. Trovare esempi in rete di utilizzo di queste librerie diventa ogni giorno più facile.
Best Practices
- Usa
async defper definire una coroutine eawaitper metterla in pausa. - Usa
asyncio.gather()oasyncio.create_task()per eseguire coroutine in maniera concorrente. - Gestisci le eccezioni nei blocchi
try... except. - Usa
asyncio.wait_for()per impostare timeout alle coroutines. - Chiudi sempre le risorse, come i file o le connessioni di rete, quando non le usi più.
- Usa librerie di terze parti basate su asyncio per le migliori performance su compiti specifici.
- Usa il debug mode di asyncio per controllare il codice.