Nei primi due capitoli abbiamo imparato a rappresentare i dati e a controllare il flusso di esecuzione di un programma.
Ora facciamo un passo fondamentale: impariamo a gestire collezioni di dati.
Un programma reale raramente lavora con un solo valore. Più spesso deve gestire:
Python mette a disposizione diverse strutture dati integrate.
In questo capitolo studieremo:
list;tuple;set;dict;lambda;map();filter();zip();array;L'obiettivo non è imparare una lunga lista di metodi a memoria, ma imparare a rispondere a una domanda fondamentale:
Qual è la struttura dati più adatta per rappresentare questo problema?
Supponiamo di dover memorizzare i nomi di cinque studenti.
Potremmo creare:
student1 = "Anna"
student2 = "Luca"
student3 = "Marco"
student4 = "Sara"
student5 = "Giulia"
Funziona, ma diventa rapidamente scomodo.
Se gli studenti diventassero cento, dovremmo creare cento variabili.
Una lista risolve il problema:
students = ["Anna", "Luca", "Marco", "Sara", "Giulia"]
Ora possiamo attraversare tutti gli studenti:
for student in students:
print(student)
La struttura dati ci permette quindi di rappresentare un insieme di informazioni correlate con un'unica variabile.
Le strutture che incontreremo più spesso sono:
Struttura Ordinata Modificabile Duplicati Accesso principale
list sì sì sì indice
tuple sì no sì indice
set no sì no appartenenza
dict sì* sì chiavi uniche chiave
* I dizionari mantengono l'ordine di inserimento nelle versioni moderne di Python. Questo non significa però che vadano utilizzati come una semplice alternativa alle liste: la loro caratteristica principale è il rapporto chiave → valore.
Possiamo riassumere così:
LISTA
"Mi interessa l'ordine degli elementi."
TUPLA
"Mi interessa l'ordine, ma il contenuto non deve essere modificato."
SET
"Mi interessa sapere quali elementi sono presenti, senza duplicati."
DIZIONARIO
"Ogni dato deve essere associato a una chiave."
listLa lista è probabilmente la struttura dati più utilizzata in Python.
Una lista è:
numbers = [1, 2, 3, 4, 5]
names = ["Anna", "Luca", "Marco"]
mixed = [1, "Python", True, 3.14]
Lista vuota:
empty = []
oppure:
empty = list()
Possiamo creare una lista a partire da un iterabile:
letters = list("Python")
print(letters)
Output:
['P', 'y', 't', 'h', 'o', 'n']
Crea una lista contenente cinque città italiane.
Crea una lista contenente:
42
"Python"
3.14
True
Trasforma la stringa:
word = "Python"
in una lista di caratteri.
Come per le stringhe, gli indici delle liste partono da 0.
fruits = ["apple", "banana", "cherry", "orange"]
print(fruits[0])
print(fruits[2])
Output:
apple
cherry
Possiamo usare indici negativi:
print(fruits[-1])
print(fruits[-2])
Output:
orange
cherry
La struttura degli indici è:
apple banana cherry orange
0 1 2 3
-4 -3 -2 -1
Le liste sono mutabili.
Possiamo quindi sostituire un elemento:
fruits = ["apple", "banana", "cherry"]
fruits[1] = "orange"
print(fruits)
Risultato:
['apple', 'orange', 'cherry']
Possiamo anche modificare una porzione:
numbers = [10, 20, 30, 40, 50]
numbers[1:3] = [200, 300]
print(numbers)
Risultato:
[10, 200, 300, 40, 50]
Lo slicing può anche modificare la dimensione della lista:
numbers[1:4] = [99]
La sintassi è:
lista[start:end:step]
Esempio:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:6])
print(numbers[:4])
print(numbers[6:])
print(numbers[::2])
print(numbers[::-1])
Output:
[2, 3, 4, 5]
[0, 1, 2, 3]
[6, 7, 8, 9]
[0, 2, 4, 6, 8]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Lo slicing restituisce una nuova lista.
Questo è importante quando si lavora con copie.
Consideriamo:
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)
Entrambe le variabili mostrano:
[1, 2, 3, 4]
Perché b = a non crea una nuova lista: assegna a b un riferimento
allo stesso oggetto.
Per creare una copia superficiale possiamo usare:
b = a.copy()
oppure:
b = a[:]
oppure:
b = list(a)
Ora:
a = [1, 2, 3]
b = a.copy()
b.append(4)
print(a)
print(b)
produce:
[1, 2, 3]
[1, 2, 3, 4]
Questa distinzione tra assegnazione e copia diventerà fondamentale quando studieremo gli oggetti in modo più approfondito.
Crea una lista di numeri e stampa:
Dimostra con un programma la differenza tra:
b = a
e:
b = a.copy()
I metodi principali per aggiungere elementi sono append(), insert()
ed extend().
append()Aggiunge un elemento alla fine:
animals = ["cat", "dog"]
animals.append("fish")
print(animals)
Risultato:
['cat', 'dog', 'fish']
insert()Inserisce un elemento in una posizione specifica:
animals.insert(1, "bird")
extend()Aggiunge più elementi:
animals.extend(["horse", "rabbit"])
Attenzione alla differenza:
animals.append(["horse", "rabbit"])
aggiunge una lista come singolo elemento.
Mentre:
animals.extend(["horse", "rabbit"])
aggiunge i due elementi separatamente.
Possiamo utilizzare diversi strumenti.
remove()Rimuove la prima occorrenza di un valore:
colors = ["red", "green", "blue", "green"]
colors.remove("green")
print(colors)
Risultato:
['red', 'blue', 'green']
Se il valore non esiste, remove() produce ValueError.
pop()Rimuove e restituisce un elemento.
colors = ["red", "green", "blue"]
color = colors.pop()
print(color)
print(colors)
Senza indice, rimuove l'ultimo elemento.
Possiamo specificare un indice:
color = colors.pop(0)
delPossiamo eliminare un elemento:
del colors[1]
oppure una porzione:
del colors[1:3]
clear()Svuota completamente la lista:
colors.clear()
Crea una lista di cinque animali e:
append();insert();extend();remove();pop().Spiega la differenza tra:
append()
e:
extend()
Possiamo utilizzare in:
fruits = ["apple", "banana", "cherry"]
print("banana" in fruits)
print("orange" in fruits)
Risultato:
True
False
Possiamo anche usare not in.
index()Per trovare la posizione della prima occorrenza:
position = fruits.index("banana")
print(position)
Se il valore non è presente, index() produce ValueError.
Per evitare l'errore possiamo prima verificare:
if "banana" in fruits:
position = fruits.index("banana")
count()Conta le occorrenze:
numbers = [1, 2, 2, 3, 2]
print(numbers.count(2))
Risultato:
3
Il modo naturale per attraversare una lista è il for:
products = ["bread", "milk", "eggs"]
for product in products:
print(product)
Se ci serve anche l'indice:
for index, product in enumerate(products, start=1):
print(f"{index}. {product}")
Output:
1. bread
2. milk
3. eggs
Possiamo anche usare un ciclo con indice:
for i in range(len(products)):
print(products[i])
ma quando ci servono contemporaneamente indice e valore, enumerate() è
normalmente più leggibile.
Stampa tutti gli elementi di una lista.
Stampa gli elementi numerandoli da 1.
Conta con un ciclo quanti numeri pari sono presenti:
numbers = [1, 4, 7, 8, 10, 13, 16]
Possiamo ordinare una lista in due modi principali.
sort()Modifica la lista originale:
numbers = [3, 1, 4, 2]
numbers.sort()
print(numbers)
Risultato:
[1, 2, 3, 4]
Ordine decrescente:
numbers.sort(reverse=True)
sorted()Restituisce una nuova lista:
numbers = [3, 1, 4, 2]
ordered = sorted(numbers)
print(numbers)
print(ordered)
Risultato:
[3, 1, 4, 2]
[1, 2, 3, 4]
La differenza fondamentale è:
sort() → modifica la lista
sorted() → crea una nuova lista
keyPossiamo specificare una funzione che stabilisce su quale valore ordinare.
Esempio:
names = ["Anna", "Luca", "Alessandro", "Mia"]
names.sort(key=len)
print(names)
Risultato:
['Mia', 'Anna', 'Luca', 'Alessandro']
Possiamo anche utilizzare una funzione:
def length(text):
return len(text)
names.sort(key=length)
Più avanti vedremo che una lambda permette di scrivere questo tipo di
funzione in modo compatto.
lambdaUna lambda è una piccola funzione anonima.
Sintassi:
lambda argomenti: espressione
Esempio:
square = lambda x: x ** 2
print(square(5))
Risultato:
25
La stessa funzione, scritta normalmente:
def square(x):
return x ** 2
Le lambda sono particolarmente utili quando dobbiamo passare una funzione semplice a un'altra funzione.
Esempio:
names = ["Anna", "Luca", "Alessandro", "Mia"]
names.sort(key=lambda name: len(name))
lambda?Sono adatte a operazioni brevi:
lambda x: x * 2
Se la logica diventa complessa, una funzione definita con def è
generalmente più leggibile.
Ordina:
numbers = [7, 2, 9, 1, 5]
in ordine crescente e decrescente.
Ordina una lista di parole in base alla lunghezza.
Dato:
students = [
("Anna", 85),
("Luca", 92),
("Marco", 78)
]
ordina gli studenti in base al voto usando lambda.
map()map() applica una funzione a ogni elemento di un iterabile.
Esempio:
numbers = [1, 2, 3, 4]
result = map(lambda x: x * 2, numbers)
print(list(result))
Risultato:
[2, 4, 6, 8]
È importante ricordare che map() restituisce un iteratore, non
direttamente una lista.
Per ottenere una lista:
result = list(map(lambda x: x * 2, numbers))
Possiamo usare una funzione normale:
def double(x):
return x * 2
result = list(map(double, numbers))
filter()filter() seleziona gli elementi per i quali una funzione restituisce
un valore truthy.
Esempio:
numbers = [1, 2, 3, 4, 5, 6]
even = filter(lambda x: x % 2 == 0, numbers)
print(list(even))
Risultato:
[2, 4, 6]
Anche filter() restituisce un iteratore.
map() e filter() a confrontoPossiamo pensare così:
map()
"Trasforma ogni elemento."
filter()
"Decide quali elementi conservare."
Esempio:
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
even = list(filter(lambda x: x % 2 == 0, numbers))
Risultato:
doubled → [2, 4, 6, 8, 10]
even → [2, 4]
Le list comprehension permettono di costruire liste in modo compatto.
Sintassi:
[espressione for elemento in iterabile]
Esempio:
squares = [x ** 2 for x in range(5)]
print(squares)
Risultato:
[0, 1, 4, 9, 16]
even = [x for x in range(10) if x % 2 == 0]
print(even)
Risultato:
[0, 2, 4, 6, 8]
squares = [
x ** 2
for x in range(10)
if x % 2 == 0
]
Risultato:
[0, 4, 16, 36, 64]
for oppure list comprehension?Queste due soluzioni sono equivalenti:
squares = []
for x in range(5):
squares.append(x ** 2)
e:
squares = [x ** 2 for x in range(5)]
La comprehension è più compatta.
Non bisogna però sacrificare la leggibilità.
Una comprehension molto complessa:
result = [f(x) for x in data if condition(x) and other_condition(x)]
può diventare difficile da leggere.
In questi casi un normale for può essere preferibile.
Crea i quadrati dei numeri da 1 a 10.
Crea una lista contenente solo i numeri dispari da 1 a 20.
Dato:
words = ["python", "java", "go", "javascript"]
crea una lista contenente le lunghezze delle parole.
Crea una lista contenente solo le parole con almeno cinque caratteri.
zip()zip() permette di attraversare più iterabili in parallelo.
Esempio:
names = ["Anna", "Luca", "Marco"]
ages = [25, 30, 22]
for name, age in zip(names, ages):
print(f"{name}: {age}")
Output:
Anna: 25
Luca: 30
Marco: 22
Possiamo anche trasformare il risultato in lista:
pairs = list(zip(names, ages))
ottenendo:
[
("Anna", 25),
("Luca", 30),
("Marco", 22)
]
zip() termina quando termina l'iterabile più corto.
Se vogliamo gestire lunghezze diverse, esistono strumenti come
itertools.zip_longest().
zip()Un uso molto comune:
names = ["Anna", "Luca", "Marco"]
ages = [25, 30, 22]
people = dict(zip(names, ages))
print(people)
Risultato:
{'Anna': 25, 'Luca': 30, 'Marco': 22}
zip()Combina:
products = ["Milk", "Bread", "Eggs"]
prices = [1.50, 2.00, 3.20]
in coppie prodotto-prezzo.
Crea un dizionario utilizzando zip().
Una tuple è una sequenza ordinata e immutabile.
coordinates = (10, 20)
Possiamo accedere agli elementi:
print(coordinates[0])
print(coordinates[-1])
Possiamo fare slicing:
point = (10, 20, 30)
print(point[1:])
Ma non possiamo modificare gli elementi:
point[0] = 100
produce:
TypeError
Una tupla può essere vuota:
empty = ()
Una tupla con un solo elemento richiede una virgola:
single = (42,)
Questa invece:
single = (42)
non è una tupla: è semplicemente il numero 42.
Possiamo creare tuple anche senza parentesi:
coordinates = 10, 20, 30
Questo comportamento viene chiamato tuple packing.
Possiamo estrarre i valori di una tupla direttamente in più variabili:
point = (10, 20)
x, y = point
print(x)
print(y)
Possiamo utilizzare lo stesso meccanismo per una lista:
colors = ["red", "green", "blue"]
first, second, third = colors
*numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers
print(first)
print(middle)
print(last)
Output:
1
[2, 3, 4]
5
La variabile preceduta da * raccoglie gli elementi rimanenti in una
lista.
L'unpacking permette una delle caratteristiche più eleganti di Python:
a = 10
b = 20
a, b = b, a
print(a)
print(b)
Output:
20
10
Non è necessario utilizzare una variabile temporanea.
Questo è un ottimo esempio di sintassi Pythonica.
Crea una tupla contenente:
nome, età, città
e assegna i tre valori a tre variabili.
Scambia:
a = 100
b = 200
utilizzando l'unpacking.
Dato:
numbers = (1, 2, 3, 4, 5, 6)
usa * per ottenere:
Le tuple sono utili quando i dati rappresentano un gruppo di valori che concettualmente deve rimanere invariato.
Esempi:
point = (10, 20)
rgb = (255, 128, 0)
date = (2026, 9, 12)
Sono inoltre utilizzate molto spesso per restituire più valori da una funzione:
def statistics(numbers):
return min(numbers), max(numbers), sum(numbers) / len(numbers)
minimum, maximum, average = statistics([1, 2, 3, 4, 5])
Python offre anche namedtuple nel modulo collections.
from collections import namedtuple
Person = namedtuple("Person", ["name", "age", "city"])
person = Person("Anna", 30, "Torino")
print(person.name)
print(person.age)
La named tuple conserva l'immutabilità della tupla ma permette di accedere ai campi con un nome.
Nelle applicazioni moderne, quando il record deve avere comportamento o
validazione più ricca, una dataclass può essere una scelta più adatta.
Per ora ci interessa comprendere il concetto.
Un set rappresenta una collezione di elementi unici.
numbers = {1, 2, 3, 4}
Se inseriamo duplicati:
numbers = {1, 2, 2, 3, 3, 4}
print(numbers)
i duplicati vengono eliminati.
Per creare un set vuoto dobbiamo utilizzare:
empty = set()
Non:
empty = {}
perché {} crea un dizionario vuoto.
add()colors = {"red", "green"}
colors.add("blue")
update()colors.update(["yellow", "black"])
remove()colors.remove("red")
Se l'elemento non esiste, produce KeyError.
discard()colors.discard("purple")
Non produce errore se l'elemento non esiste.
pop()element = colors.pop()
Rimuove e restituisce un elemento arbitrario.
Non dobbiamo fare affidamento su quale elemento venga scelto.
Il set è particolarmente efficiente per i controlli di appartenenza:
permissions = {"read", "write", "delete"}
if "write" in permissions:
print("Permesso disponibile")
Questo è uno dei casi d'uso più importanti dei set.
Possiamo anche eliminare duplicati da una lista:
numbers = [1, 1, 2, 3, 3, 4, 4]
unique_numbers = list(set(numbers))
Attenzione: questa tecnica non conserva necessariamente l'ordine originale.
Se dobbiamo rimuovere duplicati preservando l'ordine, una soluzione semplice è:
unique_numbers = list(dict.fromkeys(numbers))
Consideriamo:
a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}
Elementi presenti in a oppure b:
print(a | b)
oppure:
print(a.union(b))
Elementi presenti in entrambi:
print(a & b)
oppure:
print(a.intersection(b))
Elementi presenti in a ma non in b:
print(a - b)
Elementi presenti in uno solo dei due insiemi:
print(a ^ b)
Possiamo verificare se un set è contenuto in un altro:
a = {1, 2, 3, 4}
b = {1, 2}
print(b.issubset(a))
Risultato:
True
Viceversa:
print(a.issuperset(b))
Possiamo verificare se due set non hanno elementi in comune:
c = {10, 20}
print(a.isdisjoint(c))
Crea una lista con duplicati e trasformala in un set.
Dati:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
calcola:
Crea due set di permessi:
admin
user
e determina quali permessi ha l'admin in più rispetto all'utente.
Come le liste, anche i set possono essere creati tramite comprehension.
squares = {x ** 2 for x in range(10)}
print(squares)
Possiamo aggiungere una condizione:
even_squares = {
x ** 2
for x in range(10)
if x % 2 == 0
}
Il risultato non contiene duplicati perché la struttura risultante è un set.
Il dizionario è una struttura basata su coppie:
chiave → valore
Esempio:
student = {
"name": "Anna",
"age": 21,
"course": "Informatics"
}
Possiamo accedere a un valore tramite chiave:
print(student["name"])
Risultato:
Anna
Le chiavi devono essere uniche.
I valori possono essere praticamente di qualunque tipo:
student = {
"name": "Anna",
"age": 21,
"grades": [28, 30, 27]
}
Dizionario vuoto:
student = {}
oppure:
student = dict()
Aggiungere un valore:
student["city"] = "Torino"
Modificare un valore:
student["age"] = 22
Il comportamento dipende dal fatto che la chiave esista già.
get()L'accesso diretto:
student["phone"]
produce KeyError se la chiave non esiste.
Possiamo utilizzare get():
phone = student.get("phone")
Se la chiave non esiste, otteniamo None.
Possiamo specificare un valore predefinito:
phone = student.get("phone", "Not available")
Questo è molto utile quando una chiave è opzionale.
keys(), values() e items()Possiamo ottenere le chiavi:
for key in student.keys():
print(key)
I valori:
for value in student.values():
print(value)
Oppure entrambe le informazioni:
for key, value in student.items():
print(f"{key}: {value}")
In realtà, per le chiavi possiamo anche scrivere semplicemente:
for key in student:
print(key)
Quando scriviamo:
"name" in student
stiamo verificando la presenza della chiave, non del valore.
Esempio:
student = {
"name": "Anna",
"age": 21
}
print("name" in student)
print("Anna" in student)
Risultato:
True
False
Per verificare un valore possiamo utilizzare:
"Anna" in student.values()
pop()age = student.pop("age")
Rimuove la chiave e restituisce il valore.
popitem()item = student.popitem()
Rimuove e restituisce l'ultima coppia chiave-valore inserita.
deldel student["name"]
clear()student.clear()
Svuota il dizionario.
Crea un dizionario che rappresenti un prodotto con:
Aggiungi una chiave "available".
Modifica il prezzo.
Stampa tutte le coppie chiave-valore.
Utilizza get() per leggere una chiave che potrebbe non esistere.
I valori di un dizionario possono essere a loro volta dizionari.
users = {
"anna": {
"age": 25,
"city": "Torino"
},
"luca": {
"age": 30,
"city": "Milano"
}
}
Accesso:
print(users["anna"]["city"])
Risultato:
Torino
Questa struttura è molto comune quando rappresentiamo dati gerarchici.
Un'altra struttura molto comune è una lista di dizionari:
students = [
{"name": "Anna", "age": 21},
{"name": "Luca", "age": 22},
{"name": "Marco", "age": 20}
]
Possiamo iterare:
for student in students:
print(student["name"])
Possiamo combinare liste e dizionari in moltissimi modi.
Questa capacità rende Python particolarmente adatto alla manipolazione di dati strutturati.
Una dictionary comprehension segue questa forma:
{chiave: valore for elemento in iterabile}
Esempio:
numbers = [1, 2, 3, 4]
squares = {x: x ** 2 for x in numbers}
print(squares)
Risultato:
{1: 1, 2: 4, 3: 9, 4: 16}
Con una condizione:
even_squares = {
x: x ** 2
for x in range(10)
if x % 2 == 0
}
Possiamo creare un dizionario a partire da due liste:
names = ["Anna", "Luca", "Marco"]
ages = [25, 30, 22]
people = {
name: age
for name, age in zip(names, ages)
}
Crea:
{1: 1, 2: 4, 3: 9, ...}
per i numeri da 1 a 10.
Crea un dizionario contenente solo i numeri pari e il loro quadrato.
Trasforma due liste di nomi ed età in un dizionario.
Una generator expression assomiglia a una list comprehension, ma utilizza parentesi tonde:
(x ** 2 for x in range(5))
Non crea immediatamente una lista completa.
Crea invece un generatore che produce i valori quando vengono richiesti.
squares = (x ** 2 for x in range(5))
for square in squares:
print(square)
Questo approccio è particolarmente utile con grandi quantità di dati.
Con una list comprehension:
squares = [x ** 2 for x in range(1000000)]
viene costruita una lista contenente tutti i risultati.
Con una generator expression:
squares = (x ** 2 for x in range(1000000))
i valori vengono prodotti progressivamente.
Per esempio:
total = sum(x ** 2 for x in range(1000000))
non richiede una lista intermedia contenente tutti i quadrati.
Il generatore è lazy, cioè valuta gli elementi quando servono.
Un generatore non è una collezione riutilizzabile come una lista.
numbers = (x for x in range(3))
print(list(numbers))
print(list(numbers))
Il secondo risultato sarà:
[]
perché il generatore è stato consumato dalla prima iterazione.
Se abbiamo bisogno di riutilizzare i dati, possiamo costruire una lista:
numbers = list(x for x in range(3))
Crea un generatore dei quadrati da 1 a 10.
Utilizza un generator expression per calcolare la somma dei numeri da
1 a 100.
Spiega la differenza tra:
[x ** 2 for x in range(10)]
e:
(x ** 2 for x in range(10))
*L'operatore * permette di espandere una sequenza.
Esempio:
numbers = [1, 2, 3]
print(*numbers)
Output:
1 2 3
Possiamo utilizzarlo per creare nuove collezioni:
a = [1, 2, 3]
b = [4, 5, 6]
combined = [*a, *b]
print(combined)
Risultato:
[1, 2, 3, 4, 5, 6]
Possiamo usarlo anche nelle tuple e nei set:
combined_tuple = (*a, *b)
combined_set = {*a, *b}
**L'operatore ** permette di espandere un dizionario in un altro
dizionario o come argomenti nominati di una funzione.
Esempio:
base = {
"name": "Anna",
"age": 25
}
extra = {
"city": "Torino"
}
person = {**base, **extra}
print(person)
Risultato:
{'name': 'Anna', 'age': 25, 'city': 'Torino'}
Se due dizionari contengono la stessa chiave, il valore più a destra prevale:
a = {"x": 1}
b = {"x": 2}
result = {**a, **b}
print(result)
Risultato:
{'x': 2}
Nelle versioni moderne di Python esiste anche l'operatore | per unire
dizionari:
result = a | b
Dato:
def introduce(name, age):
print(f"{name} ha {age} anni")
possiamo avere:
person = ("Anna", 25)
introduce(*person)
Oppure:
person = {
"name": "Anna",
"age": 25
}
introduce(**person)
Questo meccanismo diventerà particolarmente importante quando studieremo le funzioni in modo approfondito.
Uno stack segue il principio:
LIFO
Last In, First Out
L'ultimo elemento inserito è il primo a essere rimosso.
Una lista Python è perfetta per implementare uno stack:
stack = []
stack.append("A")
stack.append("B")
stack.append("C")
print(stack.pop())
print(stack.pop())
Output:
C
B
Immaginiamo una pila di piatti:
C ← ultimo inserito, primo rimosso
B
A
Operazioni fondamentali:
append() → push
pop() → pop
Una queue segue il principio:
FIFO
First In, First Out
Il primo elemento inserito è il primo a uscire.
Per esempio:
A → B → C
A esce per primo.
È possibile utilizzare una lista, ma rimuovere continuamente il primo elemento con:
pop(0)
è inefficiente per code grandi perché gli elementi rimanenti devono essere spostati.
Per una vera coda è preferibile:
from collections import deque
queue = deque()
queue.append("A")
queue.append("B")
queue.append("C")
print(queue.popleft())
print(queue.popleft())
Output:
A
B
deque è progettato per inserimenti e rimozioni efficienti alle due
estremità.
Implementa uno stack con una lista.
Inserisci:
A
B
C
e rimuovili uno alla volta.
Implementa una coda con deque.
Inserisci tre elementi e rimuovili in ordine FIFO.
Spiega la differenza tra:
LIFO
FIFO
arrayPython offre anche il modulo array:
from array import array
Un array può contenere elementi dello stesso tipo e può essere più compatto di una lista per alcuni casi numerici.
Esempio:
from array import array
numbers = array("i", [1, 2, 3, 4])
numbers.append(5)
print(numbers)
Il codice "i" indica un tipo di intero.
L'array standard non deve essere confuso con le strutture offerte da librerie scientifiche come NumPy, che forniscono funzionalità molto più avanzate.
Per la maggior parte dei normali programmi Python, la lista rimane la scelta predefinita.
Questa è una delle competenze più importanti del capitolo.
list quando:Esempio:
shopping_cart = ["milk", "bread", "eggs"]
tuple quando:Esempio:
point = (10, 20)
set quando:Esempio:
permissions = {"read", "write", "delete"}
dict quando:Esempio:
user = {
"name": "Anna",
"age": 25
}
Domanda Struttura
Mi serve una sequenza modificabile? list
Mi serve una sequenza immutabile? tuple
Voglio eliminare duplicati? set
Voglio verificare rapidamente l'appartenenza? set
Voglio associare chiavi a valori? dict
Mi serve una coda FIFO efficiente? deque
Mi serve una sequenza numerica omogenea compatta? array
Mi serve generare valori senza conservarli tutti? generator
Questa tabella non sostituisce la comprensione delle strutture, ma può essere utile come riferimento rapido.
Nella pratica, le strutture dati vengono spesso combinate.
Esempio:
students = [
{
"name": "Anna",
"grades": [7, 8, 9]
},
{
"name": "Luca",
"grades": [5, 6, 7]
}
]
Qui abbiamo:
list
└── dict
└── list
Possiamo calcolare la media:
for student in students:
average = sum(student["grades"]) / len(student["grades"])
print(f"{student['name']}: {average:.2f}")
Output:
Anna: 8.00
Luca: 6.00
Questa struttura è molto vicina a dati che potremmo ricevere da un database o da un'API.
Possiamo utilizzare gli strumenti studiati.
Per esempio, ottenere i nomi degli studenti:
names = [student["name"] for student in students]
Calcolare le medie:
averages = {
student["name"]:
sum(student["grades"]) / len(student["grades"])
for student in students
}
Filtrare gli studenti con media almeno 7:
promoted = [
student
for student in students
if sum(student["grades"]) / len(student["grades"]) >= 7
]
È importante però non abusare delle comprehension. Se l'espressione diventa difficile da leggere, possiamo usare un normale ciclo.
Partiamo da:
students = [
{"name": "Anna", "grades": [7, 8, 6, 9]},
{"name": "Luca", "grades": [5, 6, 6, 7]},
{"name": "Marco", "grades": [9, 8, 10, 9]}
]
for student in students:
average = sum(student["grades"]) / len(student["grades"])
print(f"{student['name']}: {average:.2f}")
averages = {
student["name"]:
sum(student["grades"]) / len(student["grades"])
for student in students
}
promoted = [
student
for student in students
if sum(student["grades"]) / len(student["grades"]) >= 7
]
all_grades = {
grade
for student in students
for grade in student["grades"]
}
high_grades = [
grade
for student in students
for grade in student["grades"]
if grade > 7
]
total = sum(
grade
for student in students
for grade in student["grades"]
)
Abbiamo quindi utilizzato contemporaneamente:
Crea una lista di almeno tre prodotti, dove ogni prodotto sia un dizionario con:
name
price
stock
Stampa il nome di tutti i prodotti.
Calcola il valore di magazzino di ogni prodotto:
price × stock
Crea un dizionario:
nome_prodotto → valore_magazzino
Crea un set contenente tutte le categorie presenti.
Consideriamo:
students = [
{"name": "Anna", "grades": [7, 8, 6, 9]},
{"name": "Luca", "grades": [5, 6, 6, 7]},
{"name": "Marco", "grades": [9, 8, 10, 9]}
]
Realizza un programma che:
nome → media;Prova prima a utilizzare i normali cicli.
Successivamente riscrivi alcune operazioni utilizzando comprehension e generator expression.
È arrivato il momento di combinare tutto ciò che abbiamo studiato.
Costruiamo un piccolo analizzatore di inventario.
Utilizza:
products = [
{
"name": "Milk",
"category": "Dairy",
"price": 1.50,
"stock": 20
},
{
"name": "Bread",
"category": "Bakery",
"price": 2.00,
"stock": 8
},
{
"name": "Eggs",
"category": "Dairy",
"price": 3.20,
"stock": 15
},
{
"name": "Apples",
"category": "Fruit",
"price": 2.50,
"stock": 30
}
]
Utilizza un for.
Per ogni prodotto:
price × stock
e somma tutti i risultati.
Considera bassa una scorta:
<= 10
Il risultato dovrebbe contenere:
Dairy
Bakery
Fruit
senza duplicati.
Costruisci:
nome_prodotto → valore_magazzino
Inserisci nella lista i nomi dei prodotti con prezzo superiore a 2.00.
Ordina i prodotti per prezzo crescente utilizzando sorted() e
lambda.
zip()Crea due liste:
names
prices
e combinale in coppie.
Calcola il valore totale dell'inventario senza creare una lista intermedia dei valori.
L'output potrebbe essere simile a:
========================================
INVENTARIO NEGOZIO
========================================
Milk | Dairy | 1.50 € | 20
Bread | Bakery | 2.00 € | 8
Eggs | Dairy | 3.20 € | 15
Apples | Fruit | 2.50 € | 30
Valore inventario: 143.00 €
Scorta bassa:
- Bread
Categorie:
- Dairy
- Bakery
- Fruit
Prodotti con prezzo > 2.00 €:
- Eggs
- Apples
========================================
Una possibile soluzione è:
products = [
{
"name": "Milk",
"category": "Dairy",
"price": 1.50,
"stock": 20
},
{
"name": "Bread",
"category": "Bakery",
"price": 2.00,
"stock": 8
},
{
"name": "Eggs",
"category": "Dairy",
"price": 3.20,
"stock": 15
},
{
"name": "Apples",
"category": "Fruit",
"price": 2.50,
"stock": 30
}
]
print("========================================")
print(" INVENTARIO NEGOZIO")
print("========================================")
print()
# 1. Stampa dei prodotti
for product in products:
print(
f"{product['name']:10} | "
f"{product['category']:7} | "
f"{product['price']:5.2f} € | "
f"{product['stock']}"
)
# 2. Valore totale dell'inventario
total_value = sum(
product["price"] * product["stock"]
for product in products
)
print()
print(f"Valore inventario: {total_value:.2f} €")
# 3. Prodotti con scorta bassa
low_stock = [
product["name"]
for product in products
if product["stock"] <= 10
]
print()
print("Scorta bassa:")
for name in low_stock:
print(f"- {name}")
# 4. Set delle categorie
categories = {
product["category"]
for product in products
}
print()
print("Categorie:")
for category in categories:
print(f"- {category}")
# 5. Dizionario nome -> valore magazzino
inventory_values = {
product["name"]: product["price"] * product["stock"]
for product in products
}
# 6. Prodotti con prezzo > 2
expensive_products = [
product["name"]
for product in products
if product["price"] > 2.00
]
print()
print("Prodotti con prezzo > 2.00 €:")
for name in expensive_products:
print(f"- {name}")
# 7. Ordinamento per prezzo
ordered_products = sorted(
products,
key=lambda product: product["price"]
)
print()
print("Prodotti ordinati per prezzo:")
for product in ordered_products:
print(f"- {product['name']}: {product['price']:.2f} €")
# 8. zip()
names = [product["name"] for product in products]
prices = [product["price"] for product in products]
pairs = list(zip(names, prices))
print()
print("Prodotto e prezzo:")
for name, price in pairs:
print(f"- {name}: {price:.2f} €")
map() e filter()Lo stesso problema può essere affrontato anche con map() e filter().
Per esempio:
names = list(map(lambda product: product["name"], products))
e:
low_stock = list(
filter(
lambda product: product["stock"] <= 10,
products
)
)
Possiamo poi ottenere i nomi:
low_stock_names = list(
map(lambda product: product["name"], low_stock)
)
Questa soluzione è corretta.
Tuttavia, in Python moderno, spesso una comprehension è più leggibile:
low_stock_names = [
product["name"]
for product in products
if product["stock"] <= 10
]
Questo non significa che map() e filter() siano inutili.
Sono strumenti importanti e diventano particolarmente interessanti quando lavoriamo con funzioni già esistenti o pipeline di trasformazione.
numbers = [1, 2, 3]
print(numbers[5])
Produce:
IndexError
point = (10, 20)
point[0] = 50
Produce:
TypeError
{} come set vuotoempty = {}
Questo crea un dizionario.
Per un set vuoto:
empty = set()
student = {"name": "Anna"}
print(student["age"])
Produce:
KeyError
Possiamo utilizzare:
student.get("age")
append() ed extend()numbers = [1, 2]
numbers.append([3, 4])
produce:
[1, 2, [3, 4]]
mentre:
numbers.extend([3, 4])
produce:
[1, 2, 3, 4]
b = a
non crea una nuova lista.
Se vogliamo una copia:
b = a.copy()
Possiamo costruire una piccola "mappa mentale":
COLLEZIONI PYTHON
│
├── LIST
│ ├── ordinata
│ ├── mutabile
│ ├── duplicati
│ └── indice
│
├── TUPLE
│ ├── ordinata
│ ├── immutabile
│ ├── duplicati
│ └── indice
│
├── SET
│ ├── elementi unici
│ ├── mutabile
│ ├── appartenenza
│ └── operazioni insiemistiche
│
└── DICT
├── chiave → valore
├── chiavi uniche
├── mutabile
└── accesso tramite chiave
E intorno a queste strutture Python mette a disposizione strumenti come:
for
enumerate()
zip()
sorted()
map()
filter()
comprehension
generator expression
unpacking
Quale struttura utilizzeresti per una lista della spesa?
Quale struttura utilizzeresti per le coordinate (x, y) di un punto?
Quale struttura utilizzeresti per un insieme di permessi senza duplicati?
Quale struttura utilizzeresti per rappresentare:
nome → telefono
Qual è la differenza tra:
sort()
e:
sorted()
Qual è la differenza tra append() e extend()?
Perché {} non crea un set vuoto?
Che cosa fa zip()?
Che cosa fa map()?
Che cosa fa filter()?
Che cosa distingue una list comprehension da una generator expression?
A cosa serve * nell'unpacking?
A cosa serve ** con i dizionari?
Qual è la differenza tra LIFO e FIFO?
Perché deque è preferibile a list.pop(0) per una coda?
Data:
numbers = [5, 2, 9, 1, 7, 2]
2;Data:
person = ("Anna", 25, "Torino")
usa unpacking per ottenere:
name
age
city
Dati:
python_students = {"Anna", "Luca", "Marco", "Sara"}
java_students = {"Luca", "Marco", "Giulia"}
trova:
Crea un dizionario con i dati di tre prodotti.
Poi:
Data:
numbers = range(1, 21)
crea:
zip()Dati:
names = ["Anna", "Luca", "Marco"]
grades = [8, 6, 9]
crea:
Anna → 8
Luca → 6
Marco → 9
e successivamente un dizionario.
Utilizza:
students = [
{"name": "Anna", "grades": [7, 8, 6, 9]},
{"name": "Luca", "grades": [5, 6, 6, 7]},
{"name": "Marco", "grades": [9, 8, 10, 9]},
{"name": "Sara", "grades": [6, 7, 8, 7]}
]
Costruisci un programma che produca:
=== CLASSIFICAZIONE STUDENTI ===
Anna → 7.50
Luca → 6.00
Marco → 9.00
Sara → 7.00
Miglior studente: Marco
Media generale: 7.38
Promossi:
- Anna
- Marco
- Sara
Voti presenti:
{5, 6, 7, 8, 9, 10}
Voti superiori a 7:
[8, 9, 9, 8, 10, 9, 8]
Totale voti: 102
Per risolverla dovrai utilizzare almeno:
for;sum();len();max();Come esercizio aggiuntivo, prova a riscrivere almeno una parte
utilizzando map() e filter().
In questo capitolo abbiamo imparato a gestire collezioni e strutture dati.
Abbiamo visto che:
list è ordinata e mutabile;tuple è ordinata e immutabile;set contiene elementi unici;dict associa chiavi a valori;append(), insert() ed extend() aggiungono elementi;remove(), pop(), del e clear() rimuovono elementi;sort() modifica una lista mentre sorted() restituisce una nuova
lista;key permette di personalizzare l'ordinamento;lambda consente di definire piccole funzioni anonime;map() trasforma elementi;filter() seleziona elementi;zip() combina iterabili in parallelo;* e ** permettono di espandere sequenze e dizionari;keys(), values() e items();get() permette di leggere chiavi opzionali senza generare
immediatamente KeyError;deque è adatto a implementare code;array è utile in casi specifici per dati omogenei;Il punto più importante, però, non è ricordare tutti i metodi.
È imparare a ragionare sulla struttura del problema:
Come sono organizzati i dati?
Quali operazioni dovrò eseguire più spesso?
Mi interessa l'ordine?
Possono esserci duplicati?
Devo accedere tramite indice o tramite chiave?
Devo modificare i dati?
Scegliere correttamente la struttura dati rende il programma più semplice, più leggibile e spesso anche più efficiente.
Nel prossimo capitolo faremo un ulteriore passo: impareremo a organizzare il codice in funzioni, trasformando sequenze di istruzioni in componenti riutilizzabili e introducendo parametri, valori restituiti, scope e documentazione.