Articolo originale: Python Code Example Handbook – Sample Script Coding Tutorial for Beginners

Se stai imparando a programmare in Python, questo è l'articolo che fa per te. Troverai una completa descrizione della sintassi di Python e molti esempi di codice che ti guideranno durante questo percorso.

In quest'articolo parleremo di:

Sei pronto? Iniziamo!🔅

💡 Suggerimento: in tutto l'articolo, utilizzeremo <> per indicare che una parte della sintassi deve essere rimpiazzata dall'elemento descritto nel testo, ad esempio,<var> sarà sostituito con una variabile durante la scrittura del codice.

🔹 Definire una variabile in Python

L'elemento più semplice di un qualsiasi linguaggio di programmazione è costituito dal concetto di variabile, un nome e un posto in memoria che riserviamo per un valore.

In Python, utilizziamo questa sintassi per creare una variabile e assegnarle un valore:

<var_nome> = <valore>

Ad esempio:

età = 56
nome = "Nora"
colore = "Blu"
voti = [67, 100, 87, 56]

Se il nome di una variabile è costituito da più di una parola, la guida allo stile per il codice Python raccomanda di separare le parole con un  trattino basso (underscore) come operazione "necessaria per migliorare la leggibilità".

Ad esempio:

my_list = [1, 2, 3, 4, 5]

💡 Suggerimento: la guida allo stile per il codice Python (PEP 8) contiene ottimi suggerimenti che dovresti seguire per una scrittura "pulita" del codice Python.

🔸 Programma Hello, World! in Python

Prima di affrontare i diversi tipi di dati e di strutture di dati che puoi utilizzare in Python, vediamo come scrivere il tuo primo programma.

Devi soltanto chiamare la funzione print() e scrivere "Hello, World!" tra parentesi:

print("Hello, World!")

Dopo l'esecuzione del programma, vedrai questo messaggio:

"Hello, World!"

💡 Suggerimento: scrivere un programma "Hello, World!" è una tradizione della community di sviluppatori. In molti imparano a programmare partendo da questo programma.

Molto bene, hai appena scritto il tuo primo programma in Python. Adesso iniziamo a imparare qualcosa riguardo le tipologie di dati e strutture di dati che puoi utilizzare in Python.

🔹 Tipi di dati e strutture di dati integrate in Python

Abbiamo svariati tipi di dati e di strutture di dati integrate con cui possiamo lavorare all'interno dei nostri programmi e ogni tipo si presta ad applicazioni particolari. Vediamoli nel dettaglio.

Dati numerici in Python: interi, float e complessi

Questi sono i tipi di dati numerici con cui puoi lavorare in Python:

Interi

Gli interi sono i numeri senza decimali. Puoi controllare se un numero è un intero con la funzione type(), che restituisce l'output <class 'int'> se il dato in questione è un intero.

Ad esempio:

>>> type(1)
<class 'int'>

>>> type(15)
<class 'int'>

>>> type(0)
<class 'int'>

>>> type(-46)
<class 'int'>

Float

I float sono numeri decimali e puoi individuarli visivamente tramite il punto separatore decimale. Chiamando la funzione type() per controllare questi dati, otterremo il seguente output:

<class 'float'>

Ecco alcuni esempi:

>>> type(4.5)
<class 'float'>

>>> type(5.8)
<class 'float'>

>>> type(2342423424.3)
<class 'float'>

>>> type(4.0)
<class 'float'>

>>> type(0.0)
<class 'float'>

>>> type(-23.5)
<class 'float'>

Complessi

I numeri complessi sono costituiti da una parte reale e una parte immaginaria denotata da j. Puoi creare un numero complesso in Python con la funzione complex(), in cui il primo argomento rappresenta la parte reale  e il secondo argomento la parte immaginaria.

Ad esempio:

>>> complex(4, 5)
(4+5j)

>>> complex(6, 8)
(6+8j)

>>> complex(3.4, 3.4)
(3.4+3.4j)

>>> complex(0, 0)
0j

>>> complex(5)
(5+0j)

>>> complex(0, 4)
4j

Stringhe in Python

Le stringhe sono molto utili in Python. Contengono sequenze di caratteri e vengono utilizzate per rappresentare del testo all'interno del codice.

Ad esempio:

"Hello, World!"
'Hello, World!'

Per definire una stringa, possiamo utilizzare sia le virgolette singole '' che le doppie "". Sono entrambe valide ed equivalenti, ma dovresti sceglierne un tipo ed essere coerente durante tutto il programma che stai scrivendo.

💡 Suggerimento: esatto! Hai utilizzato una stringa quando hai scritto il programma "Hello, World!". Ogni volta che in Python vedi un valore compreso tra virgolette singole o doppie, si tratta di una stringa.

Le stringhe possono contenere qualsiasi carattere che possiamo digitare sulla tastiera, compresi numeri, simboli e altri caratteri speciali.

Ad esempio:

"45678"
"mia_email@email.com"
"#IlovePython"

💡 Suggerimento: anche gli spazi in una stringa vengono considerati come caratteri.

Virgolette all'interno di una stringa

Se definiamo una stringa usando le virgolette doppie "", possiamo utilizzare le virgolette singole al suo interno, ad esempio:

"Ieri ho compiuto vent'anni"

Se definiamo una stringa usando le virgolette singole '', possiamo utilizzare le virgolette doppie al suo interno, ad esempio:

'Il mio libro preferito è "Ragione e sentimento"'

Indici in una stringa

Possiamo utilizzare gli indici per indicare i caratteri di una stringa nel nostro programma in Python. Un indice è un intero che rappresenta una specifica posizione in una stringa ed è associato al carattere in quella posizione.

Ecco un diagramma degli indici della stringa "Hello":

String:  H e l l o
Index:   0 1 2 3 4

💡 Suggerimento: andando verso destra, gli indici partono da 0 e aumentano di 1 per ogni carattere.

Ad esempio:

>>> my_string = "Hello"

>>> my_string[0]
'H'

>>> my_string[1]
'e'

>>> my_string[2]
'l'

>>> my_string[3]
'l'

>>> my_string[4]
'o'

Possiamo anche utilizzare degli indici negativi:

>>> my_string = "Hello"

>>> my_string[-1]
'o'

>>> my_string[-2]
'l'

>>> my_string[-3]
'l'

>>> my_string[-4]
'e'

>>> my_string[-5]
'H'

💡 Suggerimento: l'indice -1 è associato all'ultimo carattere della stringa.

Dividere una stringa

Potremmo aver bisogno di estrarre una porzione di una stringa o un sottoinsieme dei suoi caratteri. Possiamo farlo con la seguente sintassi:

<variabile_stringa>[start:stop:step]
  • start è l'indice del primo carattere che verrà incluso nell'estrazione (di default è 0).
  • stop è l'indice dell'ultimo carattere della porzione da estrarre (questo carattere non sarà incluso). Di default, è l'ultimo carattere della stringa (se omettiamo questo valore, anche l'ultimo carattere verrà incluso).
  • step indica il valore da aggiungere all'indice corrente per raggiungere il successivo.

Possiamo specificare due parametri e usare lo step di default (1), includendo tutti i caratteri compresi tra l'indice start e stop (non incluso):

<variabile_stringa>[start:stop]

Ad esempio:

>>> freecodecamp = "freeCodeCamp"

>>> freecodecamp[2:8]
'eeCode'

>>> freecodecamp[0:3]
'fre'

>>> freecodecamp[0:4]
'free'

>>> freecodecamp[4:7]
'Cod'

>>> freecodecamp[4:8]
'Code'

>>> freecodecamp[8:11]
'Cam'

>>> freecodecamp[8:12]
'Camp'

>>> freecodecamp[8:13]
'Camp'

💡 Suggerimento: nota che se il valore di un parametro non è compreso nel range valido degli indici, l'estrazione avverrà comunque.

Se esplicitiamo lo step, "salteremo" da un indice all'altro secondo il suo valore.

Ad esempio:

>>> freecodecamp = "freeCodeCamp"

>>> freecodecamp[0:9:2]
'feCdC'

>>> freecodecamp[2:10:3]
'eoC'

>>> freecodecamp[1:12:4]
'roa'

>>> freecodecamp[4:8:2]
'Cd'

>>> freecodecamp[3:9:2]
'eoe'

>>> freecodecamp[1:10:5]
'rd'

Possiamo anche utilizzare uno step negativo per andare da destra a sinistra:

>>> freecodecamp = "freeCodeCamp"

>>> freecodecamp[10:2:-1]
'maCedoCe'

>>> freecodecamp[11:4:-2]
'paeo'

>>> freecodecamp[5:2:-4]
'o'

E possiamo omettere un parametro per utilizzare il suo valore di default, includendo soltanto i due punti (:) se non specifichiamo start, stopo entrambi:

>>> freecodecamp = "freeCodeCamp"

# Default start e step
>>> freecodecamp[:8]
'freeCode'

# Default end e step
>>> freecodecamp[4:]
'CodeCamp'

# Default start
>>> freecodecamp[:8:2]
'feCd'

# Default stop
>>> freecodecamp[4::3]
'Cem'

# Default start e stop
>>> freecodecamp[::-2]
'paeoer'

# Default start e stop
>>> freecodecamp[::-1]
'pmaCedoCeerf'

💡 Suggerimento: l'ultimo esempio è il modo più comune per invertire una stringa.

f-String

In Python 3.6 e nelle versioni più recenti, possiamo utilizzare un tipo di stringa chiamata f-string che può aiutarci a formattare le nostre stringhe più semplicemente.

Per definire una f-string, dobbiamo aggiungere una f prima delle virgolette (singole o doppie) e poi racchiudere le variabili o le espressioni tra parentesi graffe {} per sostituirne il valore nella stringa durante l'esecuzione del programma.

Ad esempio:

nome = "Nora"
linguaggio_preferito = "Python"

print(f"Ciao, mi chiamo {nome}. Sto imparando {linguaggio_preferito}.")

L'output è:

Ciao, mi chiamo Nora. Sto imparando Python.

Ecco un esempio in cui calcoliamo il valore di un'espressione e ne utilizziamo il risultato nella stringa:

valore = 5

print(f"{valore} per 2 è: {value * 2}")

I valori vengono inseriti nell'output:

5 per 2 è: 10

Possiamo anche richiamare un metodo all'interno delle parentesi graffe e il suo valore sarà visibile nella stringa quando eseguiamo il programma:

freecodecamp = "FREECODECAMP"

print(f"{freecodecamp.lower()}")

L'output è:

freecodecamp

Metodi per stringhe

Le stringhe hanno vari metodi che costituiscono le funzioni principali realizzate dagli sviluppatori Python e possono essere direttamente utilizzati nei nostri programmi. Possono essere molto utili per svolgere le operazioni più comuni.

Questa è la sintassi di base per chiamare un metodo per stringhe:

<variabile_stringa>.<nome_metodo>(<argomento>)

Ad esempio:

>>> freecodecamp = "freeCodeCamp"

>>> freecodecamp.capitalize()
'Freecodecamp'

>>> freecodecamp.count("C")
2

>>> freecodecamp.find("e")
2

>>> freecodecamp.index("p")
11

>>> freecodecamp.isalnum()
True

>>> freecodecamp.isalpha()
True

>>> freecodecamp.isdecimal()
False

>>> freecodecamp.isdigit()
False

>>> freecodecamp.isidentifier()
True

>>> freecodecamp.islower()
False

>>> freecodecamp.isnumeric()
False

>>> freecodecamp.isprintable()
True

>>> freecodecamp.isspace()
False

>>> freecodecamp.istitle()
False

>>> freecodecamp.isupper()
False

>>> freecodecamp.lower()
'freecodecamp'

>>> freecodecamp.lstrip("f")
'reeCodeCamp'

>>> freecodecamp.rstrip("p")
'freeCodeCam'

>>> freecodecamp.replace("e", "a")
'fraaCodaCamp'

>>> freecodecamp.split("C")
['free', 'ode', 'amp']

>>> freecodecamp.swapcase()
'FREEcODEcAMP'

>>> freecodecamp.title()
'Freecodecamp'

>>> freecodecamp.upper()
'FREECODECAMP'

Per imparare di più su questi metodi, ti consiglio di leggere quest'articolo della documentazione Python.

💡 Suggerimento: tutti i metodi per stringhe restituiscono copie della stringa senza modificare la stringa originaria in quanto le stringhe sono immutabili in Python.

Booleani in Python

I valori booleani sono True e False in Python e devo essere scritti con la prima lettera maiuscola per essere riconosciuti come tali.

Ad esempio:

>>> type(True)
<class 'bool'>

>>> type(False)
<class 'bool'>

Se li scriviamo senza maiuscola, otterremo un errore:

>>> type(true)
Traceback (most recent call last):
  File "<pyshell#92>", line 1, in <module>
    type(true)
NameError: name 'true' is not defined

>>> type(false)
Traceback (most recent call last):
  File "<pyshell#93>", line 1, in <module>
    type(false)
NameError: name 'false' is not defined

Liste in Python

Adesso che abbiamo parlato delle tipologie di dati più comuni in Python, iniziamo ad affrontare le strutture di dati integrate, tra cui le liste.

Per definire una lista, utilizziamo le parentesi quadre [] con gli elementi della lista separati da una virgola.

💡 Suggerimento: è consigliabile inserire uno spazio dopo la virgola per rendere il codice più leggibile.

Ecco alcuni esempi di liste:

[1, 2, 3, 4, 5]
["a", "b", "c", "d"]
[3.4, 2.4, 2.6, 3.5]

Le liste possono contenere tipi di dati diversi, quindi anche quello qui sotto è un esempio di lista in Python:

[1, "Emily", 3.4]

Possiamo anche assegnare la lista ad una variabile:

my_list = [1, 2, 3, 4, 5]
lettere = ["a", "b", "c", "d"]

Liste annidate

Le liste possono contenere qualsiasi valore, comprese altre liste. In questo caso vengono chiamate liste annidate.

[[1, 2, 3], [4, 5, 6]]

In questo esempio, [1, 2, 3] e [4, 5, 6] sono liste annidate.

Ecco altri due validi esempi:

