Tuple in Python: Guida Completa all’Immutabilità. Differenze con le Liste, Esempi Avanzati e namedtuple

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
Python le Tuple

🐍 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?

Forse potrebbe interessarti anche:  Power BI e Python: Guida all'Analisi Dati Avanzata, Visualizzazioni Personalizzate e Machine Learning Integrato

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?

Pubblicità

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.

Forse potrebbe interessarti anche:  Automi di Moore e Mealy: Guida Definitiva ai Sistemi a Stati Finiti con Esempi Pratici

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?

Pubblicità

Risposte alle Domande di Riflessione

Esercizio 1: Le tuple sono preferibili per dati che non cambiano (temperature storiche) perché:

  • Sono più efficienti in memoria e prestazioni

  • L’immutabilità previene modifiche accidentali

  • Comunicano l’intento che i dati sono fissi

Esercizio 2: L’immutabilità garantisce che le coordinate non possano essere modificate accidentalmente, preservando l’integrità dei dati geometrici.

Forse potrebbe interessarti anche:  File Parquet: La Guida Pratica per Usarli con Python (Esempi e Vantaggi su CSV)

Esercizio 3: Usiamo una lista di tuple perché la lista permette di aggiungere/rimuovere vendite (mutabile), mentre ogni vendita individuale è rappresentata come tupla perché i suoi dati non cambiano dopo la registrazione.

Esercizio 4: Vulnerabilità: le password sono salvate come hash semplice senza salt, vulnerabile a rainbow tables. In produzione servirebbe salt e algoritmi più sicuri.

Esercizio 5: Le tuple annidate sono preferibili alle classi quando:

  • I dati sono semplici e non hanno comportamenti (metodi)

  • Si vuole massimizzare le prestazioni

  • Si lavora con dati che devono essere serializzati facilmente

  • Si vuole usare l’unpacking per codice conciso

Esercizio 6: Le tuple sono hashable perché immutabili – Python può calcolare un hash fisso. Le liste sono mutabili, quindi il loro hash cambierebbe, rendendole inadatte come chiavi di dizionario.

💡 Analisi Applicativa degli Esercizi

Esercizio 1: Analisi Meteo Settimanale

  • Questo esercizio dimostra che l’immutabilità delle tuple non ne limita le operazioni di analisi. Nonostante non si possano modificare, le tuple supportano perfettamente funzioni come sum(), max(), min() e l’iterazione (for).

  • Commento Applicativo: Nella Data Analysis o nello sviluppo di report, i dati storici (come le temperature di una settimana) vengono spesso caricati in strutture immutabili per garantire che le statistiche (media, min, max) siano calcolate su un dataset non alterato.

Esercizio 2: Sistema di Coordinate

  •  È l’esempio canonico dell’uso delle tuple per i record fissi. Sottolinea l’importanza di accedere agli elementi tramite indicizzazione (punto_A[0]) e come l’immutabilità sia la rappresentazione digitale di un punto statico nello spazio.

  • Commento Applicativo: Essenziale in Geomatica, Robotica e Grafica 3D. Le coordinate x, y, z$ o i colori RGB sono quasi sempre rappresentati come tuple, poiché un punto (o un colore) è un’entità che non si modifica, garantendo l’integrità geometrica o cromatica.

Esercizio 3: Analisi Vendite Mensili

  • Introduce il pattern “Lista di Tuple” (List of Records). La lista è mutabile (perché l’elenco delle vendite può cambiare, con nuove transazioni aggiunte), ma ogni transazione individuale è una tupla (immutabile) per fissare i dati di quella specifica vendita (prodotto, quantità, prezzo). Dimostra inoltre l’uso avanzato di max() con l’argomento key=lambda x: x[1].

  • Commento Applicativo: Questo è un modello comune nel Backend Development e nei sistemi ERP/E-commerce. I CSV o i dati estratti da un JSON vengono spesso caricati in memoria come liste di tuple per l’elaborazione statistica.

Esercizio 4: Sistema di Autenticazione

  •  Il punto cruciale è l’uso delle tuple per i dati sensibili e di configurazione (username, hash, ruolo). L’immutabilità agisce come una prima barriera di integrità e sicurezza, impedendo modifiche accidentali a credenziali o ruoli.

  • Commento Applicativo: In qualunque sistema di Gestione Utenti (User Management), i record di accesso sono trattati come dati che, una volta creati, non devono essere modificati se non tramite procedure specifiche (es. cambio password). L’uso del hashlib rende l’esempio realistico (nonostante la nota sulla mancanza del salt indichi un’ulteriore consapevolezza di sicurezza).

Esercizio 5: Gestione Inventario con Tuple Annidate

  •  Questo è l’esempio più chiaro delle Tuple Annidate, dimostrando come le tuple possano rappresentare una struttura dati complessa e gerarchica (record di prodotto che contiene un record di prezzo che contiene una lista di record di magazzino/quantità). La gestione dei prezzi e degli sconti fissa la “scheda prodotto”.

  • Commento Applicativo: Molto usato nell’Informatica Gestionale e nei sistemi di Stock Management. Le tuple annidate sono una forma efficiente e Pythonica di rappresentare un “documento” o un “record gerarchico” in memoria, senza la complessità di una Classe (OOP) o di un dizionario.

Esercizio 6: Sistema di Punteggi per Torneo

  • L’esercizio di livello avanzato per eccellenza. Dimostra il superpotere hashable della tupla: l’uso di tuple come chiavi di dizionario (scontri_diretti[(casa, ospite)]). Poiché solo gli oggetti immutabili (hashable) possono essere chiavi, le tuple sono ideali per mappare relazioni fisse (coppie di squadre, coordinate, ecc.). L’algoritmo di ordinamento della classifica è un’ottima dimostrazione di sorted() con chiavi complesse (punti > differenza reti).

  • Commento Applicativo: Fondamentale in Algoritmi di Grafi e Sistemi di Cache. Quando è necessario memorizzare un risultato o una statistica relativa a una coppia di elementi fissa (es. rotta aerea tra due città, relazione tra due utenti), l’uso di una tupla (elemento1, elemento2) come chiave del dizionario è la soluzione più elegante ed efficiente in Python.

Pubblicità