Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Corso Python

Le dispense per le lezioni di programmazione del prof



Python: le basi!!!

In questo gruppo trovate le dispense di base sulla programmazione Python per iniziare il percorso di apprendimento

Python: OOP

La base di tutta la programmazione moderna: impariamo a programmare in Python secondo il paradigma della OOP!

wxPython GUI

Una semplice libreria Python, basata su wxWidgets per la programmazione grafica e l’implementazione di programmi basati su GUI

Approfondimenti

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)

Guido Von Rossum

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…


youtube video

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.

Esecuzione di un linguaggio compilato


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.

Esecuzione di un linguaggio interpretato


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.

Python: bytecode generation

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à!

image

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.

image

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…


youtube video

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:

image

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!!!

image

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

image

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

image


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.

Thonny mostra shell

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:

Thonny script “Hello, World!”

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!

Esempi da eseguire nell'interprete
>>> 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:

Hello World su Thonny!

Tutto qui!

Istruzione print

La prima istruzione che abbiamo incontrato è la classica print(). Serve per visualizzare i suoi argomenti!

Prove con l'istruzione print nell'interprete
>>> 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:

Semplici esempi di variabili dichiarate nell'interprete
>>> 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:

  1. scegliere un nome per la variabile (ad esempio: altezza)
  2. decidere un valore da assegnargli (ad esempio: 143)
  3. 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 rettangolo con 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 , VAR sono 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().

Prove con 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.

Esempi di assegnazione
>>> 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…

Uso errato dell'operatore di assegnazione
>>> 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).

Esempio con il nome
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:

TipoOggettoDescrizione
Numeri InteriintI numeri interi (secondo Python).
Il più grande (o più piccolo) intero rappresentabile dipende dalla quantità di memoria presente nella macchina.
Numeri RealifloatI numeri reali (secondo Python), tipicamente definiti a virgola mobile (floating point numbers)
Sono rappresentabili numeri da -1.797693134862316 e308 a 1.797693134862316 e308
Valori BooleaniboolI 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!

OperatoreDescrizioneEsempioRisultato
==È uguale a5 == 8False
!=È diverso da5 != 8True
>È maggiore di5 > 8False
<È minore di5 < 8True
>=È maggiore o uguale a5 >= 8False
<=È minore o uguale a5 <= 8True

Operatori Logici

Gli operatori logici in Python sono operatori binari il cui risultato è sempre un valore booleano!

OperatoreDescrizioneEsempioRisultato
andCongiunzione logicax = 6
y = 3
x < 10 and y > 1


True
orDisgiunzione logicax = 6
y = 3
x == 5 or y == 5


False
notNegazione logicax = 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:

SimboloDescrizione
\nCapo riga
\ttabulazione

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 f davanti 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:

SimboloDescrizione
`:nsLascia almeno n spazi per una stringa
:+Esplicita il segno
:,Usa la virgola come separatore delle migliaia
:bFormato binario del numero
:fFormato con parte intera e decimale (float)
:XFormato esadecimale del numero
:%Formato percentuale del numero
:0ndIntero (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.

FunzioneDescrizione
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.

File: areaRettangolo.py
# 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:

  1. sequenza, in cui le istruzioni sono eseguite una dopo l’altra
  2. alternanza, in cui alcune istruzioni sono eseguite alternativamente (o un gruppo o un altro)
  3. 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:

Definizione struttura di alternanza
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")
Esecuzione
Inserisci un numero intero: 7
POSITIVO
FINITO

Dal punto di vista funzionale, il codice scorre le istruzioni sopra scritte come nel diagramma seguente:


Diagramma Alternanza


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")
Esecuzione
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 :(")
Esecuzione
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:

Esempio di codice in linguaggio C, INDENTATO
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.

Esempio di codice in linguaggio C, NON INDENTATO
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.

Esempio di codice in linguaggio PASCAL, NON INDENTATO
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:

Esempio di codice in linguaggio Python (indentato per forza)
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):

  1. Istruzione for, che si utilizza per scorrere gli elementi di una sequenza
  2. 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 è:

Definizione ciclo for
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!!!

Scrivi 5 volte "ciao"
for x in range(5):
    print("ciao")
Disegna una piramide di "x"
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:

Esempio 1
# 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
Esempio 2
# due numeri, il primo è start, il secondo è stop
# step è predefinito a +1
for i in range(1, 5):
    print(i)
1
2
3
4
Esempio 3
# 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
Esempio 4
# 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:

Esempio 5
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 numeri fra 5 e 12
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 dei numeri dispari fra 15 e 30
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.

Definizione ciclo while
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:

  1. Abbinato ad un ciclo for, quando la sequenza da scorrere termina.
  2. Abbinato ad un ciclo while, quando la condizione diventa False

Vediamo un esempio con il ciclo for:

Verifica se un numero è primo o no
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")
Prima esecuzione, inserisco 15
Inserisci numero. Io ti dico se è primo o no: 15
15 non è primo, infatti 
15 = 3 * 5
Seconda esecuzione, inserisco 17
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.

sette e mezzo
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)
Prima esecuzione, vado sempre avanti
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
Seconda esecuzione, mi fermo dopo la prima carta
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.

Esercizio: inserimento di un numero intero fra 1 e 90 (per la tombola)
# 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:

  1. Chiedere all’utente di inserire un numero intero positivo N
  2. 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:

  1. arrivati qui avete già praticamente risolto l’esercizio
  2. 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:

Passaggio 1
# Dato N, visualizzo tutti i numeri fra 2 e N - 1
for div in range(2, N):
    print (div)
Passaggio 2
# Dato N, visualizzo quali numeri fra 2 e N - 1 dividono N
for div in range(2, N):
    if N % div == 0:
        print (div)
Passaggio 3
# 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
Passaggio 4 (finale)
# 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 = 720

Per 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!

Errore classico: divisione per zero
>>> 42 / 0
ZeroDivisionError: division by zero

Il programmatore può intercettare le eccezioni generate e gestirle in modo opportuno.

Vediamo il codice:

Struttura completa del codice per la gestione delle eccezioni
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/except semplice
try:
    n = int(input("Inserisci un numero intero: "))
    print("Il doppio è", n * 2)
except ValueError:
    print("Non hai inserito un numero intero valido!")
except multipli
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!")
else e finally
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")
ripetizione con gestione di eccezioni
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:

EccezioneQuando si verifica
ValueErrorvalore “giusto” come tipo ma sbagliato come contenuto (es. int(“ciao”))
ZeroDivisionErrordivisione o modulo per zero
TypeErroroperazione tra tipi incompatibili (es. “5” + 3)
NameErroruso di una variabile non definita
KeyboardInterruptl’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() solleva ValueError come int()


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:

  • except per input non numerico
  • else per stampare il risultato e la fascia (sotto/normo/sovrappeso) solo se il calcolo è andato a buon fine
  • finally per 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 FunzioneDescrizione
x in sTrue se un elemento di s è uguale a x, altrimenti False
x not in sFalse se un elemento di s è uguale a x, altrimenti True
s + tLa sequenza prodotta dalla concatenazione di s e t
s * n oppure n * sLa 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:

Operatore *in*
if "n" in "Python":
    print("Presente")
else:
    print("Assente")

# scrive "Presente"

Altro esempio:

Operatore *not in*
if "s" not in "Python":
    print("Assente")

# scrive "Assente"
Operatore +
nome = "Capo" + "Stazione"
print(nome)

# scrive "CapoStazione"

Altro esempio:

Operatore *
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"

Python come stringa

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'

Codice ASCII

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:

if elemento in sequenza
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:

for elemento in sequenza: scorre gli elementi
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)).

for pos in range(len(sequenza)): scorre le posizioni
    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!

Esempio: definire la tupla 'datiPersonali' con nome e cognome inseriti dall'utente
nome = input("Inserisci nome: ")
cognome = input("Inserisci cognome: ")
datiPersonali = ( nome , cognome )
Esempio: definire la tupla tabellina del numero scelto dall'utente
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)
Esempio: definire una tupla con un solo valore
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.

FunzioneDescrizione
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:

Esempio: metodo append()
# 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' , '!' ]
Esempio: metodo insert()
# 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' , '!' ]
Esempio: metodo pop()
# 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' ]
Esempio: metodo pop(i)
# 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' ]
Esempio: metodo remove()
# 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

Esempio: metodo sort()
# list.sort() ordina la lista stessa, senza ritornare nulla!!!
# lettere vale [ 's' , 'c' , 'o' ]
lettere.sort()
print(lettere)    # scrive [ 'c' , 'o' , 's' ]
Esempio: metodo reverse()
# list.reverse() rovescia l'ordine degli elementi della lista
# lettere vale [ 'c' , 'o' , 's' ]
lettere.reverse()
print(lettere)    # scrive [ 's' , 'o' , 'c' ]
Esempio: metodo clear()
# 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.

Esempio 1: senza copy le 2 variabili si riferiscono alla stessa lista
list1 = [1 , 2 , 3]
list2 = list1
list2[0] = 5
print(list1)
[ 5 , 2 , 3 ]
Esempio 2: usando il metodo copy() la seconda variabile vale solo inizialmente uguale alla prima.
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:

DITOMANOPIEDE
Primopollicealluce
Secondoindiceillice

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à:

  1. l’inserimento ordinato dei valori
  2. 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:

  1. In questo capitolo vedremo come si crea un modulo e come si utilizza.

  2. 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.

  3. 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:

Esempio 1: un unico file
# file pippo.py

# la variabile __name__ si riferisce al nome del modulo pippo
# essendo al suo interno, conterrà la parola "__main__"
print(__name__)
Esempio 2: un altro file
# 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:

  1. scrivo il modulo con la definizione delle funzioni che mi interessano
  2. faccio delle prove per assicurarmi che tutto… funzioni! (faccio i test!!!) in fondo al file, dentro un if __name__ == "__main__"
  3. Se eseguo direttamente il modulo con le funzioni, l’if mi permette di svolgere i test (e verificare che tutto funzioni correttamente)
  4. 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:

  1. i test devono essere scritti dentro la documentazione come se fossero eseguiti lì (lo vediamo fra un attimo)
  2. 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 comuni
  • time: Gestione Tempo
  • datetime: Gestione Data e Ora
  • pathlib: Accesso a file e directories
  • csv: 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…

Esempi di operazioni varie con math
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:

Variabili (pre)definite in math
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 tipo Date , 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…):

ParametroDescrizioneEsempio
%aGiorno della settimana (breve)Wed
%AGiorno della settimanaWednesday
%wGiorno della settimana come numero: 0 è Domenica3
%dGiorno del mese: 01-3131
%bNome del mese (breve)Dec
%BNome del meseDecember
%mMese come numero: 01-1212
%yAnno (breve, 2 cifre)18
%YAnno2018
%HOre: 00-2317
%MMinuti: 00-5941
%SSecondi: 00-5908
%%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.

Directory di lavoro corrente
import os

print(f"Directory corrente: {os.getcwd()}")
os.chdir("/nuova/directory/di/lavoro/corrente")
print(f"Nuova directory corrente: {os.getcwd()}")
Gestione cartelle
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
Eseguire comandi di sistema
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:

Esempi con il modulo shutil
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.

pypi site

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!

thonny package 1

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

thonny package 2

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

thonny package 3

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:

ruota l'immagine di 90 gradi e la visualizza
rotatedImg = img.rotate(90)
rotatedImg.show()
ridimensiona l'immagine a 200x200 pixel
resizedImg = img.resize((200,200))
resizedImg.show()
ribalta l'immagine orizzontalmente
# (oppure verticalmente, mettendo Image.FLIP_TOP_BOTTOM)
flippedImg = img.transpose(Image.FLIP_LEFT_RIGHT)
flippedImg.show()
converte l'immagine in scala di grigi
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)

Salva l'immagine su file
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

Aggiunge un bordo rosso di 20 pixel all'immagine
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à).

Rettangolo rosso semitrasparente 600x400
from PIL import Image

image = Image.new('RGBA', (600,400), (255,0,0,128) )
image.show()

Se si preferisce è possibile utilizzare la notazione esadecimale

Stesso colore, notazione esadecimale
image = Image.new('RGBA', (600,400), #FF000080 )

Immaginate di voler piazzare un testo al centro del rettangolo così creato

Testo con Draw
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.

Screenshot del Desktop
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:

grafico retta y=2x

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()

Grafico 2 Grafico 3

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

2 grafici nello stesso piano cartesiano
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()

Grafico 4

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:

Barre Verticali
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()

Grafico 5

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à:

ModoDescrizione
rLettura (read).
Il file NON sarà modificato da alcuna operazione. Se il file non esiste l’apertura fallisce.
Modo di default.
wScrittura (write).
Un file esistente verrà sovrascritto, altrimenti ne verrà creato uno nuovo.
xCreazione file (non lo so perché x…).
Ritorna un errore se il file esiste già. Se funziona, apre il file in scrittura.
aAggiunta 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:

ModoDescrizione
tModalità testuale.
In questa modalità i dati vengono gestiti byte a byte.
Modo di default.
bModalità 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:

Esempio 1: creazione di un file con testo inserito all’interno
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:

Esempio 2: lettura del file 'ciao.txt' creato sopra
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

Esempio 3: aggiunta a fine file
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:

Esempio 4: lettura delle righe con ciclo for
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 print manda a capo a fine riga, risultando un doppio a capo

La corretta esecuzione di questo codice necessita di una pulizia della riga tramite la funzione strip().

Esempio 5: lettura delle righe di un file senza a capo finale
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:

CaratteristicaDescrizioneEsempio
file.nameContiene il nome del file“file.txt”
file.modeContiene il modo di apertura del file“w”
file.closedIndica se il file è chiuso oppure noFalse

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 percorso C:\Users e 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 documenti
  • Downloads: la cartella dove vanno a finire i download
  • Desktop: 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 chiamano root (radice).

Le cartelle degli utenti si trovano nella cartella /home e 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

Esempio 1: verificare se nella Home è presente un file chiamato 'pippo'
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")

Esempio 2: elenco dei file *.txt presenti nel Desktop dell’utente 'pippo'
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.


Esempio 3: elenco del contenuto della home con suggerimento a fianco
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)")    

Esempio 4: creazione, cambio nome ed eliminazione di una cartella nella home utente
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()

Esempio 5: creazione del file 'pippo.txt' nel Desktop
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.

MetodoDescrizione
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' oppure F’)

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)
  1. Visualizzare i nomi di tutte le femmine.
  2. Contare il numero di minorenni più alti di 180 cm e visualizzare i loro nomi.
  3. 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:

Stringa JSON ben indentata
{
  "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:

FunzioneCosa fa
json.dumps(diz)dict Python → stringa JSON
json.loads(string)Stringa JSON → dict Python

Vediamo alcuni esempi per capire meglio:

json.dumps()
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}
Opzioni utili di dumps()
# 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 PythonTipi JSON
dictobject {}
list, tuplearray []
strstring ""
int, floatnumber
True / Falsetrue / false
Nonenull
Da JSON a Python: json.loads()
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!

Salvataggio su file
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 JSON
import 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:

  1. Legge il file log.json (se esiste); se non esiste, parte da una lista vuota.
  2. Aggiunge un nuovo record con {"lettura": N, "valore": <numero casuale tra 20 e 30>} dove N è il numero progressivo.
  3. 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 “MonumentiRegione.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:

NomeRuoloSquadra
MaradonaCentrocampistaNapoli
GullitAttaccanteMilan
BrehmeDifensoreInter
FalcaoCentrocampistaRoma

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:

Esempio 0: il file 'giocatori.csv'
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.

Esempio 1: lettura del file CSV e caricamento dati in una LISTA di DIZIONARI
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.

Esempio 2: salvataggio su file dei dati presenti in una lista di dizionar
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 attributi di un oggetto, sono quegli elementi utili a descriverne le proprietà e lo stato. Sono solitamente individuate tramite dei sostantivi.

I comportamenti, o metodi di 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.

Definizione della classe Cerchio

# 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:

  1. In quale momento particolare questa funzione speciale viene eseguita automaticamente?
  2. 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.

Classe Quadrato

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 Python
  • protected, 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:

Esempio di classe in linguaggio C++
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 , setter e… 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

self vs cls

  • selfmetodo di istanza: riceve l’oggetto su cui è chiamato. Può accedere agli attributi di istanza e di classe.
  • clsmetodo 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é selfcls: è 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_conti che conta i conti creati;
  • una variabile di classe totale_depositi che tiene il totale di tutti i versamenti eseguiti su tutti i conti (si aggiorna ogni volta che un deposito va a buon fine);
  • un @classmethod report(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 @staticmethod lancia_dado(facce) che ritorna un numero casuale fra 1 e facce;
  • un metodo normale lancia(self) che usa il metodo statico per lanciare il dado dell’istanza;
  • un @classmethod dado_percentuale(cls) che crea e ritorna un dado da 100 facce;
  • un @classmethod dado_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 @classmethod da_secondi(cls, secondi_totali) che costruisce un Orario a 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.

Implementazione banale (NON va bene)
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():

Implementazione corretta
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 nome e specie (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 nome e ore_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 come tipo() ritorna "Bicicletta";
  • il metodo astratto velocita_massima() in Veicolo, che ritorna la velocità massima in km/h;
  • il metodo concreto tempo_viaggio(km) in Veicolo, che ritorna il tempo minimo stimato in ore per percorrere km chilometri 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 è una Persona → ha senso usare l’ereditarietà: Studente deriva da Persona.
  • Un Cerchio è una Forma → ha senso usare l’ereditarietà: Cerchio deriva da Forma.
  • Un’Automobile ha un Motore → ha senso usare la composizione: Automobile contiene un oggetto Motore.
  • Una Rubrica ha una lista di Contatti → ha senso usare la composizione: Rubrica contiene oggetti Contatto.

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’oggetto Libro con quel titolo, o None se non trovato;
  • libriDiPiu(n): ritorna la lista dei libri con più di n pagine.

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 scrivereImplementa nella classe la funzione:che ritorna
printprint ( a )def __str__(self)una stringa
Addizionea + bdef __add__(self,other)un oggetto della classe
Sottrazionea - bdef __sub__(self,other)un oggetto della classe
Moltiplicazionea * bdef __mul__(self,other)un oggetto della classe
Divisionea / bdef __truediv__(self,other)un oggetto della classe
Divisione Interaa // bdef __floordiv__(self,other)un oggetto della classe
Potenzaa ** bdef __pow__(self,other)un oggetto della classe
Moduloa % bdef __mod__(self,other)un oggetto della classe
Minorea < bdef __lt__(self,other)un bool (True o False)
Minore o ugualea <= bdef __le__(self,other)un bool (True o False)
Ugualea == bdef __eq__(self,other)un bool (True o False)
Diversoa != bdef __ne__(self,other)un bool (True o False)
Maggiorea > bdef __gt__(self,other)un bool (True o False)
Maggiore o ugualea >= bdef __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 sort vi 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:

image

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

image

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!

image

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!

Eseguire una funzione al click di un pulsante
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 classe
  • evt, che rappresenta l’evento che le ha scatenate.

Provate a sostituire la funzione self.Close() con altre funzioni tipo:

  • self.Maximize(): massimizza la finestra
  • self.Iconize(): riduce a icona la finestra
  • self.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…

  1. L’oggetto che rappresenta un pulsante si chiama wx.Button.
  2. L’evento predefinito per un pulsante è il click su di esso: questo evento, per un pulsante, si chiama wx.EVT_BUTTON.
  3. Probabilmente (ed è proprio così) se una widget si chiamasse wx.Pippo, il suo evento predefinito si chiamerebbe wx.EVT_PIPPO
  4. 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:

Identificare la widget che ha generato un evento
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.

Esempio con funzione GetId
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 funzione Close().

Infatti: funzione Close() —> evento wx.EVT_CLOSE —> bind alla funzione chiudi —> funzione Close() —> 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.Panel e 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…

image

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.Panel in 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 parent il Frame (tipicamente self)
  • Tutte le widget inserite nella finestra, che giacciono sopra il Panel, hanno il panel stesso come parent

Tutti gli esempi da qui in poi sono strutturati in questo modo: vi basterà osservarli un minimo :)

Finestre CoN e SENZA pannello


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.

image

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.

image

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.

image

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.

image

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:

StileDescrizione
(default)Linea singola di testo, digitabile dall’utente
wx.TE_MULTILINEPermette alla TextCtrl di utilizzare più righe
wx.TE_PASSWORDOgni carattere viene visualizzato con un asterisco
wx.TE_READONLYTesto non modificabile dall’utente
wx.TE_PROCESS_ENTERGestisce 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 widget
  • wx.EVT_TEXT_ENTER: viene scatenato quando hai impostato lo stile wx.TE_PROCESS_ENTER e 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.

image

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

image

La ComboBox supporta i seguenti stili di lavoro:

StileDescrizione
(default)Linea singola di testo editabile + menù a tendina
wx.CB_READONLYLinea di testo NON editabile.
wx.CB_SORTOrdina automaticamente le voci
wx.TE_PROCESS_ENTERGestisce 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.

image

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.

image

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.

image

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.

image

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.

image

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.

image

Questa widget supporta 2 eventi considerabili di default:

  • wx.EVT_LISTBOX: scatenato quando si seleziona un elemento della lista
  • wx.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:

image

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.

image

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:

SizerDescrizione
wx.BoxSizerContenitore per una riga (o una colonna) di widget
wx.StaticBoxSizerCome il Box Sizer ma con un contorno evidenziato (una StaticBox)
- - - - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
wx.GridSizerContenitore a tabella per le widget
wx.FlexGridSizerContenitore a tabella per le widget con dimensioni variabili
wx.GridBagSizerContenitore a tabella con spanning

Prima di iniziare a preoccuparvi, sappiate che i contenitori sono solo di 2 tipi:

  1. Contenitori a fila, come il Sizer wx.BoxSizer che può essere impostato in senso orizzontale oppure verticale o come wx.StaticBoxSizer, che funziona esattamente come il precedente con la differenza di avere un contorno evidenziato intorno: un abbellimento e nulla più.
  2. 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à
  • 1 significa che la widget si ridimensionerà nella direzione del layout proporzionalmente al suo contenitore
  • 2 significa 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 espansioneLayout di utilizzoWidget
wx.EXPANDTutti i layoutespansa 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 allineamentoLayout di utilizzoWidget
wx.ALIGN_TOPlinea in orizzontale, grigliaincolonnata in alto
wx.ALIGN_BOTTOMlinea in orizzontale, grigliaincolonnata in basso
wx.ALIGN_CENTER_VERTICALlinea in orizzontale, grigliaallineamento verticale al centro
wx.ALIGN_LEFTlinea in verticale, grigliaallineata a sinistra
wx.ALIGN_RIGHTlinea in verticale, grigliaallineata a destra
wx.ALIGN_CENTER_HORIZONTALlinea in verticale, grigliaallineamento orizzontale al centro
wx.ALIGN_CENTERgrigliaallineamento centrato
orizzontalmente e verticalmente

I flag di bordo possono decidere la direzione del bordo fra una widget e l’altra. Essi sono:

Flag di bordoLayout di utilizzoWidget
wx.TOPTutti i layoutbordo verso l’alto
wx.RIGHTTutti i layoutbordo verso destra
wx.BOTTOMTutti i layoutbordo verso il basso
wx.LEFTTutti i layoutbordo verso sinistra
wx.ALLTutti i layoutbordo 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:

Non funziona nulla se non
# ...
# 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.

Layout Orizzontale
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:

image

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 0 il valore di proportion di uno dei due pulsanti
  • metti a 2 il valore di proportion di uno dei due pulsanti
  • aggiungi wx.EXPAND ai flag dei pulsanti
  • modifica il valore del bordo per uno dei pulsanti.
  • togli wx.ALL dai flag e metti qualcuno degli altri flag disponibili

Fatte 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.

image

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:

  1. scrivete sempre tutti i parametri nella funzione Add: sizer.Add(widget, proportion = 0, flag = wx.ALL, border = 5)
  2. partiamo da una situazione semplice: proporzione ZERO, e bordo 5 da tutti i lati.
  3. 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)

image


Esercizio 2202 (line)

image


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 della BoxSizer. Perchè? Beh… perché derivano entrambe dalla stessa classe (la classe wx.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 GridSizer tramite la funzione Add in 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:

GridSizer
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:

image

Warning

Anche qui, prima di andare avanti, copia e incolla il primo esempio e fai i seguenti tentativi:

  • metti proportion = 1 sui 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.

image

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!!!)

image

Esempio FlexGridSizer (notate l'utilizzo delle Growable...)
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:

image

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.

image

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:

Esempio GridBagSizer
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)

image


Esercizio 2252 (grid)

image


Esercizio 2253 (grid)

image


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)

image

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)

image

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)

image

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):

  1. il pulsante CANCELLA disabilitato all’inizio e abilitato solo se c’è una voce selezionata nella ListBox.
  2. 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)

image

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)

image

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:

ValoreDescrizioneValore di ritorno
wx.OKPulsante OK. Combinabile con CANCELwx.ID_OK
wx.CANCELPulsante CANCEL. Combinabile con OK o YES_NOwx.ID_CANCEL
wx.YES_NOPulsanti Sì e No. Combinabile con CANCELwx.ID_YES oppure wx.ID_NO
wx.HELPMostra il pulsante AIUTOwx.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.

StileDescrizione
wx.ICON_EXCLAMATIONMostra un icona di allerta
wx.ICON_ERRORMostra una icona di errore
wx.ICON_HANDCome wx.ICON_ERROR
wx.ICON_INFORMATIONMostra una icona informativa
wx.ICON_QUESTIONMostra 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.

Esempio 1: SELEZIONA FILE DA APRIRE
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()
Esempio 2: SELEZIONA PERCORSO FILE SU CUI SALVARE
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:

  1. 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!!!

  2. Se vuoi visualizzare una icona da abbinare ad un pulsante, un menù o una toolbar… vai al capitolo Icone!!!

  3. 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!

Esempio 1: visualizzare una immagine
# ...
panel = wx.Panel(self)

self.widgetImageViewer = wx.StaticBitmap(panel)
oggettoImmagine = wx.Bitmap("foto.jpg")
self.widgetImageViewer.SetBitmap(oggettoImmagine)
# ...


Esempio 2: visualizzare una immagine in scala di grigi
# ...
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):

ElencoArt Provider IDDisponibili
wx.ART_LIST_VIEWwx.ART_NEWwx.ART_TICK_MARK
wx.ART_REPORT_VIEWwx.ART_NEW_DIRwx.ART_CROSS_MARK
wx.ART_FINDwx.ART_MINUSwx.ART_FOLDER
wx.ART_FIND_AND_REPLACEwx.ART_PLUSwx.ART_FOLDER_OPEN
wx.ART_CDROMwx.ART_EXECUTABLE_FILEwx.ART_FILE_OPEN
wx.ART_FLOPPYwx.ART_MISSING_IMAGEwx.ART_FILE_SAVE
wx.ART_HARDDISKwx.ART_NORMAL_FILEwx.ART_FILE_SAVE_AS
wx.ART_HELPwx.ART_ERRORwx.ART_UNDO
wx.ART_HELP_BOOKwx.ART_QUESTIONwx.ART_REDO
wx.ART_HELP_FOLDERwx.ART_WARNINGwx.ART_CUT
wx.ART_HELP_PAGEwx.ART_INFORMATIONwx.ART_COPY
wx.ART_HELP_SETTINGSwx.ART_TIPwx.ART_PASTE
wx.ART_HELP_SIDE_PANEL
wx.ART_GOTO_FIRSTwx.ART_PRINT
wx.ART_GOTO_LAST
wx.ART_GO_TO_PARENTwx.ART_ADD_BOOKMARK
wx.ART_GO_UPwx.ART_DEL_BOOKMARK
wx.ART_GO_DOWN
wx.ART_GO_BACKwx.ART_CLOSE
wx.ART_GO_FORWARDwx.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 IDDescrizione
wx.ART_TOOLBARIcone per le toolbar (media dimensione)
wx.ART_MENUIcone per i menu (piccole)
wx.ART_BUTTONicone per i pulsanti (grandine)
wx.ART_FRAME_ICONIcone per … Boh!
wx.ART_CMN_DIALOGIcone per le finestre di dialogo (medie)
wx.ART_HELP_BROWSERIcone per i messaggi di aiuto (grandine)
wx.ART_MESSAGE_BOXIcone per le message box (medie)
wx.ART_OTHERAltre 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:

Pulsante Indietro

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.

image

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…

Skeleton Full GUI
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:

ElencoACTION IDDisponibili
wx.ID_ABOUTwx.ID_EDITwx.ID_JUMP_TOwx.ID_PRINTwx.ID_STRIKETHROUGH
wx.ID_ADDwx.ID_EXECUTEwx.ID_JUSTIFY_CENTERwx.ID_PROPERTIES
wx.ID_APPLYwx.ID_EXITwx.ID_JUSTIFY_FILLwx.ID_TOP
wx.ID_JUSTIFY_LEFTwx.ID_REDO
wx.ID_BACKWARDwx.ID_FILEwx.ID_JUSTIFY_RIGHTwx.ID_REFRESHwx.ID_UNDELETE
wx.ID_BOLDwx.ID_FINDwx.ID_REMOVEwx.ID_UNDERLINE
wx.ID_BOTTOMwx.ID_FIRSTwx.ID_LASTwx.ID_REPLACEwx.ID_UNDO
wx.ID_FLOPPYwx.ID_REVERT_TO_SAVEDwx.ID_UNINDENT
wx.ID_CANCELwx.ID_FORWARDwx.ID_NETWORKwx.ID_UP
wx.ID_CDROMwx.ID_NEWwx.ID_SAVE
wx.ID_CLEARwx.ID_HARDDISKwx.ID_NOwx.ID_SAVEASwx.ID_YES
wx.ID_CLOSEwx.ID_HELPwx.ID_SELECTALL
wx.ID_CONVERTwx.ID_HOMEwx.ID_OKwx.ID_SELECT_COLORwx.ID_ZOOM_100
wx.ID_COPYwx.ID_OPENwx.ID_SELECT_FONTwx.ID_ZOOM_FIT
wx.ID_CUTwx.ID_INDENTwx.ID_SORT_ASCENDINGwx.ID_ZOOM_IN
wx.ID_INDEXwx.ID_PASTEwx.ID_SORT_DESCENDINGwx.ID_ZOOM_OUT
wx.ID_DELETEwx.ID_INFOwx.ID_PREFERENCESwx.ID_SPELL_CHECK
wx.ID_DOWNwx.ID_ITALICwx.ID_PREVIEWwx.ID_STOP

A breve vedremo anche come utilizzarle in maniera più che semplice!!!

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:

  1. lavoriamo nella funzione creaMenubar dello skeleton.
  2. prima creiamo un oggetto Menubar
  3. poi creiamo ogni menù che andremo ad aggiungervi (es: File, Modifica, Visualizza, etc…)
  4. aggiungiamo le azioni necessarie ai nostri menù
  5. aggiungiamo i menù creati alla menubar
  6. applichiamo la menubar alla nostra applicazione

Vediamo degli esempi di codice:

Esempi di inserimento voci di menù

# 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:


image


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 evento wx.EVT_MENU e 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.

image

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:

image

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:

image

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

MiniBrowser
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.

image

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.

image

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:

image

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:

image

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:

image

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

image

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:

image

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:

Comando powershell su Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Analoga cosa su MacOS, apri il terminale ed esegui:

Comando da terminale su MacOS e Linux
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):

TipoOpzioneBuild SystemStrutturaPunto di accessoDistribuzioneCasi d’uso
Applicazione–app (default)noflatmain.pynon intesaWeb apps, script
Libreria–libsrc layout(nessuno)PyPiLibrerie riutilizzabili
Pacchetto–packagesrc layoutconsole scriptapp installabili, PyPiCLI, 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:

  1. Crea automaticamente l’ambiente virtuale nella cartella .venv
  2. Installa il pacchetto requests e le sue dipendenze
  3. Aggiorna il file pyproject.toml con 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.lock al 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-version contiene semplicemente la versione di Python richiesta dal progetto, ad esempio:

3.12

uv legge automaticamente questo file e usa la versione corretta. Se la versione non è installata, uv la 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:

  1. Modificare il nome del pacchetto nel file pyproject.toml

  2. 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:

  1. prima gli import della libreria Python Standard (es: random, math, etc…), poi salta una riga

  2. poi gli import aggiunti tramite pip (es: pygame, platformdirs, etc…), poi salta una riga

  3. 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:

import(s)

# 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:

resources.py
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!

Programma di base
# 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 di ppp pixel in avanti (dritto al suo muso)

  • t.backward( ppp ): muove la tartaruga di ppp pixel all’indietro

  • t.left( ggg ): gira la tartaruga di ggg gradi verso sx (in senso antiorario)

  • t.right( ggg ): gira la tartaruga di ggg gradi verso dx (in senso orario)

  • t.setheading( dir ): orienta la tartaruga verso dir:

    • 0 –> destra , 90 –> alto , 180 –> sinistra , 270 –> basso

Vediamo l’utilizzo di alcune di queste per disegnare 2 quadrati col bordo colorato.

Quadrati Colorati
# 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 a color il colore di riempimento di una forma chiusa
  • t.begin_fill(): inizia a riempire una forma
  • t.end_fill(): smette di riempire una forma

La logica da applicare è molto semplice:

  1. Imposti il colore scelto con fillcolor
  2. lanci la funzione begin_fill
  3. disegni una forma chiusa
  4. lanci la funzione end_fill

Al termine vedrai una forma chiusa colorata. Guardiamo un esempio di codice che è sempre più facile!

Triangoli!!!
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 tartaruga
  • t.hideturtle(): nasconde la tartaruga
  • t.stamp(): stampa una copia della tartaruga (o della forma) nel punto ove essa si trova.

Disegna cerchi, punti, parole

  • t.circle ( ppp ): disegna un cerchio di diametro ppp pixel
  • t.dot ( ppp ,"color" ): disegna un punto (un cerchio pieno) di diametro ppp pixel e colore color
  • t.write ( "words" ): scrive le parole indicate nella posizione corrente

Muovi, velocizza, riporta a casa (al centro)

  • t.speed( vvv ): imposta la velocità della tartaruga
  • t.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!!!

bandiera italiana quadrati colorati casa con albero stella a 10 punte cerchi coi triangoli




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!

Primo programma
# 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 una PyGame 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!

Uscire con il tasto ESC
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.

Aggiungere scritte
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:

  1. reagendo (ad esempio, cambiando colore) quando ci passi sopra con il mouse
  2. 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!)

Aggiungere Pulsanti
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!!!!

Aggiungere forme geometriche
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.

Muovere un rettangolo
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:

  1. un evento che capiti ogni TOT ms che ci permetta, quando si scatena, di fare qualcosa (ad esempio: aggiungere un nemico)

  2. una lista che ci ricordi l’elenco (soprattutto… la posizione) di tutti i nemici presenti sul campo di gioco

  3. per ogni elemento nemico, il controllo della eventuale collisione con il giocatore

Osservate bene il codice qui sotto!

Nemici e Collisioni
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
Lavorare con le Immagini
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 :)

Aggiungere suoni
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???

Arduino

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, ASCII non è 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:

  1. tramite la conversione di una stringa (encoding)
  2. come prodotto di una trasmissione di rete
  3. generando una nuova sequenza tramite le funzione predefinite bytes() e bytearray()

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.

Esempio base: saluto asyncrono
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.

Creazione ed esecuzione di task asincroni
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!

Se non finisci entro 2 secondi, muori!
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

async(io) echo server
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/server
  • aiomysql, una libreria asincrona per interagire con il database MySQL e MariaDB
  • AsyncSSH, una libreria asincorna per implementare un client o un server SSH
  • aiodns, un semplice DNS resolver basato su asyncio
  • aioping, 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 def per definire una coroutine e await per metterla in pausa.
  • Usa asyncio.gather() o asyncio.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.