[["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]]
[1, [2, 3, 4], [5, 6, 7], 3.4]

Possiamo accedere alle liste annidate utilizzando l'indice correspondente:

>>> my_list = [[1, 2, 3], [4, 5, 6]]

>>> my_list[0]
[1, 2, 3]

>>> my_list[1]
[4, 5, 6]

Le liste annidate possono essere usate per rappresentare, ad esempio, la struttura di un semplice tabellone di un gioco in 2D dove ogni numero corrisponde a un elemento o a una casella diversi:

# Esempio tabellone in cui: 
# 0 = Casella vuota
# 1 = Moneta
# 2 = Nemico
# 3 = Obiettivo
tabellone = [[0, 0, 1],
         [0, 2, 0],
         [1, 0, 3]]

Lunghezza di una lista

Possiamo usare la funzione len() per ottenere la lunghezza di una lista (il numero di elementi in essa contenuti).

Ad esempio:

>>> my_list = [1, 2, 3, 4]

>>> len(my_list)
4

Aggiornare un valore in una lista

Possiamo aggiornare un valore contenuto in una lista a un particolare indice con la sintassi:

<variabile_lista>[<indice>] = <valore>

Ad esempio:

>>> lettere = ["a", "b", "c", "d"]

>>> lettere[0] = "z"

>>> lettere
['z', 'b', 'c', 'd']

Aggiungere un valore ad una lista

Per aggiungere un nuovo valore alla fine di una lista possiamo usare il metodo .append().

Ad esempio:

>>> my_list = [1, 2, 3, 4]

>>> my_list.append(5)

>>> my_list
[1, 2, 3, 4, 5]

Rimuovere un valore da una lista

Possiamo rimuovere un valore da una lista con il metodo .remove().

Ad esempio:

>>> my_list = [1, 2, 3, 4]

>>> my_list.remove(3)

>>> my_list
[1, 2, 4]

💡 Suggerimento: se un elemento è presente più di una volta in una lista, questo metodo lo rimuoverà soltanto una volta. Ad esempio, se proviamo a rimuovere il numero 3 da una lista contenente due 3, il secondo non verrà rimosso:

>>> my_list = [1, 2, 3, 3, 4]

>>> my_list.remove(3)

>>> my_list
[1, 2, 3, 4]

Indici di una lista

Possiamo usare gli indici per una lista esattamente come nel caso di una stringa, con il primo indice che parte da 0:

>>> lettere = ["a", "b", "c", "d"]

>>> lettere[0]
'a'

>>> lettere[1]
'b'

>>> lettere[2]
'c'

>>> lettere[3]
'd'

Dividere una lista

Per ottenere una porzione di una lista utilizziamo la stessa sintassi che usiamo per le stringhe, con la possibilità di omettere i parametri per utilizzare quelli di default. In questo caso, invece di estrarre dei caratteri da una stringa, selezioneremo specifici elementi contenuti nella lista.

<variabile_lista>[start:stop:step]

Ad esempio:

>>> my_list = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]

>>> my_list[2:6:2]
['c', 'e']

>>> my_list[2:8]
['c', 'd', 'e', 'f', 'g', 'h']

>>> my_list[1:10]
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

>>> my_list[4:8:2]
['e', 'g']

>>> my_list[::-1]
['i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']

>>> my_list[::-2]
['i', 'g', 'e', 'c', 'a']

>>> my_list[8:1:-1]
['i', 'h', 'g', 'f', 'e', 'd', 'c']

Metodi per liste

Python contiene anche metodi integrati per liste per semplificarne l'utilizzo. Ecco alcuni esempi dei metodi per liste utilizzati più frequentemente:

>>> my_list = [1, 2, 3, 3, 4]

>>> my_list.append(5)
>>> my_list
[1, 2, 3, 3, 4, 5]

>>> my_list.extend([6, 7, 8, 2, 2])
>>> my_list
[1, 2, 3, 3, 4, 5, 6, 7, 8]

>>> my_list.insert(2, 15)
>>> my_list
[1, 2, 15, 3, 3, 4, 5, 6, 7, 8, 2, 2]

>>> my_list.remove(2)
>>> my_list
[1, 15, 3, 3, 4, 5, 6, 7, 8, 2, 2]

>>> my_list.pop()
2
>>> my_list
[1, 15, 3, 3, 4, 5, 6, 7, 8, 2]

>>> my_list.index(6)
6

>>> my_list.count(2)
1

>>> my_list.sort()
>>> my_list
[1, 2, 3, 3, 4, 5, 6, 7, 8, 15]

>>> my_list.reverse()
>>> my_list
[15, 8, 7, 6, 5, 4, 3, 3, 2, 1]

>>> my_list.clear()
>>> my_list
[]

Per sapere di più sui metodi per liste, ti suggerisco di leggere questo articolo della documentazione Python.

Tuple in Python

Per definire una tupla in Python, usiamo le parentesi tonde () separando gli elementi con una virgola. È consigliato l'uso di uno spazio sopo ogni virgola per rendere il codice più leggibile.

(1, 2, 3, 4, 5)
("a", "b", "c", "d")
(3.4, 2.4, 2.6, 3.5)

Possiamo assegnare le tuple a delle variabili:

my_tuple = (1, 2, 3, 4, 5)

Indici delle tuple

Possiamo indicare ciascun elemento di una tupla con l'indice corrispondente:

>>> my_tuple = (1, 2, 3, 4)

>>> my_tuple[0]
1

>>> my_tuple[1]
2

>>> my_tuple[2]
3

>>> my_tuple[3]
4

Possiamo anche usare degli indici negativi:

>>> my_tuple = (1, 2, 3, 4)

>>> my_tuple[-1]
4

>>> my_tuple[-2]
3

>>> my_tuple[-3]
2

>>> my_tuple[-4]
1

Lunghezza di una tupla

Per trovare la lunghezza di una tupla, utilizziamo la funzione len() con la tupla con argomento:

>>> my_tuple = (1, 2, 3, 4)

>>> len(my_tuple)
4

Tuple annidate

Le tuple possono contenere qualsiasi tipo di valore, comprese liste o altre tuple. Le tuple contenute in altre tuple sono dette tuple annidate.

([1, 2, 3], (4, 5, 6))

Nell'esempio qui sopra, abbiamo una tupla annidata (4, 5, 6) e una lista. Possiamo avere accesso alla struttura dati annidata utilizzando con gli indici corrispondenti.

Ad esempio:

>>> my_tuple = ([1, 2, 3], (4, 5, 6))

>>> my_tuple[0]
[1, 2, 3]

>>> my_tuple[1]
(4, 5, 6)

Dividere una tupla

Possiamo dividere una tupla nello stesso modo in cui operiamo su liste e stringhe.

La sintassi di base è:

<variabile_tupla>[start:stop:step]

Ad esempio:

>>> my_tuple = (4, 5, 6, 7, 8, 9, 10)

>>> my_tuple[3:8]
(7, 8, 9, 10)

>>> my_tuple[2:9:2]
(6, 8, 10)

>>> my_tuple[:8]
(4, 5, 6, 7, 8, 9, 10)

>>> my_tuple[:6]
(4, 5, 6, 7, 8, 9)

>>> my_tuple[:4]
(4, 5, 6, 7)

>>> my_tuple[3:]
(7, 8, 9, 10)

>>> my_tuple[2:5:2]
(6, 8)

>>> my_tuple[::2]
(4, 6, 8, 10)

>>> my_tuple[::-1]
(10, 9, 8, 7, 6, 5, 4)

>>> my_tuple[4:1:-1]
(8, 7, 6)

Metodi per le tuple

In Python, ci sono due metodi integrati per le tuple:

>>> my_tuple = (4, 4, 5, 6, 6, 7, 8, 9, 10)

>>> my_tuple.count(6)
2

>>> my_tuple.index(7)
5

💡 Suggerimento: le tuple sono immutabili e non possono essere modificate. Quindi non possiamo aggiungere o rimuovere elementi da una tupla, nè aggiornarle in alcun modo. Se ne abbiamo necessità dobbiamo creare una nuova tupla.

Attribuzione di una tupla

In Python, è disponibile una funzionalità molto utile chiamata Tuple Assignment. Con questo tipo di attribuzione, possiamo assegnare valori a variabili multiple sulla stessa riga.

I valori vengono assegnati alle variabili corrispondenti nell'ordine in cui appaiono. Ad esempio, in a, b = 1, 2 il valore 1 è assegnato alla variabile a e il valore 2 è assegnato alla variabile b.

Ad esempio:

# Tuple Assignment
>>> a, b = 1, 2

>>> a
1

>>> b
2

💡 Suggerimento: Tuple assignment è comunemente usato per scambiare i valori di due variabili:

>>> a = 1

>>> b = 2

# Scambio dei valori
>>> a, b = b, a

>>> a
2

>>> b
1

Dizionari in Python

E adesso parliamo un po' dei dizionari. Queste strutture di dati integrate ci permettono di creare coppie di dati in cui un valore è associato ad un altro.

Per definire un dizionario in Python, utilizziamo le parentesi graffe {} con le coppie chiave-valore separate da una virgola.

La chiave è separata dal valore dai due punti :, come ad esempio:

{"a": 1, "b": 2, "c"; 3}

Puoi assegnare un dizionario a una variabile:

my_dict = {"a": 1, "b": 2, "c"; 3}

Le chiavi del dizionario devono essere costituite da un tipo di dato immutabile, come stringhe, numeri o tuple, ma non liste perché possono essere cambiate.

  • Stringhe: {"City 1": 456, "City 2": 577, "City 3": 678}
  • Numeri: {1: "Move Left", 2: "Move Right", 3: "Move Up", 4: "Move Down"}
  • Tuple: {(0, 0): "Start", (2, 4): "Goal"}

I valori di un dizionario possono essere di qualsiasi tipo di dati, tra cui stringhe, numeri, liste, tuple, set o anche altri dizionari. Ecco alcuni esempi:

{"product_id": 4556, "ingredients": ["tomato", "cheese", "mushrooms"], "price": 10.67}
{"product_id": 4556, "ingredients": ("tomato", "cheese", "mushrooms"), "price": 10.67}
{"id": 567, "name": "Emily", "grades": {"Mathematics": 80, "Biology": 74, "English": 97}}

Lunghezza di un dizionario

Per ottenere il numero di coppie chiave-valore, utilizziamo la funzione len():

>>> my_dict = {"a": 1, "b": 2, "c": 3, "d": 4}

>>> len(my_dict)
4

Ottenere un valore da un dizionario

Per richiamare un valore da un dizionario, usiamo la sua chiave secondo questa sintassi:

<variabile_dizionario>[<chiave>]

Questa espressione verrà sostituita dal valore corrispondente alla chiave.

Ad esempio:

my_dict = {"a": 1, "b": 2, "c": 3, "d": 4}

print(my_dict["a"])

L'output è il valore associato ad "a":

1

Aggiornare i valori di un dizionario

Per aggiornare il valore associato ad una chiave esistente, utilizziamo la stessa sintassi ma aggiungiamo l'operatore di assegnazione e il valore:

<variabile_dizionario>[<chiave>] = <valore>

Ad esempio:

>>> my_dict = {"a": 1, "b": 2, "c": 3, "d": 4}

>>> my_dict["b"] = 6

Adesso il dizionario è:

{'a': 1, 'b': 6, 'c': 3, 'd': 4}

Aggiungere una coppia chiave-valore a un dizionario

Le chiavi di un dizionario devono essere uniche. Per aggiungere una nuova coppia chiave-valore utilizziamo la stessa sintassi che abbiamo usato per aggiornare un valore, ma stavolta la chiave deve essere nuova.

<variabile_dizionario>[<nuova_chiave>] = <valore>

Ad esempio:

>>> my_dict = {"a": 1, "b": 2, "c": 3, "d": 4}

>>> my_dict["e"] = 5

Adesso il dizionario ha una nuova coppia chiave-valore:

{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

Eliminare una coppia chiave-valore da un dizionario

Per eliminare una coppia chiave-valore, usiamo l'istruzione del:

del <variabile_dizionario>[<chiave>]

Ad esempio:

>>> my_dict = {"a": 1, "b": 2, "c": 3, "d": 4}

>>> del my_dict["c"]

Ora il dizionario è:

{'a': 1, 'b': 2, 'd': 4}

Metodi per dizionari

Ecco alcuni esempi dei metodi per dizionari più comuni:

>>> my_dict = {"a": 1, "b": 2, "c": 3, "d": 4}

>>> my_dict.get("c")
3

>>> my_dict.items()
dict_items([('a', 1), ('b', 2), ('c', 3), ('d', 4)])

>>> my_dict.keys()
dict_keys(['a', 'b', 'c', 'd'])

>>> my_dict.pop("d")
4
>>> my_dict
{"a": 1, "b": 2, "c": 3}

>>> my_dict.popitem()
('c', 3)
>>> my_dict
{"a": 1, "b": 2}

>>> my_dict.setdefault("a", 15)
1
>>> my_dict
{'a': 1, 'b': 2}

>>> my_dict.setdefault("f", 25)
25
>>> my_dict
{'a': 1, 'b': 2, 'f': 25}

>>> my_dict.update({"c": 3, "d": 4, "e": 5})
>>> my_dict
{"a": 1, "b": 2, "f": 25, "c": 3, "d": 4, "e": 5}

>>> my_dict.values()
dict_values([1, 2, 25, 3, 4, 5])

>>> my_dict.clear()
>>> my_dict
{}

Per imparare di più sui metodi per dizionari, consiglio di leggere quest'articolo della documentazione Python.

🔸 Operatori in Python

Molto bene, adesso conosci la sintassi di base delle tipologie di dati e strutture di dati più comuni in Python. Siamo pronti per parlare degli operatori, essenziali per svolgere operazioni e creare espressioni.

Operatori aritmetici in Python

Queste operazioni sono:

Addizione: +

>>> 5 + 6
11

>>> 0 + 6
6

>>> 3.4 + 5.7
9.1

>>> "Hello" + ", " + "World"
'Hello, World'

>>> True + False
1

💡 Suggerimento: gli ultimi due esempi sono particolari, non è vero? Questo operatore si comporta in maniera diversa a seconda del tipo di dato su cui agisce.

Quando si tratta di stringhe, l'operatore concatena le stringhe e quando si tratta di valori booleani, svolge un'operazione particolare.

In Python, True è equivalente a 1 e False è equivalente a 0, ed ecco spiegato il risultato (1 + 0 = 1).

Sottrazione: -

>>> 5 - 6
-1

>>> 10 - 3
7

>>> 5 - 6
-1

>>> 4.5 - 5.6 - 2.3
-3.3999999999999995

>>> 4.5 - 7
-2.5

>>> - 7.8 - 6.2
-14.0

Moltiplicazione: *

>>> 5 * 6
30

>>> 6 * 7
42

>>> 10 * 100
1000

>>> 4 * 0
0

>>> 3.4 *6.8
23.119999999999997

>>> 4 * (-6)
-24

>>> (-6) * (-8)
48

>>> "Hello" * 4
'HelloHelloHelloHello'

>>> "Hello" * 0
''

>>> "Hello" * -1
''

💡 Suggerimento: puoi moltiplicare una stringa per un intero per ripetere la stringa un dato numero di volte.

Elevamento a potenza: **

>>> 6 ** 8
1679616

>>> 5 ** 2
25

>>> 4 ** 0
1

>>> 16 ** (1/2)
4.0

>>> 16 ** (0.5)
4.0

>>> 125 ** (1/3)
4.999999999999999

>>> 4.5 ** 2.3
31.7971929089206

>>> 3 ** (-1)
0.3333333333333333

Divisione: /

>>> 25 / 5
5.0

>>> 3 / 6
0.5

>>> 0 / 5
0.0

>>> 2467 / 4673
0.5279263856195163

>>> 1 / 2
0.5

>>> 4.5 / 3.5
1.2857142857142858

>>> 6 / 7
0.8571428571428571

>>> -3 / -4
0.75

>>> 3 / -4
-0.75

>>> -3 / 4
-0.75

💡 Suggerimento: questo operatore restituisce un float come risultato, anche se la parte decimale è .0

Se provi a dividere per 0, otterrai ZeroDivisionError:

>>> 5 / 0
Traceback (most recent call last):
  File "<pyshell#109>", line 1, in <module>
    5 / 0
ZeroDivisionError: division by zero

Divisione tra interi: //

Questo operatore restituisce un intero se agisce su degli interi. Se agisce su un decimale, il risultato sarà un float con .0 come decimale, in quanto la parte decimale viene troncata.

>>> 5 // 6
0

>>> 8 // 2
4

>>> -4 // -5
0

>>> -5 // 8
-1

>>> 0 // 5
0

>>> 156773 // 356
440

Modulo: %

>>> 1 % 5
1

>>> 2 % 5
2

>>> 3 % 5
3

>>> 4 % 5
4

>>> 5 % 5
0

>>> 5 % 8
5

>>> 3 % 1
0

>>> 15 % 3
0

>>> 17 % 8
1

>>> 2568 % 4
0

>>> 245 % 15
5

>>> 0 % 6
0

>>> 3.5 % 2.4
1.1

>>> 6.7 % -7.8
-1.0999999999999996

>>> 2.3 % 7.5
2.3

Operatori di confronto

Questi operatori sono:

  • Maggiore: >
  • Maggiore o uguale: >=
  • Minore: <
  • Minore o uguale: <=
  • Uguale: ==
  • Diverso: !=

Questi operatori di confronto sono usati per costruire espressioni che vengono valutate come True o False. Ecco alcuni esempi:

>>> 5 > 6
False

>>> 10 > 8
True

>>> 8 > 8
False

>>> 8 >= 5
True

>>> 8 >= 8
True

>>> 5 < 6
True

>>> 10 < 8
False

>>> 8 < 8
False

>>> 8 <= 5
False

>>> 8 <= 8
True

>>> 8 <= 10
True

>>> 56 == 56
True

>>> 56 == 78
False

>>> 34 != 59
True

>>> 67 != 67
False

Possiamo anche usarli per confrontare delle stringhe in base al loro ordine alfabetico:

>>> "Hello" > "World"
False
>>> "Hello" >= "World"
False
>>> "Hello" < "World"
True
>>> "Hello" <= "World"
True
>>> "Hello" == "World"
False
>>> "Hello" != "World"
True

Tipicamente, vengono utilizzate per confrontare i valori di due o più variabili:

>>> a = 1
>>> b = 2

>>> a < b
True

>>> a <= b
True

>>> a > b
False

>>> a >= b
False

>>> a == b
False

>>> a != b
True

💡 Suggerimento: nota che l'operatore di confronto è == mentre quello di assegnazione è =. Il loro effetto è diverso: == restituisce True o False mentre = assegna un valore a una variabile.

Concatenamento con operatori di confronto

In Python, possiamo utilizzare il "concatenamento con operatori di confronto" in cui gli operatori di confronto vengono usati per esprimere più di un confronto in maniera concisa.

Ad esempio, questo verifica che a è minore di b e che b è minore di c:

a < b < c

Ecco alcuni esempi:

>>> a = 1
>>> b = 2
>>> c = 3

>>> a < b < c
True

>>> a > b > c
False

>>> a <= b <= c
True

>>> a >= b >= c
False

>>> a >= b > c
False

>>> a <= b < c
True

Operatori logici

In Python, esistono tre operatori logici: and, or e not. Ognuno di questi operatori possiede una propria tabella logica e risultano essenziali per lavorare con le istruzioni condizionali.

L'operatore and:

>>> True and True
True

>>> True and False
False

>>> False and True
False

>>> False and False
False

L'operatore or:

>>> True or True
True

>>> True or False
True

>>> False or True
True

>>> False or False
False

L'operatore not:

>>> not True
False

>>> not False
True

Questi operatori vengono impiegati per formare espressioni più complesse che combinano vari operatori e variabili.

Ad esempio:

>>> a = 6
>>> b = 3

>>> a < 6 or b > 2
True

>>> a >= 3 and b >= 1
True

>>> (a + b) == 9 and b > 1
True

>>> ((a % 3) < 2) and ((a + b) == 3)
False

Operatori di assegnazione

Gli operatori di assegnazione vengono usati per assegnare un valore a una variabile e sono: =, +=, -=, *=, %=, /=, //=, **=

  • L'operatore = assegna il valore a una variabile.
  • Gli oltri operatori svolgono un'operazione sul valore corrente della variabile e il nuovo valore, assegnando il risultato alla variabile stessa.

Ad esempio:

>>> x = 3
>>> x
3

>>> x += 15
>>> x
18

>>> x -= 2
>>> x
16

>>> x *= 2
>>> x
32

>>> x %= 5
>>> x
2

>>> x /= 1
>>> x
2.0

>>> x //= 2
>>> x
1.0

>>> x **= 5
>>> x
1.0

💡 Suggerimento: questi operatori eseguono operazioni bit per bit prima di assegnare il risultato alla variabile: &=, |=, ^=, >>=, <<=.

Operatori di appartenenza

Puoi verificare se un elemento è presente o meno in una sequenza di codice con gli operatori in e not in. Il risultato sarà True o False.

Ad esempio:

>>> 5 in [1, 2, 3, 4, 5]
True

>>> 8 in [1, 2, 3, 4, 5]
False

>>> 5 in (1, 2, 3, 4, 5)
True

>>> 8 in (1, 2, 3, 4, 5)
False

>>> "a" in {"a": 1, "b": 2}
True

>>> "c" in {"a": 1, "b": 2}
False

>>> "h" in "Hello"
False

>>> "H" in "Hello"
True

>>> 5 not in [1, 2, 3, 4, 5]
False

>>> 8 not in (1, 2, 3, 4, 5)
True

>>> "a" not in {"a": 1, "b": 2}
False

>>> "c" not in {"a": 1, "b": 2}
True

>>> "h" not in "Hello"
True

>>> "H" not in "Hello"
False

Vengono tipicamente usati con variabili che contengono sequenze, come nel prossimo esempio:

>>> message = "Hello, World!"

>>> "e" in message
True

🔹 Istruzioni condizionali in Python

E ora diamo un'occhiata a come possiamo scrivere istruzioni condizionali per far sì che alcune parti del nostro codice vengano eseguite (oppure no) a seconda che una condizione sia True o False.

L'istruzione if in Python

La sintassi di base di un'istruzioneif:

if <condizione>:
    <codice>

Se la condizione è True, il codice verrà eseguito, se invece è False, non verrà eseguito.

💡 Suggerimento: ci sono due punti (:) alla fine della prima riga e il codice presenta un'indentazione, che è essenziale per far sì che il codice venga riconosciuto come appartenente all'istruzione condizionale.

Ecco alcuni esempi:

Condizione falsa

x = 5

if x > 9:
    print("Hello, World!")

La condizione è x > 9 e il codice è print("Hello, World!").

In questo caso, la condizione è False, dunque non c'è output.

Condizione vera

Ecco un esempio in cui la condizione è True:

colore = "Blu"

if colore == "Blu":
    print("Questo è il mio colore preferito")

L'output è:

"Questo è il mio colore preferito"

Il codice dopo l'istruzione condizionale

Qui sotto abbiamo un esempio in cui è presente del codice dopo l'istruzione condizionale. Non appartenendo a quest'ultima, questa porzione di codice non è indentata.

x = 5

if x > 9:
    print("Hello!")

print("End")

In quest'esempio, la condizione x > 9 è False, quindi la prima riga di codice (quella indentata) non viene eseguita ma quella successiva sì in quanto non fa parte dell'istruzione condizionale. In questo caso l'output è:

End

Se invece la condizione è True:

x = 15

if x > 9:
    print("Hello!")

print("End")

L'output sarà:

Hello!
End

Esempi di istruzioni condizionali

Ecco un altro esempio:

stagione_preferita = "Estate"

if stagione_preferita == "Estate":
    print("È anche la mia stagione preferita!")

In questo caso, l'output sarà:

È anche la mia stagione preferita!

Ma se cambiamo il valore della stagione_preferita:

stagione_preferita = "Inverno"

if stagione_preferita == "Estate":
    print("È anche la mia stagione preferita!")

Non ci sarà output perché la condizione è False.

L'istruzione if/else in Python

Possiamo aggiungere una clausola else all'istruzione condizionale se abbiamo bisogno di specificare cosa deve accadere nel caso in cui la condizione sia False.

La sintassi generale è:

if <condizione>:
    <codice>
else:
    <codice>

💡 Suggerimento: nota che i due blocchi di codice sono indentati (if e else). Questo è essenziale in Python per distinguere tra il codice appartenente al programma principale e quello appartenente all'istruzione condizionale.

Vediamo alcuni esempi contenenti la clausola else:

Condizione vera

x = 15

if x > 9:
    print("Hello!")
else:
    print("Bye!")

print("End")

L'output è:

Hello!
End

Quando la condizione legata a if è True, il codice viene eseguito, mentre la clausola else no.

Condizione falsa

Adesso è il codice legato alla clausola else ad essere eseguito perché la condizione è False.

x = 5

if x > 9:
    print("Hello!")
else:
    print("Bye!")

print("End")

L'output è:

Bye!
End

L'istruzione if/elif/else in Python

Per personalizzare ulteriormente le nostre istruzioni condizionali, possiamo aggiungere una o più clausole elif. In questo modo saremo in grado di verificare e gestire condizioni multiple, ma soltanto il codice della prima condizione vera verrà eseguito.

💡 Suggerimento: elif deve essere inserito dopo if e prima di else.

Prima condizione vera

x = 5

if x < 9:
    print("Hello!")
elif x < 15:
    print("It's great to see you")
else:
    print("Bye!")

print("End")

Le due condizioni sono x < 9 e x < 15 e soltanto il blocco di codice della prima condizione che è True verrà eseguito.

In questo caso, l'output è:

Hello!
End

Daro che la prima condizione è True: x < 9.

Seconda condizione vera

Se la prima condizione è False, la seconda condizione verrà valutata.

In quest'esempio, la prima condizione x < 9 è False ma la seconda condizione x < 15 è True e il codice che appartiene a questa clausola verrà eseguito.

x = 13

if x < 9:
    print("Hello!")
elif x < 15:
    print("It's great to see you")
else:
    print("Bye!")

print("End")

L'output è:

It's great to see you
End

Tutte le condizioni false

Se tutte le condizioni sono False, verrà eseguita la clausola else:

x = 25

if x < 9:
    print("Hello!")
elif x < 15:
    print("It's great to see you")
else:
    print("Bye!")

print("End")

L'output sarà:

Bye!
End

Clausole elif multiple

Possiamo aggiungere più clausole elif a seconda delle nostre necessità. Ecco un esempio con due clausole elif:

if stagione_preferita == "inverno":
    print("È anche la mia stagione preferita")
elif stagione_preferita == "estate":
    print("L'estate è fantastica")
elif stagione_preferita == "primavera":
    print("Adoro la primavera")
else:
    print("L'autunno è la stagione preferita di mia madre")

Ogni condizione verrà valutata e soltanto il blocco di codice della prima condizione che risulta True verrà eseguito. Se nessuna condizione risulta True, verrà esrguita la clausola else.

🔸 Loop for in Python

Dopo aver visto come scrivere le istruzioni condizionali in Python, è arrivato il momento di parlare dei loop. I loop for sono delle fantastiche strutture di programmazione che puoi utilizzare per ripetere un blocco di codice un certo numero di volte.

Questa è la sintassi di base per scrivere un loop for in Python:

for <variabile_loop> in <iterabile>:
    <codice>

L'iterabile può essere una lista, una tupla, un dizionario, una stringa, una sequenza restituita da range, un file o qualsiasi altro tipo di iterabile in Python. Iniziamo a considerare range().

La funzione range() in Python

Questa funzione restituisce una sequenza di interi che possiamo usare per determinare quante iterazioni (ripetizioni) del loop verranno completate (una per ogni intero).

💡 Suggerimento: ciascun intero è assegnato alla variabile del loop, uno per ogni iterazione.

Ecco la sintassi di base per scrivere un loop for con la funzione range():

for <variabile_loop> in range(<start>, <stop>, <step>):
    <codice>

Come puoi vedere, la funzione range() accetta tre parametri:

  • start: indica la partenza della sequenza di interi (0 di default).
  • stop: indica la fine della sequenza di interi (questo valore non è incluso).
  • step: è il valore che viene aggiunto ad ogni elemento per ottenere il successivo della serie (di default è 1).

La funzione range() accetta 1, 2 o 3 argomenti:

  • Con 1 argomento, il valore viene assegnato al parametro stop mentre vengono utilizzati i valori di default per gli altri parametri.
  • Con 2 argomenti, i valori vengono assegnati ai parametri start e stop mentre viene usato il valore di default del parametro step.
  • Con 3 argomenti, i valori vengono assegnati ai parametri start, stop e step (in ordine).

Ecco alcuni esempi con un parametro:

for i in range(5):
    print(i)

Output:

0
1
2
3
4

💡 Suggerimento: la variabile del loop viene aggiornata automaticamente.

>>> for j in range(15):
    print(j * 2)

Output:

0
2
4
6
8
10
12
14
16
18
20
22
24
26
28

Nel prossimo esempio, ripetiamo una stringa tante volte quante indicate dal valore della variabile del loop:

>>> for num in range(8):
	print("Hello" * num)

Output:

Hello
HelloHello
HelloHelloHello
HelloHelloHelloHello
HelloHelloHelloHelloHello
HelloHelloHelloHelloHelloHello
HelloHelloHelloHelloHelloHelloHello

Possiamo anche utilizzare i loop for con delle strutture di dati integrate come le liste:

>>> my_list = ["a", "b", "c", "d"]

>>> for i in range(len(my_list)):
	print(my_list[i])

Output:

a
b
c
d

💡 Suggerimento: quando utilizzi range(len(<seq>)), ottieni una sequenza di numeri tra 0 e len(<seq>)-1.

Ecco alcuni esempi con due parametri:

>>> for i in range(2, 10):
	print(i)

Output:

2
3
4
5
6
7
8
9

Codice:

>>> for j in range(2, 5):
	print("Python" * j)

Output:

PythonPython
PythonPythonPython
PythonPythonPythonPython

Codice:

>>> my_list = ["a", "b", "c", "d"]

>>> for i in range(2, len(my_list)):
	print(my_list[i])

Output:

c
d

Codice:

>>> my_list = ["a", "b", "c", "d"]

>>> for i in range(2, len(my_list)-1):
	my_list[i] *= i

Adesso la lista è: ['a', 'b', 'cc', 'd']

Vediamo alcuni esempi con tre parametri:

>>> for i in range(3, 16, 2):
	print(i)

Output:

3
5
7
9
11
13
15

Codice:

>>> for j in range(10, 5, -1):
	print(j)

Output:

10
9
8
7
6

Codice:

>>> my_list = ["a", "b", "c", "d", "e", "f", "g"]

>>> for i in range(len(my_list)-1, 2, -1):
	print(my_list[i])

Output:

g
f
e
d

Ripetizioni con diversi iterabili in Python

Un approccio molto utile è l'utilizzo dei loop for per iterare direttamente su iterabili come liste, tuple, dizionari, stringhe e file. Gli elementi contenuti negli iterabili saranno coinvolti uno alla volta per ciascuna iterazione.

Vediamo alcuni esempi:

Iterare su una stringa

Se iteriamo su una stringa, i suoi caratteri verranno assegnati alla variabile del loop uno alla volta (inclusi spazi e simboli).

>>> message = "Hello, World!"

>>> for char in message:
	print(char)

	
H
e
l
l
o
,
 
W
o
r
l
d
!

Possiamo anche iterare su copie modificate di una stringa tramite l'uso di un metodo per stringhe specificando l'terabile nel loop. In questo modo, assegneremo una copia della stringa come iterabile da usare per effettuare l'iterazione:

>>> word = "Hello"

>>> for char in word.lower(): # calling the string method
	print(char)

	
h
e
l
l
o
>>> word = "Hello"

>>> for char in word.upper(): # calling the string method
	print(char)

	
H
E
L
L
O

Iterare su Liste e Tuple

>>> my_list = [2, 3, 4, 5]

>>> for num in my_list:
	print(num)

L'output è:

2
3
4
5

Codice:

>>> my_list = (2, 3, 4, 5)

>>> for num in my_list:
	if num % 2 == 0:
		print("Even")
	else:
		print("Odd")

Output:

Even
Odd
Even
Odd

Iterare su chiavi, valori e coppie chiave-valore di dizionari

Possiamo iterare su chiavi, valori e coppie chiave-valore di dizionari tramite specifici metodi per dizionari. Vediamo come.

Per iterare sulle chiavi, scriviamo:

for <var> in <variabile_dizionario>:
    <codice>

Scriviamo semplicemente il nome della variabile che contiene il dizionario come iterabile.

💡 Suggerimento: possiamo anche utilizzare la sintassi <variabile_dizionario>.keys() ma indicare direttamente il nome della variabile ha lo stesso effetto e risulta in un codice più conciso.

Ad esempio:

>>> my_dict = {"a": 1, "b": 2, "c": 3}

>>> for key in my_dict:
	print(key)

	
a
b
c

💡 Suggerimento: puoi assegnare qualsiasi nome valido alla variabile del loop.

Per iterare sui valori utilizziamo:

for <var> in <variabile_dizionario>.values():
    <codice>

Ad esempio:

>>> my_dict = {"a": 1, "b": 2, "c": 3}

>>> for value in my_dict.values():
	print(value)

	
1
2
3

Per iterare sulle coppie chiave-valore:

for <chiave>, <valore> in <variabile_dizionario>.items():
    <codice>

💡 Suggerimento: stiamo definendo due variabili del loop perché vogliamo assegnare la chiave e il valore a variabili da utilizzare nel loop.

>>> my_dict = {"a": 1, "b": 2, "c": 3}

>>> for chiave, valore in my_dict.items():
	print(chiave, valore)

	
a 1
b 2
c 3

Se definiamo soltanto una variabile del loop, questa conterrà una tupla con la coppia chiave-valore:

>>> my_dict = {"a": 1, "b": 2, "c": 3}
>>> for pair in my_dict.items():
	print(pair)

	
('a', 1)
('b', 2)
('c', 3)

break e continue in Python

È possibile utilizzare le istruzioni break e continue per avere un controllo personalizzato sui processi che avvengono durante l'iterazione del loop.

L'istruzione break

L'istruzione break viene utilizzata per interrompere un loop immediatamente.

Quando viene incontrata, il loop si ferma e il programma ritorna alla normale esecuzione al di fuori del loop.

Nell'esempio qui sotto, interrompiamo il loop quando troviamo un numero pari.

>>> my_list = [1, 2, 3, 4, 5]

>>> for elem in my_list:
	if elem % 2 == 0:
		print("Pari:", elem)
		print("break")
		break
	else:
		print("Dispari:", elem)

		
Dispari: 1
Pari: 2
break

L'istruzione continue

L'istruzionecontinue viene utilizzata per saltare il resto dell'iterazione corrente.

Quando viene incontrata durante l'esecuzione del loop, l'iterazione corrente viene interrotta e ne inizia una nuova aggiornando il valore della variabile del loop.

Nel prossimo esempio, l'iterazione viene saltata quando incontriamo un elemento pari. L'esecuzione del loop restituisce soltanto il valore degli elementi dispari:

>>> my_list = [1, 2, 3, 4, 5]

>>> for elem in my_list:
	if elem % 2 == 0:
		print("continue")
		continue
	print("Dispari:", elem)

	
Dispari: 1
continue
Dispari: 3
continue
Dispari: 5

La funzione zip() in Python

zip() è un'utile funzione integrata che possiamo utilizzare in Python per iterare contemporaneamente su sequenze multiple, ottenendo gli elementi corrispondenti in ogni iterazione.

Abbiamo bisogno soltanto di indicare le sequenze come argomenti della funzione zip() e usarne i risultati nel resto del loop.

Ad esempio:

>>> my_list1 = [1, 2, 3, 4]
>>> my_list2 = [5, 6, 7, 8]

>>> for elem1, elem2 in zip(my_list1, my_list2):
	print(elem1, elem2)

	
1 5
2 6
3 7
4 8

La funzione enumerate() in Python

Puoi utilizzare un contatore durante l'esecuzione del loop tramite la funzione enumerate(), comunemente usata per iterare su una sequenza e ottenere l'indice corrispondente.

💡 Suggerimento: di default il contatore parte da 0.

Ad esempio:

>>> my_list = [5, 6, 7, 8]

>>> for i, elem in enumerate(my_list):
	print(i, elem)

	
0 5
1 6
2 7
3 8
>>> word = "Hello"

>>> for i, char in enumerate(word):
	print(i, char)

	
0 H
1 e
2 l
3 l
4 o

Se fai partire il conteggio da 0, puoi usare l'indice e il valore corrente nella stessa iterazione per modificare la sequenza:

>>> my_list = [5, 6, 7, 8]

>>> for index, num in enumerate(my_list):
	my_list[index] = num * 3

>>> my_list
[15, 18, 21, 24]

Puoi far partire il conteggio da un numero diverso assegnandolo come argomento della funzione enumerate():

>>> word = "Hello"

>>> for i, char in enumerate(word, 2):
	print(i, char)

	
2 H
3 e
4 l
5 l
6 o

La clausola else

I loop for hanno anche una clausola else, che puoi aggiungere se hai necessità di far eseguire uno specifico blocco di codice quando il loop completa tutte le sue iterazioni senza trovare un'istruzione break.

💡 Suggerimento: se viene incontrata l'istruzione break, la clausola else non verrà eseguita (e vice versa).

Nel prossimo esempio, proviamo a trovare un elemento maggiore di 6 nella lista. Visto che quest'elemento non viene trovato, il comando break non viene eseguito ma la clausola else sì.

my_list = [1, 2, 3, 4, 5]

for elem in my_list:
    if elem > 6:
        print("Trovato")
        break
else:
    print("Non trovato")

L'output è:

Non trovato

In ogni caso, se il comando break viene eseguito, La clausola else non verrà attuata, come possiamo vedere nel prossimo esempio:

my_list = [1, 2, 3, 4, 5, 8] # Adesso la lista contiene il valore 8

for elem in my_list:
    if elem > 6:
        print("Trovato")
        break
else:
    print("Non trovato")

L'output è:

Trovato

🔹 Loop while in Python

I loop while son simili ai loop for in quanto ci permettono di ripetere dei blocchi di codice. La differenza tra i due è che i loop while vengono eseguiti quando una data condizione risulta vera.

In un loop while, definiamo la condizione e non il numero di iterazioni e il loop viene interrotto quando questa condizione diventa falsa.

Ecco la sintassi di base di un loop while:

while <condizione>:
    <codice>

💡 Suggerimento: nei loop while, devi aggiornare le variabili che fanno parte della condizione per assicurarti che ad un certo punto la condizione del loop diventi False.

Ad esempio:

>>> x = 6

>>> while x < 15:
	print(x)
	x += 1

	
6
7
8
9
10
11
12
13
14
>>> x = 4

>>> while x >= 0:
	print("Hello" * x)
	x -= 1

	
HelloHelloHelloHello
HelloHelloHello
HelloHello
Hello
>>> num = 5

>>> while num >= 1:
	print("*" * num)
	num -= 2

	
*****
***
*

break e continue

Possiamo adoperare break e continue anche con i loop while, nello stesso modo che abbiamo precedentemente descritto:

  • break interrompe immediatamente il loop.
  • continue interrompe l'iterazione in atto e ne avvia una nuova.

Ad esempio:

>>> x = 5

>>> while x < 15:
	if x % 2 == 0:
		print("Pari:", x)
		break
	print(x)
	x += 1
    

5
Pari: 6
>>> x = 5

>>> while x < 15:
	if x % 2 == 0:
		x += 1
		continue
	print("Dispari:", x)
	x += 1

	
Dispari: 5
Dispari: 7
Dispari: 9
Dispari: 11
Dispari: 13

La clausola else

È anche possibile aggiungere la clausola else al loop. Se viene incontrato il comando break, la clausola else non viene eseguita, ma in assenza di break è else ad essere eseguito.

Nel prossimo esempio, il comando break non viene incontrato perché nessuno dei numeri è pari prima che la condizione diventi  False, così else viene eseguito.

x = 5

while x < 15:
	if x % 2 == 0:
		print("Numero pari trovato")
		break
	print(x)
	x += 2
else:
	print("Tutti i numeri sono dispari")

Ecco l'output:

5
7
9
11
13
Tutti i numeri sono dispari

Nella versione successiva dell'esempio, il comando  break viene incontrato e la clausola else non viene eseguita:

x = 5

while x < 15:
	if x % 2 == 0:
		print("Numero pari trovato")
		break
	print(x)
	x += 1 # Stiamo incrementando il valore di 1
else:
	print("Tutti i numeri sono dispari")

L'output è:

5
Numero pari trovato

Loop while infiniti

Lavorando con i loop while, possiamo avere a che fare con quelli che vengono chiamati "loop infiniti", che si generano quando la condizione non diventa mai  False, così che il loop non si interrompe senza un intervento esterno.

Questo accade generalmente quando le variabili nella condizione non vengono aggiornate in modo appropriato durante l'esecuzione del loop.

💡 Suggerimento: devi esplicitare l'aggiornamento delle variabili per assicurarti che ad un certo punto la condizione del loop diventi falsa.

Ad esempio:

>>> x = 5

>>> while x > 2:
	print(x)

	
5
5
5
5
5
5
5
5
5
.
.
.
# L'output continua indefinitamente

💡 Suggerimento: per interrompere il processo, digita CTRL + C (dovresti vedere il messaggio KeyboardInterrupt).

🔸 Loop annidati in Python

Possiamo scrivere un loop for all'interno di un loop for e lo stesso vale per i loop while. I loop interni vengono chiamati annidati.

💡 Suggerimento: il loop interno viene eseguito ad ogni iterazione di quello esterno.

Loop for annidati in Python

>>> for i in range(3):
	for j in range(2):
		print(i, j)

		
0 0
0 1
1 0
1 1
2 0
2 1

Aggiungendo dei comandi print, possiamo visualizzare più facilmente ciò che accade in background:

>>> for i in range(3):
	print("===> Loop esterno")
	print(f"i = {i}")
	for j in range(2):
		print("Loop interno")
		print(f"j = {j}")

		
===> Loop esterno
i = 0
Loop interno
j = 0
Loop interno
j = 1
===> Loop esterno
i = 1
Loop interno
j = 0
Loop interno
j = 1
===> Loop esterno
i = 2
Loop interno
j = 0
Loop interno
j = 1

Il loop interno completa due iterazioni per ogni iterazione del loop esterno e le variabili vengono aggiornate quando parte una nuova iterazione.

Ecco un altro esempio:

>>> num_rows = 5

>>> for i in range(5):
	for num_cols in range(num_rows-i):
		print("*", end="")
	print()

	
*****
****
***
**
*

Loop while annidati in Python

Adesso vediamo alcuni esempi di loop while annidati. In questo caso, dobbiamo aggiornare le variabili che fanno parte di ogni condizione per assicurarci che il loop si interrompa.

>>> i = 5

>>> while i > 0:
	j = 0
	while j < 2:
		print(i, j)
		j += 1
	i -= 1

	
5 0
5 1
4 0
4 1
3 0
3 1
2 0
2 1
1 0
1 1

💡 Suggerimento: possiamo anche annidare loop for all'interno di loop while e vice versa.

🔹 Funzioni in Python

In Python, possiamo definire delle funzioni per rendere il nostro codice riutilizzabile, più leggibile e organizzato. Questa è la sintassi di base di una funzione in Python:

def <nome_funzione>(<param1>, <param2>, ...):
    <codice>

💡 Suggerimento: una funzione può avere zero, uno o più parametri.

Funzioni senza parametri in Python

Una funzione senza parametri presenta le parentesi tonde vuote dopo il suo nome nella definizione della funzione, ad esempio:

def print_pattern():
    size = 4
    for i in range(size):
        print("*" * size)

Ecco l'output che otteniamo dopo l'esecuzione della funzione:

>>> print_pattern()
****
****
****
****

💡 Suggerimento: per richiamare una funzione devi scrivere le parentesi tonde vuote dopo il suo nome.

Funzioni con un parametro in Python

Una funzione con uno o più parametri presenta una lista di parametri compresa tra le parentesi dopo il suo nome nella definizione della funzione:

def welcome_student(name):
    print(f"Hi, {name}! Welcome to class.")

Quando chiamiamo la funzione, dobbiamo assegnare un valore come argomento, che verrà usato al posto del parametro utilizzato nella definizione della funzione:

>>> welcome_student("Nora")
Hi, Nora! Welcome to class.

Vediamo un altro esempio – una funzione che restituisce un pattern composto da asterischi. In questo caso, devi specificare su quante righe vuoi far operare la funzione:

def print_pattern(num_rows):
    for i in range(num_rows):
        for num_cols in range(num_rows-i):
            print("*", end="")
        print()

Di seguito, puoi vedere i diversi output a seconda del valore di num_rows:

>>> print_pattern(3)
***
**
*

>>> print_pattern(5)
*****
****
***
**
*

>>> print_pattern(8)
********
*******
******
*****
****
***
**
*

Funzioni con due o più parametri in Python

Per definire due o più parametri, dobbiamo separarli con una virgola:

def print_sum(a, b):
    print(a + b)

Una volta definita la funzione, possiamo chiamarla assegnandole due argomenti:

>>> print_sum(4, 5)
9

>>> print_sum(8, 9)
17

>>> print_sum(0, 0)
0

>>> print_sum(3, 5)
8

Possiamo adattare la funzione con un parametro che abbiamo visto in precedenza per lavorare con due parametri e restituire un pattern con caratteri personalizzati:

def print_pattern(num_rows, char):
	for i in range(num_rows):
		for num_cols in range(num_rows-i):
			print(char, end="")
		print()

A seguire, puoi vedere gli output con i caratteri personalizzati ottenuti chiamando la funzione con due argomenti:

>>> print_pattern(5, "A")
AAAAA
AAAA
AAA
AA
A

>>> print_pattern(8, "%")
%%%%%%%%
%%%%%%%
%%%%%%
%%%%%
%%%%
%%%
%%
%

>>> print_pattern(10, "#")
##########
#########
########
#######
######
#####
####
###
##
#

Come ottenere un valore di ritorno in Python

Adesso che sai come definire una funzione, è arrivato il momento di capire come lavorare con il comando return.

Spesso abbiamo bisogno di avere un valore di ritorno da una funzione e possiamo ottenerlo tramite il comando return. Dobbiamo soltanto includere il codice nell'esempio qui sotto all'interno della definizione della funzione:

return <valore_da_restituire>

💡 Suggerimento: la funzione si ferma immediatamente quando incontra il return e il valore viene restitutito.

Ecco un esempio:

def calcola_area_rettangolo(lunghezza, larghezza):
    return lunghezza * larghezza

Adesso possiamo chiamare la funzione e assegnarne il risultato a una variabile perché il risultato viene restituito dalla funzione:

>>> area = calcola_area_rettangolo(4, 5)
>>> area
20

Possiamo anche utilizzare return con un'istruzione condizionale per ottenere un valore a seconda che una condizione sia True o False.

In quest'esempio, la funzione restituisce il primo elemento pari riscontrato nella sequenza:

def primo_pari(seq):
    for elem in seq:
        if elem % 2 == 0:
            return elem
    else:
        return None

Ecco dei risultati che possiamo ottenere chiamando la funzione:

>>> value1 = primo_pari([2, 3, 4, 5])
>>> value1
2
>>> value2 = primo_pari([3, 5, 7, 9])
>>> print(value2)
None

💡 Suggerimento: se una funzione non ha un comando return o non ne incontra uno durante la sua esecuzione, restituisce None di default.

La  Guida allo stile per il codice Python raccomanda di:

Essere coerenti nell'uso del comando return. Tutti i return in una funzione dovrebbero restituire un'espressione oppure nessuno di loro dovrebbe. Se un return restituisce un'espressione, qualsiasi altro return che non restituisce un valore dovrebbe operare in modo esplicito restituendo None e un return esplicito dovrebbe essere presente alla fine della funzione (se raggiungibile).

Argomenti di default in Python

Possiamo assegnare argomenti di default ai parametri della nostra funzione scrivendo <parametro>=<valore> nella lista dei parametri

💡 Suggerimento: La Guida allo stile per il codice Python afferma che non dovremmo "usare spazi attorno al simbolo di attribuzione (=) quando utilizzato per indicare la keyword di un argomento."

In quest'esempio, assegniamo il valore di default 5 al parametro b e nel momento in cui chiamiamo la funzione omettendo questo parametro, verrà utilizzato il valore di default.

def print_product(a, b=5):
    print(a * b)

A conferma che il valore di default 5 viene utilizzato, ecco l'output che otteniamo chiamando la funzione senza questo argomento:

>>> print_product(4)
20

Possiamo utilizzare anche un altro valore per b esplicitando il secondo argomento:

>>> print_product(3, 4)
12

💡 Suggerimento: i parametri con argomenti di default devono essere definiti alla fine della lista dei parametri, altrimenti otterrai questo messaggio di errore: SyntaxError: non-default argument follows default argument.

Ecco un altro esempio con una funzione ideata per restituire un pattern, in cui assegniamo il valore di default "*" al parametro char.

def print_pattern(num_rows, char="*"):
	for i in range(num_rows):
		for num_cols in range(num_rows-i):
			print(char, end="")
		print()

Adesso possiamo adoperare il valore di default oppure sceglierne un altro:

>>> print_pattern(5)
*****
****
***
**
*

>>> print_pattern(6, "&")
&&&&&&
&&&&&
&&&&
&&&
&&
&

🔸 Ricorsività in Python

Una funzione ricorsiva è una funzione che richiama se stessa. Questo tipo di funzione ha un caso-base che interrompe il procedimento ricorsivo e un caso-ricorsivo che continua la ricorsione tramite una nuova chiamata ricorsiva.

Vediamo qualche esempio:

def factorial(n):
    if n == 0 or n == 1:
        return 1
    else:
        return n * factorial(n-1)
Funzione fattoriale ricorsiva
def fibonacci(n):
    if n == 0 or n == 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)
La funzione di Fibonacci
def find_power(a, b):
    if b == 0:
        return 1
    else:
        return a * find_power(a, b-1)
Trova una potenza ricorsivamente

🔹 Gestione delle eccezioni in Python

Un errore o un evento inatteso che si verifica durante l'esecuzione di un programma è chiamato eccezione. Grazie agli elementi di cui parleremo tra poco, possiamo evitare che il programma venga terminato bruscamente quando ci troviamo in questa situazione.

Vediamo quali sono le tipologie di eccezioni in Python e come gestirle.

Eccezioni comuni in Python

Ecco una lista delle eccezioni più comuni in Python e del perché si verificano:

  • ZeroDivisionError: si genera quando il secondo argomento di una divisione o modulo è zero.
>>> 5 / 0
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    5 / 0
ZeroDivisionError: division by zero

>>> 7 // 0
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    7 // 0
ZeroDivisionError: integer division or modulo by zero

>>> 8 % 0
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    8 % 0
ZeroDivisionError: integer division or modulo by zero
  • IndexError: è causata dall'uso di un indice invalido per accedere a un elemento di una sequenza.
>>> my_list = [3, 4, 5, 6]

>>> my_list[15]
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    my_list[15]
IndexError: list index out of range
  • KeyError: si verifica quando proviamo ad accedere ad una coppia chiave-valore che non esiste perchè la chiave non è presente nel dizionario.
>>> my_dict = {"a": 1, "b": 2, "c": 3}

>>> my_dict["d"]
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    my_dict["d"]
KeyError: 'd'
  • NameError: è dovuto all'utilizzo di una variabile che non è stata precedentemente definita.
>>> b
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    b
NameError: name 'b' is not defined
  • RecursionError: si genera quando l'interprete rileva che il numero massimo di chiamate ricorsive è stato superato. Ciò di solito si verifica quando il processo non raggiunge mai il caso-base.

Nell'esempio qui sotto, otteniamo un RecursionError. La funzione fattoriale opera in modo ricorsivo ma l'argomento è n anziché n-1. A meno che il valore non sia 0 o 1, il caso-base non viene mai raggiunto perché l'argomento non viene diminuito e il procedimento continua fino a dare errore.

>>> def factorial(n):
	if n == 0 or n == 1:
		return 1
	else:
		return n * factorial(n)

	
>>> factorial(5)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    factorial(5)
  File "<pyshell#5>", line 5, in factorial
    return n * factorial(n)
  File "<pyshell#5>", line 5, in factorial
    return n * factorial(n)
  File "<pyshell#5>", line 5, in factorial
    return n * factorial(n)
  [Previous line repeated 1021 more times]
  File "<pyshell#5>", line 2, in factorial
    if n == 0 or n == 1:
RecursionError: maximum recursion depth exceeded in comparison

💡 Suggerimento: per sapere di più su queste eccezioni, consiglio di leggere quest'articolo della documentazione Python.

try / except in Python

Possiamo adoperare try/except in Python per catturare le eccezioni quando si verificano e gestirle adeguatamente. In questo modo, il programma può terminare in modo appropriato o addirittura ripartire dell'eccezione.

Questa è la sintassi di base:

try:
    <codice_che_può_causare_una_eccezione>
except:
    <codice_per_gestire_l_eccezione_se_si_verifica>

Ad esempio, se utilizziamo un input utente per accedere a un elemento in una lista, l'input potrebbe non essere un indice valido, generando un'eccezione:

index = int(input("Inserisci un indice valido: "))

try:
    my_list = [1, 2, 3, 4]
    print(my_list[index])
except:
    print("Per favore inserisci un indice valido.")

Se inseriamo un valore non valido come 15, l'istruzione except verrà eseguita e l'output sarà:

Per favore inserisci un indice valido.

Se invece viene inserito un valore valido, verrà eseguito il codice presente in try come atteso.

Ecco un altro esempio:

a = int(input("Inserisci a: "))
b = int(input("Inserisci b: "))

try:
    division = a / b
    print(division)
except:
    print("Per favore inserisci valori validi.")

L'output è:

Inserisci a: 5
Inserisci b: 0

Per favore inserisci valori validi.

Come catturare uno tipo specifico di eccezione in Python

Invece di cercare e gestire tutte le possibili eccezioni che possono capitare nella clausola try, dovremmo concentrarci su di un tipo specifico, specificando l'eccezione dopo la keyword except:

try:
    <codice_che_può_causare_una_eccezione>
except <tipo_di_eccezione>:
    <codice_per_gestire_l_eccezione_se_si_verifica>

Ad esempio:

index = int(input("Inserisci l'indice: "))

try:
    my_list = [1, 2, 3, 4]
    print(my_list[index])
except IndexError: # specifica il tipo
    print("Per favore inserisci un indice valido.")
a = int(input("Inserisci a: "))
b = int(input("Inserisci b: "))

try:
    division = a / b
    print(division)
except ZeroDivisionError: # specifica il tipo
    print("Per favore inserisci valori validi.")

Come assegnare un nome all'oggetto dell'eccezione in Python

Possiamo specificare un nome per l'oggetto dell'eccezione assegnandolo a una variabile da usare nella clausola except. In questo modo, potremo averne una descrizione e conoscerne le caratteristiche.

Abbiamo solo bisogno di aggiungere as <nome>:

try:
    <codice_che_può_causare_una_eccezione>
except <tipo_di_eccezione> as <nome>:
    <codice_per_gestire_l_eccezione_se_si_verifica>

Ad esempio:

index = int(input("Inserisci l'indice: "))

try:
    my_list = [1, 2, 3, 4]
    print(my_list[index])
except IndexError as e:
    print("Eccezione generata:", e)

Osserviamo l'output che otteniamo inserendo 15 come indice:

Inserisci l'indice: 15
Eccezione generata: list index out of range

Ecco un altro esempio:

a = int(input("Inserisci a: "))
b = int(input("Inserisci b: "))

try:
    division = a / b
    print(division)
except ZeroDivisionError as err:
    print("Per favori inserisci valori validi.", err)

Questo è l'output che otteniamo inserendo 0 per b:

Per favore inserisci valori validi. division by zero

try / except / else in Python

Possiamo aggiungere una clausola else a questa struttura dopo except se vogliamo scegliere cosa deve accadere se non si verifica nessuna eccezione durante l'esecuzione della clausola try:

try:
    <codice_che_può_causare_una_eccezione>
except:
    <codice_per_gestire_l_eccezione_se_si_verifica>
else:
    <codice_eseguito_solo_senza_eccezioni_in_try>

Ad esempio:

a = int(input("Inserisci a: "))
b = int(input("Inserisci b: "))

try:
    division = a / b
    print(division)
except ZeroDivisionError as err:
    print("Per favore inserisci valori validi.", err)
else:
    print("Entrambi i valori sono validi.")

Se inseriamo i valori 5 e 0 per a e b rispettivamente, l'output è:

Per favore inserisci valori validi. division by zero

Se invece entrambi i valori immessi sono validi, per esempio 5 e 4 per a e b rispettivamente, la clausola else viene eseguita dopo try e otteniamo:

1.25
Entrambi i valori sono validi.

try / except / else / finally in Python

Un'ulteriore aggiunta può essere costituita dalla clausola finally, se abbiamo necessità di eseguire del codice in qualsiasi caso, anche quando un'eccezione viene rilevata in try.

Ad esempio:

a = int(input("Inserisci a: "))
b = int(input("Inserisci b: "))

try:
    division = a / b
    print(division)
except ZeroDivisionError as err:
    print("Per favore inserisci valori validi.", err)
else:
    print("Entrambi i valori sono validi.")
finally:
    print("Finalmente!")

Se entrambi i valori sono validi, l'output sarà il risultato della divisione e:

Entrambi i valori sono validi.
Fine!

E se viene riscontrata un'eccezione perché b è 0, vedremo:

Per favore inserisci valori validi. division by zero
Fine!

La clausola finally viene sempre eseguita.

💡 Suggerimento: questa clausola può essere utilizzata, ad esempio, per chiudere dei file anche quando si verifica un'eccezione.

🔸 Programmazione orientata agli oggetti in Python

Nella programmazione orientata agli oggetti (OOP), definiamo delle classi che agiscono come template per creare oggetti in Python con attributi e metodi (funzionalità associate agli oggetti).

Questa è la sintassi di base per definire una classe:

class <NomeClasse>:

    <nome_attributo_classe> = <valore>

    def __init__(self,<param1>, <param2>, ...):
        self.<attr1> = <param1>
        self.<attr2> = <param2>
        .
        .
        .
        # Tanti attributi quanti sono necessari
    
   def <method_name>(self, <param1>, ...):
       <codice>
       
   # Tanti metodi quanti sono necessari

💡 Suggerimento: self si riferisce a un'istanza della classe (un oggetto creato con il template della classe).

Come puoi vedere, le classi contengono molti elementi. Analizziamoli nel dettaglio:

Intestazione della classe

La prima riga della definizione di una classe contiene la keyword class e il nome della classe:

class Dog:
class House:
class Ball:

💡 Suggerimento: se eredita attributi e metodi da un'altra classe, vedremo il nome della classe tra parentesi:

class Poodle(Dog):
class Truck(Vehicle):
class Mom(FamilyMember):

In Python, scriviamo il nome delle classi in notazione a cammello (o Camel Case), in cui ogni parola inizia con la lettera maiuscola, come ad esempio FamilyMember.

__init__ e attributi di istanze

Partendo da una classe, andremo a creare degli oggetti in Python, proprio come si costruisce una casa basandosi sul suo progetto.

Gli oggetti avranno degli attributi che definiamo in una classe. La creazione di un'istanza della classe avviene tramite il metodo __init__.

Questa è la sintassi di base:

def __init__(self, <parametro1>, <parametro2>, ...):
        self.<attributo1> = <parametro1>  # Attributo di istanza
        self.<attributo2> = <parametro2>  # Attributo di istanza
        .
        .
        .
        # Tanti attributi di istanza quanti sono ne sono necessari

Specifichiamo tanti parametri quanti ne sono necessari per personalizzare i valori degli attributi degli oggetti che verranno creati.

Ecco un esempio della classe Dog con questo metodo:

class Dog:

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

💡 Suggerimento: nota che prima e dopo "init" è necessario un doppio trattino basso (underscore).

Come creare un'istanza

Per creare un'istanza di Dog, dobbiamo specificare il nome e l'età dell'istanza per assegnare questi valori agli attributi:

my_dog = Dog("Nora", 10)

Adesso l'istanza è pronta per essere utilizzata nel programma.

Alcune classi non richiedono nessun argomento per creare un'istanza. In questo caso, scriveremo soltanto delle parentesi vuote, ad esempio:

class Circle:

    def __init__(self):
        self.radius = 1

Per creare un'istanza:

>>> my_circle = Circle()

💡 Suggerimento: self è come un parametro che agisce in background quindi anche se è presente nella definizione del metodo, non va considerato inserendo gli argomenti.

Argomenti di default

Possiamo assegnare argomenti di default per gli attributi e concedere all'utente l'opzione nel caso voglia personalizzare il valore.

In questo caso, scriveremo <attributo>=<valore> nella lista dei parametri.

Ecco un esempio:

class Circle:

    def __init__(self, radius=1):
        self.radius = radius

Adesso possiamo creare una istanza di  Circle con il valore di default per radius omettendo il valore oppure personalizzarlo a nostro piacere:

# Valore di default
>>> my_circle1 = Circle()

# Valore personalizzato
>>> my_circle2 = Circle(5)

Come ottenere un attributo di istanza

Per avere accesso all'attributo di un'istanza, usiamo questa sintassi:

<variabile_oggetto>.<attributo>

Ad esempio:

# Definisci la classe
>>> class Dog:

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

# Crea l'istanza       
>>> my_dog = Dog("Nora", 10)

# Ottieni l'attributo
>>> my_dog.nome
'Nora'

>>> my_dog.età
10

Come aggiornare un attributo di istanza

Per aggiornare un attributo di istanza, usiamo questa sintassi:

<variabile_oggetto>.<attributo> = <nuovo_valore>

Ad esempio:

>>> class Dog:

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

        
>>> my_dog = Dog("Nora", 10)

>>> my_dog.nome
'Nora'

# Aggiorna l'attributo
>>> my_dog.nome = "Norita"

>>> my_dog.nome
'Norita'

Come rimuovere un attributo di istanza

Per rimuovere un attributo di istanza, usiamo questa sintassi:

del <variabile_oggetto>.<attributo>

Ad esempio:

>>> class Dog:

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

        
>>> my_dog = Dog("Nora", 10)

>>> my_dog.nome
'Nora'

# Cancella questo attributo
>>> del my_dog.nome

>>> my_dog.nome
Traceback (most recent call last):
  File "<pyshell#77>", line 1, in <module>
    my_dog.nome
AttributeError: 'Dog' object has no attribute 'nome'

Come cancellare un'istanza

In un modo simile, possiamo cancellare un'istanza con del:

>>> class Dog:

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

        
>>> my_dog = Dog("Nora", 10)

>>> my_dog.nome
'Nora'

# Cancella l'istanza
>>> del my_dog

>>> my_dog
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    my_dog
NameError: name 'my_dog' is not defined

Attributi pubblici e non pubblici in Python

In Python, non abbiamo modificatori di accesso per impedire l'accesso agli attributi di istanza, quindi ci basiamo su delle convenzioni della nomenclatura per specificare eventuali restrizioni.

Per esempio, aggiungendo un trattino basso prima dell'attributo, segnaliamo agli altri sviluppatori che l'attributo è pensato per essere non pubblico.

Ad esempio:

class Dog:

    def __init__(self, nome, età):
        self.nome = nome  # Attributo pubblico
        self._età = età   # Attributo non pubblico

La documentazione Python afferma:

Usa l'underscore solo per metodi e variabili di istanza non pubblici.

Decidi sempre se i metodi di classe e le variabili d'istanza (collettivamente: "attributi") devono essere pubblici o non pubblici. Se sei in dubbio, rendili non pubblici; è più semplice renderli pubblici in seguito che far diventare non pubblici degli attributi pubblici.

Gli attributi non pubblici sono pensati per non essere utilizzati da terzi; non hai garanzie che un attributo non pubblico non verrà cambiato o addirittura rimosso. - fonte

La documentazione aggiunge:

Non utilizziamo il termine "privato" in quanto nessun attributo è realmente privato in Python (senza una notevole e non necessaria mole di lavoro). - fonte

💡 Suggerimento: tecnicamente, possiamo ancora avere accesso e modificare degli attributi che presentano l'underscore prima del loro nome, ma non dovremmo.

Attributi di classe in Python

Gli attributi di classe sono condivisi da tutte le istanze di una classe. Tutte hanno accesso a questi attributi e verranno influenzati da eventuali cambiamenti apportati su di essi.

class Dog:

    # Attributi di classe
    regno = "Animalia"
    specie = "Canis lupus"

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

💡 Suggerimento: di solito, vengono scritti prima del metodo __init__.

Come ottenere un attributo di classe

Per ottenere il valore di un attributo di classe, usiamo la sintassi:

<nome_classe>.<attributo>

Ad esempio:

>>> class Dog:

    regno = "Animalia"

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

        
>>> Dog.regno
'Animalia'

💡 Suggerimento: puoi utilizzare la stessa sintassi all'interno della classe.

Come aggiornare un attributo di classe

Per aggiornare un attributo di classe, usiamo la sintassi:

<nome_classe>.<attributo> = <valore>

Ad esempio:

>>> class Dog:

    regno = "Animalia"

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

        
>>> Dog.regno
'Animalia'

>>> Dog.regno = "Nuovo Regno"

>>> Dog.regno
'Nuovo Regno'

Come cancellare un attributo di classe

Possiamo utilizzare del per cancellare un attributo di classe, ad esempio:

>>> class Dog:

    regno = "Animalia"

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

>>> Dog.regno
'Animalia'
        
# Cancella attributo di classe
>>> del Dog.regno

>>> Dog.regno
Traceback (most recent call last):
  File "<pyshell#88>", line 1, in <module>
    Dog.regno
AttributeError: type object 'Dog' has no attribute 'regno'

Come definire i metodi

I metodi rappresentano le funzionalità delle istanze di una classe.

💡 Suggerimento: i metodi per le istanze possono funzionare con gli attributi dell'istanza che viene usata per chiamare il metodo: nella definizione del metodo scriviamo self.<attribute>.

Ecco la sintassi di un metodo in una classe (di solito vengono inseriti sotto__init__):

class <NomeClasse>:

    # Attributi di classe

    # __init__

    def <nome_metodo>(self, <param1>, ...):
        <codice>

Possono avere zero, uno o più parametri se necessario (proprio come le funzioni!) ma i metodi per istanze devono sempre avere self come primo parametro.

Ad esempio, il metodo bark non ha parametri (in aggiunta a self):

class Dog:

    def __init__(self, nome, età):
        self.nome = nome
        self.età = età

    def bark(self):
        print(f"woof-woof. Mi chiamo {self.nome}")

Per chiamare questo metodo, usiamo la sintassi:

<variabile_oggetto>.<metodo>(<argomenti>)

Ad esempio:

# Crea l'istanza
>>> my_dog = Dog("Nora", 10)

# Chiama il metodo
>>> my_dog.bark()
woof-woof. Mi chiamo Nora

Nel prossimo esempio abbiamo la classe Giocatore con il metodo aumenta_velocità che accetta un parametro:

class Giocatore:

    def __init__(self, nome):
        self.nome = nome
        self.velocità = 50

    def aumenta_velocità(self, valore):
        self.velocità += valore

Per chiamare il metodo:

# Crea l'istanza        
>>> my_player = Giocatore("Nora")

# Verifica la velocità iniziale per vedere la variazione
>>> my_player.velocità
50

# Aumenta la velocità
>>> my_player.aumenta-velocità(5)

# Verifica il cambiamento
>>> my_player.velocità
55

💡 Suggerimento: puoi aggiungere più parametri separati da una virgola. È consigliabile aggiungere uno spazio dopo la virgola per migliorare la leggibilità.

Proprietà, getter e setter in Python

Getter e setter sono dei metodi che possiamo impiegare rispettivamente per ottenere o impostare il valore di un attributo d'istanza. Funzionano come intermediari per proteggere gli attributi da una variazione diretta.

In Python, utilizziamo tipicamente le proprietà invece dei getter e setter. Vediamo come.

Per definire una proprietà, scriviamo un metodo con questa sintassi:

@property
def <nome_proprietà>(self):
    return self.<attributo>

Questo metodo agirà come un getter e potrà essere eseguito per avere accesso al valore dell'attributo.

Vediamo anche come definire un setter:

@<nome_proprietà>.setter
def <nome_proprietà>(self, <parametro>):
    self.<attributo> = <parametro>

E infine un deleter per cancellare l'attributo:

@<nome_proprietà>.deleter
def <nome_proprietà>(self):
    del self.<attributo>

💡 Suggerimento: puoi scrivere qualsiasi codice di cui hai bisogno in questi metodi per ottenere, impostare e cancellare un attributo. È raccomandato di mantenere il codice più semplice possibile.

Ecco un esempio:

class Dog:

    def __init__(self, nome):
        self._nome = nome

    @property
    def nome(self):
        return self._nome

    @nome.setter
    def nome(self, nuovo_nome):
        self._nome = nuovo_nome

    @nome.deleter
    def nome(self):
        del self._nome

Se aggiungiamo delle dichiarazioni descrittive, possiamo avere un riscontro visivo di come vengono chiamati quando  svolgono la loro funzione:

>>> class Dog:

    def __init__(self, nome):
        self._nome = nome

    @property
    def nome(self):
        print("Esecuzione getter")
        return self._nome

    @name.setter
    def nome(self, nuovo_nome):
        print("Esecuzione setter")
        self._nome = nuovo_nome

    @nome.deleter
    def nome(self):
        print("Esecuzione deleter")
        del self._nome

        
>>> my_dog = Dog("Nora")

>>> my_dog.nome
Esecuzione getter
'Nora'

>>> my_dog.nome = "Norita"
Esecuzione setter

>>> my_dog.nome
Esecuzione getter
'Norita'

>>> del my_dog.nome
Esecuzione deleter

🔹 Come lavorare con i file in Python

Lavorare con i file è estremamente importante per creare dei buoni programmi. Diamo un'occhiata a come farlo in Python.

Come leggere dei file in Python

Per lavorare con i file in Python, è raccomandato l'uso dell'istruzione with in quanto in questo modo i file vengono aperti solo quando ci servono e poi vengono automaticamente chiusi al termine del processo.

Per aprire un file, utilizziamo la sintassi:

with open("<percorso_file>") as <var_file>:
    <codice>

Possiamo anche specificare che vogliamo aprire il file in modalità di lettura con una "r":

with open("<percorso_file>", "r") as <var_file>:
    <codice>

Ma questa è già la modalità di default per aprire un file, quindi possiamo ometterla come nel primo esempio:

with open("famous_quotes.txt") as file:
    for line in file:
        print(line)

oppure…

with open("famous_quotes.txt", "r") as file:
    for line in file:
        print(line)

💡 Suggerimento: esatto! possiamo iterare sulle righe di un file usando un loop for. Il percorso del file può essere relativo allo script che stiamo eseguendo in Python oppure può essere un percorso assoluto.

Come scrivere in un file in Python

Ci sono due modi per scrivere in un file. Puoi sostituire l'intero contenuto del file prima di aggiungere il nuovo contenuto oppure aggiungerlo a quello già esistente.

with open("<percorso_file>", "w") as <var_file>:
    <codice>

Per sostituire completamente il contenuto del file usiamo la modalità "w",  inserendo questa stringa come secondo argomento di open(). Chiamiamo il metodo .write() sull'oggetto file inserendo il contenuto che vogliamo scrivere come argomento.

Ad esempio:

words = ["Fantastico", "Verde", "Python", "Codice"]

with open("famous_quotes.txt", "w") as file:
    for word in words:
        file.write(word + "\n")

Quando apriamo il programma, viene creato un nuovo file (se non esiste già nel percorso che abbiamo specificato).

Il contenuto del file sarà:

Fantastico
Verde
Python
Codice

Come aggiungere contenuto a un file in Python

Comunque, se invece vuoi aggiungere del contenuto al file, hai bisogno della modalità "a":

with open("<percorso_file>", "a") as <var_file>:
    <codice>

Ad esempio:

words = ["Fantastico", "Verde", "Python", "Codice"]

with open("famous_quotes.txt", "a") as file:
    for word in words:
        file.write(word + "\n")

Questo piccolo cambiamento fa sì che il contenuto originario del file venga mantenuto. Il nuovo contenuto viene aggiunto alla fine.

Se eseguiamo di nuovo il programma, queste stringhe verranno aggiunte alla fine del file:

Fantastico
Verde
Python
Codice
Fantastico
Verde
Python
Codice

Come cancellare un file in Python

Per cancellare un file con il nostro script, possiamo utilizzare il modulo os. È consigliabile controllare con un'istruzione condizionale se il file esiste prima di chiamare la funzione remove() da questo modulo:

import os

if os.path.exists("<percorso_file>"):
  os.remove("<percorso_file>")
else:
  <codice>

Ad esempio:

import os

if os.path.exists("famous_quotes.txt"):
  os.remove("famous_quotes.txt")
else:
  print("This file doesn't exist")

Forse hai notato l'istruzione import os nella prima riga. Vediamo perché e come le istruzioni di import sono utili e come usarle.

🔸 Istruzioni import in Python

Organizzare il codice in file multipli man mano che il programma aumenta di dimensione e complessità è sempre una buona pratica, ma dobbiamo trovare un modo per unire tutti questi file per realizzare un programma che funzioni correttamente. È esattamente ciò che fanno le istruzioni import.

Scrivendo un'istruzione  import, possiamo importare un modulo (un file che contiene definizioni e istruzioni Python) in un altro file.

Ecco le diverse alternative che abbiamo per le istruzioni import:

Prima alternativa:

import <nome_modulo>

Ad esempio:

import math

💡 Suggerimento: math è un modulo integrato di Python.

Se utilizziamo questa istruzione import, abbiamo bisogno del nome del modulo prima del nome della funzione o dell'elemento a cui facciamo riferimento nel nostro codice:

>>> import math
>>> math.sqrt(25)
5.0

Dobbiamo indicare esplicitamente nel nostro codice il modulo al quale appartiene l'elemento.

Seconda alternativa:

import <modulo> as <nuovo_nome>

Ad esempio:

import math as m

Nel nostro codice, possiamo utilizzare il nuovo nome che abbiamo assegnato al modulo invece del nome originale:

>>> import math as m
>>> m.sqrt(25)
5.0

Terza alternativa:

from <nome_modulo> import <elemento>

Ad esempio:

from math import sqrt

Con questa istruzione import, possiamo chiamare la funzione direttamente senza specificare il nome del modulo:

>>> from math import sqrt
>>> sqrt(25)
5.0

Quarta alternativa:

from <nome_modulo> import <elemento> as <nuovo_nome>

Ad esempio:

from math import sqrt as square_root

Con questa istruzione import, assegniamo un nuovo nome all'elemento importato dal modulo:

>>> from math import sqrt as square_root
>>> square_root(25)
5.0

Quinta alternativa:

from <nome_modulo> import *

Questa istruzione importa tutti gli elementi contenuti nel modulo e ti permette di chiamarli direttamente senza specificare prima il nome del modulo.

Ad esempio:

>>> from math import *

>>> sqrt(25)
5.0

>>> factorial(5)
120

>>> floor(4.6)
4

>>> gcd(5, 8)
1

💡 Suggerimento: questo tipo di istruzione import può rendere più difficile sapere il modulo di appartenenza di un elemento, in particolare se stiamo importando elementi da molti moduli diversi.

Secondo la Guida allo stile per il codice Python:

L'import wilcard (from <modulo> import *) dovrebbe essere evitata poiché rende poco chiaro quali nomi sono presenti nel namespace, confondendo sia i lettori che molti strumenti automatizzati.

🔹 Comprensioni di lista e dizionario in Python

Una caratteristica molto simpatica di Python che dovresti conoscere riguarda un modo più compatto per creare liste e dizionari.

Comprensioni di lista in Python

La sintassi utilizzata per definire le comprensioni di lista, di solito, segue uno di questi quattro pattern:

[<valore_da_includere> for <var> in <sequenza>]
[<valore_da_includere> for <var1> in <sequenza1> for <var2> in <sequenza2>]
[<valore_da_includere> for <var> in <sequenza> if <condizione>]
[<valore> for <var1> in <sequenza1> for <var2> in <sequenza2> if <condizione>]

💡 Suggerimento: dovresti usarli soltanto quando non rendono il tuo codice più difficile da leggere e interpretare.

Ecco alcuni esempi:

>>> [i for i in range(4, 15)]
[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

>>> [chr(i) for i in range(67, 80)]
['C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O']

>>> [i**3 for i in range(2, 5)]
[8, 27, 64]

>>> [i + j for i in range(5, 8) for j in range(3, 6)]
[8, 9, 10, 9, 10, 11, 10, 11, 12]

>>> [k for k in range(3, 35) if k % 2 == 0]
[4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34]

>>> [i * j for i in range(2, 6) for j in range(3, 7) if i % j == 0]
[9, 16, 25]

Comprensioni di lista vs. espressioni del generatore in Python

Le comprensioni di lista vengono definite tramite le parentesi quadre [], diversamente dalle espressioni del generatore, che sono definite da parentesi tonde (). Sembrano simili ma in realtà sono piuttosto diverse tra loro. Vediamo perché.

  • Le comprensione di lista generano l'intera sequenza in una volta e la salvano in memoria.
  • Le espressioni del generatore restituiscono gli elementi uno alla volta quando richiesto.

Possiamo verificarlo grazie al modulo sys. Nel prossimo esempio, puoi vedere che lo spazio occupato in memoria è molto diverso:

>>> import sys
>>> sys.getsizeof([i for i in range(500)])
2132
>>> sys.getsizeof((i for i in range(500)))
56

Possiamo usare le espressioni del generatore per iterare in un loop for e ottenere gli elementi uno alla volta, ma se vogliamo salvare gli elementi in una lista dovremmo usare le comprensioni di lista.

Comprensioni di dizionario in Python

La sintassi di base di cui abbiamo bisogno per definire una comprensione di dizionario è:

{<chiave_valore>: <valore> for <var> in <sequenza>}
{<chiave_valore>: <valore> for <var> in <sequenza> if <condizione>}

Ecco alcuni esempi di comprensione di dizionario:

>>> {num: num**3 for num in range(3, 15)}
{3: 27, 4: 64, 5: 125, 6: 216, 7: 343, 8: 512, 9: 729, 10: 1000, 11: 1331, 12: 1728, 13: 2197, 14: 2744}

>>> {x: x + y for x in range(4, 8) for y in range(3, 7)}
{4: 10, 5: 11, 6: 12, 7: 13}

Questo invece è un esempio con un'istruzione condizionale dove un nuovo dizionario con soltanto gli studenti, che hanno raggiunto un voto maggiore o uguale a 60, viene creato a partire da quello preesistente:

>>> voti = {"Nora": 78, "Gino": 100, "Talina": 56, "Elizabeth": 45, "Lulu": 67}

>>> studenti_promossi = {student: grade for (studente, voto) in voti.items() if voto >= 60}

>>> studenti_promossi
{'Nora': 78, 'Gino': 100, 'Lulu': 67}

Spero davvero che quest'articolo ti sia piaciuto e che ti sia stato d'aiuto. Adesso sai come lavorare con gli elementi più importanti di Python.