🐍 Tuple in Python: Il Segreto dell’Immutabilità
Tutti conosciamo le Liste in Python, quei contenitori flessibili che possiamo riempire, svuotare e riorganizzare a nostro piacimento. Sono le borse della spesa del programmatore. Ma cosa succede quando abbiamo bisogno di qualcosa di più solido, qualcosa che, una volta riempito, resti scolpito nella pietra?
È qui che entrano in gioco le Tuple.
A prima vista sembrano identiche alle liste, una semplice collezione ordinata di elementi. La magia—e la differenza fondamentale che cambierà il tuo modo di programmare—sta nella loro immutabilità. Una tupla non si può toccare. Non si aggiungono, né si tolgono, né si cambiano elementi.
Questo non è un limite, è un superpotere che le rende perfette per gestire dati come record fissi, coordinate geografiche o le chiavi dei dizionari. Se sei uno sviluppatore che lavora su sistemi che richiedono integrità del dato e performance, conoscere le tuple non è un optional, ma un pilastro fondamentale.
Cos’è una tupla?
Una tupla è una collezione ordinata di elementi, proprio come una lista, ma immutabile: una volta creata, non puoi più modificarla (né aggiungere, né togliere, né cambiare gli elementi).
Come si scrive una tupla
Codice Python:
mia_tupla = (1, 2, 3, 4, 5)
anche_così = 10, 20, 30 # senza parentesi funziona lo stesso!
tupla_con_un_solo_elemento = (42,) # attenzione alla virgola!
tupla_vuota = ()
Attenzione: se scrivi (42) senza virgola, Python pensa che sia solo un numero tra parentesi, non una tupla!
Differenze principali con le liste
| Operazione | Lista | Tupla |
|---|---|---|
| Si può modificare? | Sì | No |
| Si scrive con | [ ] |
( ) |
| Velocità | un po’ più lenta | più veloce |
| Si possono usare come chiavi di dizionario? | No | Sì |
Esempi pratici
Codice Python:
frutta = ("mela", "banana", "arancia")
# Posso leggere gli elementi (come con le liste)
print(frutta[0]) # mela
print(frutta[-1]) # arancia
# Posso fare slicing
print(frutta[1:3]) # ('banana', 'arancia')
# NON posso fare queste cose:
# frutta[0] = "pera" # ERRORE!
# frutta.append("kiwi") # ERRORE!
# del frutta[1] # ERRORE!
Quando usare le tuple? (Da Pro)
Dati che non devono MAI cambiare
Le tuple sono l’ideale per definire costanti o raccolte di valori che devono rimanere fissi per tutta la vita del programma, garantendo così l’integrità.
GIORNI_SETTIMANA = ("lunedì", "martedì", "mercoledì", "giovedì", "venerdì", "sabato", "domenica")
MESI = ("gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic")
Coordinate o chiavi nei dizionari
Essendo immutabili, le tuple sono hashable e possono essere usate come chiavi nei dizionari (a differenza delle liste). Sono perfette per identificatori compositi come le coordinate geografiche.
posizione_utente = {(41.9028, 12.4964): "Roma", (45.4642, 9.1900): "Milano"}
Restituire più valori da una funzione (lo usiamo tutti i giorni)
Quando una funzione restituisce più oggetti, questi vengono automaticamente impacchettati in una tupla. Questo facilita l’**unpacking** (o assegnazione multipla) da parte del chiamante.
def statistiche(numeri):
return (min(numeri), max(numeri), sum(numeri)/len(numeri)) # tupla!
minimo, massimo, media = statistiche([23, 51, 12, 89, 34])
Integrità dei record in contesti Big Data / Architettura
Quando lavori con database, stream di eventi (Kafka, Spark, log di sistema, transazioni bancarie…), i record vengono spesso rappresentati come tuple proprio perché sono immutabili:
# Esempio: un evento prelevato da un log di transazioni (non deve essere modificabile!)
transazione = (12345, "2025-04-05 14:32:10", "Marco Rossi", "bonifico", 1500.00, "IT60X0542811101")
# Qualsiasi tentativo di cambiare il record fallisce → sicurezza garantita!
# transazione[4] = 2000.00 # TypeError!
Questo è il motivo per cui framework come Apache Spark usano le tuple come tipo base per i Row nei DataFrame.
Evoluzione avanzata: namedtuple (super usata in Data Science e backend)
Le normali tuple sono veloci ma poco leggibili:
persona = ("Mario", "Rossi", 35, "[email protected]")
print(persona[0]) # Mario → ok, ma persona[2] che cos'è? Età? Anni di servizio?
Con namedtuple hai immutabilità + leggibilità:
from collections import namedtuple
Utente = namedtuple("Utente", ["nome", "cognome", "eta", "email"])
persona = Utente("Mario", "Rossi", 35, "[email protected]")
print(persona.nome) # Mario ← molto più chiaro!
print(persona[0]) # Mario ← funziona anche come tupla normale
# persona.eta = 36 # ERRORE! rimane immutabile
Le namedtuple sono usatissime in:
- Pandas (quando converti righe in record)
- API REST / FastAPI (per modelli di risposta immutabili)
- Configurazioni e impostazioni di sistema
Le tuple normali sono solo sequenze immutabili di valori, mentre le namedtuple sono tuple a cui dai un nome ai campi, rendendo il codice molto più leggibile e meno soggetto a errori, mantenendo però tutte le proprietà di immutabilità e prestazioni delle tuple normali.
Mini-riassunto finale (da memorizzare)
| Tipo | Modificabile? | Veloce? | Leggibile? | Uso tipico |
|---|---|---|---|---|
| list | Sì | No | Sì (con nomi) | dati che cambiano spesso |
| tuple | No | Sì | Solo con indici | dati fissi, chiavi, ritorno funzioni |
| namedtuple | No | Sì | Sì (con nomi!) | record immutabili ma leggibili (pro!) |
Riassunto super-facile
-
Tuple = liste che non si possono più cambiare
-
Si scrivono con le parentesi tonde
() -
Sono più veloci e occupano meno memoria
-
Perfette per dati fissi o per restituire più valori dalle funzioni
-
Una volta creata, è scolpita nella pietra!
6 Esercizi Progressivi sulle Tuple Python
Esercizio 1: Analisi Meteo Settimanale (Facile)
Testo:
Hai raccolto le temperature massime di una settimana: (22, 25, 19, 23, 27, 24, 21)°C.
Calcola:
- La temperatura media
- Il giorno più caldo e quello più freddo
- Quanti giorni hanno superato i 23°C
def analisi_temperature(temperature):
"""
Analizza una tupla di temperature giornaliere
"""
# 1. Calcolo della temperatura media
# Le tuple supportano le operazioni di base come le liste
media = sum(temperature) / len(temperature)
print(f"Temperatura media: {media:.1f}°C")
# 2. Trovare giorno più caldo e più freddo
# max() e min() funzionano con le tuple
giorno_caldo = max(temperature)
giorno_freddo = min(temperature)
print(f"Giorno più caldo: {giorno_caldo}°C")
print(f"Giorno più freddo: {giorno_freddo}°C")
# 3. Contare giorni sopra 23°C
# Possiamo iterare sulla tupla
giorni_oltre_23 = 0
for temp in temperature:
if temp > 23:
giorni_oltre_23 += 1
print(f"Giorni sopra 23°C: {giorni_oltre_23}")
# Dati
temperature_settimanali = (22, 25, 19, 23, 27, 24, 21)
analisi_temperature(temperature_settimanali)
Output:
Temperatura media: 23.0°C
Giorno più caldo: 27°C
Giorno più freddo: 19°C
Giorni sopra 23°C: 3
💡 Osservazione: Le tuple sono immutabili ma supportano molte operazioni delle liste come sum(), max(), min(), e l’iterazione con for.
Domanda di riflessione: Perché usare una tupla invece di una lista per dati come le temperature settimanali?
Esercizio 2: Sistema di Coordinate (Facile-Intermedio)
Testo:
Date le coordinate di 3 punti: A(2, 5), B(7, 3), C(4, 8). Calcola:
- La distanza tra A e B
- Il punto medio tra A e C
- Verifica se i punti formano un triangolo rettangolo
import math
def operazioni_coordinate():
"""
Operazioni geometriche con coordinate rappresentate come tuple
"""
# Definizione dei punti come tuple (x, y)
punto_A = (2, 5)
punto_B = (7, 3)
punto_C = (4, 8)
print(f"Punto A: {punto_A}")
print(f"Punto B: {punto_B}")
print(f"Punto C: {punto_C}")
# 1. Distanza tra A e B (teorema di Pitagora)
# Accesso agli elementi con indicizzazione: tupla[0], tupla[1]
distanza_AB = math.sqrt((punto_B[0] - punto_A[0])**2 +
(punto_B[1] - punto_A[1])**2)
print(f"\n1. Distanza A-B: {distanza_AB:.2f}")
# 2. Punto medio tra A e C
# Le tuple supportano l'unpacking: x, y = punto_A
x_medio = (punto_A[0] + punto_C[0]) / 2
y_medio = (punto_A[1] + punto_C[1]) / 2
punto_medio = (x_medio, y_medio)
print(f"2. Punto medio A-C: {punto_medio}")
# 3. Verifica triangolo rettangolo (teorema di Pitagora)
distanza_BC = math.sqrt((punto_C[0] - punto_B[0])**2 +
(punto_C[1] - punto_B[1])**2)
distanza_AC = math.sqrt((punto_C[0] - punto_A[0])**2 +
(punto_C[1] - punto_A[1])**2)
# Creiamo una tupla con le distanze al quadrato
distanze_quad = (distanza_AB**2, distanza_BC**2, distanza_AC**2)
# Verifichiamo il teorema di Pitagora
is_rettangolo = (max(distanze_quad) == sum(distanze_quad) - max(distanze_quad))
print(f"3. Forma triangolo rettangolo: {is_rettangolo}")
operazioni_coordinate()
Output:
Punto A: (2, 5)
Punto B: (7, 3)
Punto C: (4, 8)
1. Distanza A-B: 5.39
2. Punto medio A-C: (3.0, 6.5)
3. Forma triangolo rettangolo: False
💡 Osservazione: Le tuple sono ideali per rappresentare coordinate perché:
- Sono immutabili (le coordinate non cambiano)
- Supportano l’unpacking:
x, y = punto - Sono più efficienti delle liste per dati fissi
Domanda di riflessione: Quale vantaggio offre l’immutabilità delle tuple quando rappresentiamo coordinate geometriche?
Esercizio 3: Analisi Vendite Mensili (Intermedio)
Testo:
Un negozio ha registrato le vendite mensili come tuple di (prodotto, quantità, prezzo_unitario):
[("Maglietta", 150, 15.50), ("Jeans", 80, 45.00), ("Scarpe", 60, 75.00), ("Cappello", 200, 12.00)]
Calcola:
- Il ricavo totale per ogni prodotto
- Il prodotto che ha generato il ricavo maggiore
- La quantità totale di prodotti venduti
def analisi_vendite(vendite):
"""
Analizza una lista di tuple rappresentanti vendite
"""
print("Dati vendite:")
for i, vendita in enumerate(vendite, 1):
prodotto, quantita, prezzo = vendita # Unpacking della tupla
print(f"{i}. {prodotto}: {quantita} pz × €{prezzo}")
# 1. Ricavo per prodotto
print("\n1. Ricavo per prodotto:")
ricavi = [] # Lista per memorizzare tuple (prodotto, ricavo)
for vendita in vendite:
prodotto, quantita, prezzo = vendita
ricavo = quantita * prezzo
ricavi.append((prodotto, ricavo))
print(f" {prodotto}: €{ricavo:.2f}")
# 2. Prodotto con ricavo maggiore
# max() con key per specificare su quale elemento confrontare
prodotto_top = max(ricavi, key=lambda x: x[1])
print(f"\n2. Prodotto top: {prodotto_top[0]} (€{prodotto_top[1]:.2f})")
# 3. Quantità totale
quantita_totale = sum(vendita[1] for vendita in vendite)
print(f"3. Quantità totale venduta: {quantita_totale} pezzi")
# Dati di vendita
vendite_mensili = [
("Maglietta", 150, 15.50),
("Jeans", 80, 45.00),
("Scarpe", 60, 75.00),
("Cappello", 200, 12.00)
]
analisi_vendite(vendite_mensili)
Output:
Dati vendite:
1. Maglietta: 150 pz × €15.5
2. Jeans: 80 pz × €45.0
3. Scarpe: 60 pz × €75.0
4. Cappello: 200 pz × €12.0
1. Ricavo per prodotto:
Maglietta: €2325.00
Jeans: €3600.00
Scarpe: €4500.00
Cappello: €2400.00
2. Prodotto top: Scarpe (€4500.00)
3. Quantità totale venduta: 490 pezzi
💡 Osservazione: Le tuple dentro liste sono un pattern comune per rappresentare dati strutturati. L’unpacking rende il codice molto leggibile.
Domanda di riflessione: Perché in questo caso usiamo una lista di tuple invece di una tupla di tuple?
Esercizio 4: Sistema di Autenticazione (Intermedio-Avanzato)
Testo:
Crea un sistema di autenticazione che memorizza credenziali come tuple (username, password_hash, ruolo).
Implementa:
- Registrazione di nuovi utenti
- Verifica login
- Controllo permessi basato sul ruolo
import hashlib
def hash_password(password):
"""Crea l'hash di una password usando SHA-256"""
return hashlib.sha256(password.encode()).hexdigest()
def sistema_autenticazione():
"""
Sistema di autenticazione che usa tuple per memorizzare utenti
"""
# Database utenti: lista di tuple (username, password_hash, ruolo)
utenti = [
("admin", hash_password("admin123"), "amministratore"),
("mario_rossi", hash_password("password1"), "utente"),
("luca_verdi", hash_password("secure123"), "moderatore")
]
def registra_utente(username, password, ruolo="utente"):
"""Registra un nuovo utente"""
# Verifica se l'utente esiste già
for utente in utenti:
if utente[0] == username:
return f"Errore: utente {username} già esistente"
# Aggiungi nuovo utente
nuovo_utente = (username, hash_password(password), ruolo)
utenti.append(nuovo_utente)
return f"Utente {username} registrato con successo"
def login(username, password):
"""Verifica le credenziali di login"""
password_hash = hash_password(password)
for utente in utenti:
# Confronto username e password hash
if utente[0] == username and utente[1] == password_hash:
return f"Login successful! Ruolo: {utente[2]}"
return "Login failed: credenziali errate"
def ha_permesso(username, azione):
"""Verifica i permessi in base al ruolo"""
# Trova l'utente
utente_corrente = None
for utente in utenti:
if utente[0] == username:
utente_corrente = utente
break
if not utente_corrente:
return f"Utente {username} non trovato"
ruolo = utente_corrente[2]
# Definizione permessi per ruolo
permessi = {
"utente": ["leggere", "commentare"],
"moderatore": ["leggere", "commentare", "moderare"],
"amministratore": ["leggere", "commentare", "moderare", "eliminare", "bannare"]
}
if azione in permessi.get(ruolo, []):
return f"Permesso CONcesso: {username} può {azione}"
else:
return f"Permesso NEGATO: {username} non può {azione}"
# Test del sistema
print("=== TEST REGISTRAZIONE ===")
print(registra_utente("giulia_bianchi", "newpass123", "utente"))
print(registra_utente("mario_rossi", "altro123")) # Già esistente
print("\n=== TEST LOGIN ===")
print(login("admin", "admin123")) # Corretto
print(login("admin", "wrongpass")) # Errato
print("\n=== TEST PERMESSI ===")
print(ha_permesso("admin", "eliminare"))
print(ha_permesso("mario_rossi", "commentare"))
print(ha_permesso("mario_rossi", "bannare"))
sistema_autenticazione()
Output:
=== TEST REGISTRAZIONE ===
Utente giulia_bianchi registrato con successo
Errore: utente mario_rossi già esistente
=== TEST LOGIN ===
Login successful! Ruolo: amministratore
Login failed: credenziali errate
=== TEST PERMESSI ===
Permesso CONcesso: admin può eliminare
Permesso CONcesso: mario_rossi può commentare
Permesso NEGATO: mario_rossi non può bannare
💡 Osservazione: Le tuple sono ideali per dati che non cambiano, come le credenziali utente dopo la registrazione. L’immutabilità previene modifiche accidentali.
Domanda di riflessione: Quale vulnerabilità di sicurezza noti in questo sistema semplice?
Esercizio 5: Gestione Inventario con Tuple Annidate (Avanzato)
Testo:
Gestisci un inventario di prodotti con struttura: (id, nome, (prezzo, sconto), [(magazzino1, quantità1), (magazzino2, quantità2)]).
Calcola:
- Prezzo scontato per ogni prodotto
- Quantità totale in tutti i magazzini
- Valore totale dell’inventario
def gestione_inventario():
"""
Gestione inventario con tuple annidate complesse
"""
# Inventario: (id, nome, (prezzo, sconto%), [(magazzino, quantità)...])
inventario = [
(101, "Laptop Gaming", (1200.00, 10), [("Milano", 5), ("Roma", 3)]),
(102, "Smartphone", (800.00, 5), [("Milano", 10), ("Roma", 7), ("Napoli", 4)]),
(103, "Tablet", (450.00, 0), [("Roma", 8), ("Napoli", 2)]),
(104, "Monitor", (300.00, 15), [("Milano", 6)])
]
print("=== ANALISI INVENTARIO ===\n")
totale_inventario = 0
for prodotto in inventario:
id_prod, nome, prezzi, magazzini = prodotto # Unpacking multiplo
prezzo_base, sconto = prezzi # Unpacking della tupla prezzi
# 1. Calcolo prezzo scontato
prezzo_scontato = prezzo_base * (1 - sconto/100)
# 2. Quantità totale in tutti i magazzini
quantita_totale = sum(quant for mag, quant in magazzini)
# 3. Valore prodotto nell'inventario
valore_prodotto = prezzo_scontato * quantita_totale
totale_inventario += valore_prodotto
print(f"Prodotto: {nome} (ID: {id_prod})")
print(f" Prezzo: €{prezzo_base:.2f} → €{prezzo_scontato:.2f} (-{sconto}%)")
print(f" Quantità totale: {quantita_totale} pezzi")
print(f" Distribuzione: {', '.join(f'{mag}: {quant}' for mag, quant in magazzini)}")
print(f" Valore inventario: €{valore_prodotto:.2f}\n")
print(f"VALORE TOTALE INVENTARIO: €{totale_inventario:.2f}")
# Analisi aggiuntiva: prodotti per magazzino
print("\n=== ANALISI PER MAGAZZINO ===")
magazzini_dict = {}
for prodotto in inventario:
nome = prodotto[1]
for magazzino, quantita in prodotto[3]:
if magazzino not in magazzini_dict:
magazzini_dict[magazzino] = []
magazzini_dict[magazzino].append((nome, quantita))
for magazzino, prodotti in magazzini_dict.items():
print(f"\n{magazzino}:")
for nome, quantita in prodotti:
print(f" - {nome}: {quantita} pz")
gestione_inventario()
Output:
=== ANALISI INVENTARIO ===
Prodotto: Laptop Gaming (ID: 101)
Prezzo: €1200.00 → €1080.00 (-10%)
Quantità totale: 8 pezzi
Distribuzione: Milano: 5, Roma: 3
Valore inventario: €8640.00
Prodotto: Smartphone (ID: 102)
Prezzo: €800.00 → €760.00 (-5%)
Quantità totale: 21 pezzi
Distribuzione: Milano: 10, Roma: 7, Napoli: 4
Valore inventario: €15960.00
Prodotto: Tablet (ID: 103)
Prezzo: €450.00 → €450.00 (-0%)
Quantità totale: 10 pezzi
Distribuzione: Roma: 8, Napoli: 2
Valore inventario: €4500.00
Prodotto: Monitor (ID: 104)
Prezzo: €300.00 → €255.00 (-15%)
Quantità totale: 6 pezzi
Distribuzione: Milano: 6
Valore inventario: €1530.00
VALORE TOTALE INVENTARIO: €30630.00
=== ANALISI PER MAGAZZINO ===
Milano:
- Laptop Gaming: 5 pz
- Smartphone: 10 pz
- Monitor: 6 pz
Roma:
- Laptop Gaming: 3 pz
- Smartphone: 7 pz
- Tablet: 8 pz
Napoli:
- Smartphone: 4 pz
- Tablet: 2 pz
💡 Osservazione: Le tuple annidate permettono di creare strutture dati complesse mantenendo l’immutabilità. L’unpacking multiplo rende il codice molto leggibile.
Domanda di riflessione: In quali scenari le tuple annidate sono preferibili alle classi?
Esercizio 6: Sistema di Punteggi per Torneo (Molto Avanzato)
Testo:
Crea un sistema per gestire i punteggi di un torneo dove ogni partita è rappresentata come:
( (squadra_casa, gol_casa), (squadra_ospite, gol_ospite), data )
Calcola:
- Classifica finale (3 punti per vittoria, 1 per pareggio)
- Squadra con più gol fatti e subiti
- Storico scontri diretti tra due squadre
from collections import defaultdict
from datetime import datetime
def torneo_calcio():
"""
Sistema di gestione torneo con analisi avanzata
"""
# Partite: ((squadra_casa, gol_casa), (squadra_ospite, gol_ospite), data)
partite = [
(("Juventus", 2), ("Milan", 1), "2024-01-15"),
(("Inter", 3), ("Roma", 3), "2024-01-16"),
(("Napoli", 1), ("Juventus", 0), "2024-01-20"),
(("Milan", 2), ("Inter", 2), "2024-01-21"),
(("Roma", 1), ("Napoli", 2), "2024-01-25"),
(("Juventus", 1), ("Inter", 1), "2024-01-26"),
(("Milan", 3), ("Roma", 0), "2024-01-30"),
(("Napoli", 2), ("Milan", 1), "2024-02-01")
]
# Strutture per accumulare statistiche
classifica = defaultdict(lambda: {"punti": 0, "gol_fatti": 0, "gol_subiti": 0})
scontri_diretti = defaultdict(list)
# Analisi di ogni partita
for partita in partite:
(casa, gol_casa), (ospite, gol_ospite), data = partita
# Aggiorna gol fatti/subiti
classifica[casa]["gol_fatti"] += gol_casa
classifica[casa]["gol_subiti"] += gol_ospite
classifica[ospite]["gol_fatti"] += gol_ospite
classifica[ospite]["gol_subiti"] += gol_casa
# Determina risultato e assegna punti
if gol_casa > gol_ospite:
classifica[casa]["punti"] += 3
risultato = f"{casa} vince"
elif gol_ospite > gol_casa:
classifica[ospite]["punti"] += 3
risultato = f"{ospite} vince"
else:
classifica[casa]["punti"] += 1
classifica[ospite]["punti"] += 1
risultato = "Pareggio"
# Registra scontro diretto
scontri_diretti[(casa, ospite)].append((gol_casa, gol_ospite, data))
print(f"{data}: {casa} {gol_casa}-{gol_ospite} {ospite} → {risultato}")
# 1. Classifica ordinata per punti
print("\n=== CLASSIFICA FINALE ===")
classifica_ordinata = sorted(
classifica.items(),
key=lambda x: (-x[1]["punti"], -x[1]["gol_fatti"] + x[1]["gol_subiti"])
)
for i, (squadra, stats) in enumerate(classifica_ordinata, 1):
diff_reti = stats["gol_fatti"] - stats["gol_subiti"]
print(f"{i:2d}. {squadra:12} {stats['punti']:2d} PTS | "
f"GF: {stats['gol_fatti']:2d} | GS: {stats['gol_subiti']:2d} | "
f"DR: {diff_reti:3d}")
# 2. Squadra con più gol fatti e subiti
max_gol_fatti = max(classifica.items(), key=lambda x: x[1]["gol_fatti"])
max_gol_subiti = max(classifica.items(), key=lambda x: x[1]["gol_subiti"])
print(f"\nAttacco migliore: {max_gol_fatti[0]} ({max_gol_fatti[1]['gol_fatti']} gol)")
print(f"Difesa peggiore: {max_gol_subiti[0]} ({max_gol_subiti[1]['gol_subiti']} gol subiti)")
# 3. Storico scontri diretti tra due squadre specifiche
print("\n=== STORICO JUVENTUS-MILAN ===")
coppia = ("Juventus", "Milan")
if coppia in scontri_diretti:
for gol_c, gol_o, data in scontri_diretti[coppia]:
print(f"{data}: Juventus {gol_c}-{gol_o} Milan")
else:
print("Nessuno scontro diretto registrato")
torneo_calcio()
Output:
2024-01-15: Juventus 2-1 Milan → Juventus vince
2024-01-16: Inter 3-3 Roma → Pareggio
2024-01-20: Napoli 1-0 Juventus → Napoli vince
2024-01-21: Milan 2-2 Inter → Pareggio
2024-01-25: Roma 1-2 Napoli → Napoli vince
2024-01-26: Juventus 1-1 Inter → Pareggio
2024-01-30: Milan 3-0 Roma → Milan vince
2024-02-01: Napoli 2-1 Milan → Napoli vince
=== CLASSIFICA FINALE ===
1. Napoli 9 PTS | GF: 5 | GS: 2 | DR: 3
2. Juventus 4 PTS | GF: 3 | GS: 3 | DR: 0
3. Milan 4 PTS | GF: 6 | GS: 6 | DR: 0
4. Inter 2 PTS | GF: 5 | GS: 5 | DR: 0
5. Roma 1 PTS | GF: 4 | GS: 8 | DR: -4
Attacco migliore: Milan (6 gol)
Difesa peggiore: Roma (8 gol subiti)
=== STORICO JUVENTUS-MILAN ===
2024-01-15: Juventus 2-1 Milan
💡 Osservazione: Le tuple come chiavi di dizionario sono molto potenti perché sono immutabili e hashable. Permettono di creare strutture dati complesse come scontri_diretti[(squadra1, squadra2)].
Domanda di riflessione: Perché le tuple possono essere usate come chiavi di dizionario mentre le liste no?
Risposte alle Domande di Riflessione
Esercizio 1: Le tuple sono preferibili per dati che non cambiano (temperature storiche) perché:





