Python & AI Tutorials Logo
Programmazione Python

13. Fare scelte con match e case (Pattern Matching Strutturale)

Quando il tuo programma deve prendere decisioni basate su più valori o pattern possibili, hai già imparato a usare le catene if-elif-else dal Capitolo 8. Python 3.10 ha introdotto una potente alternativa chiamata pattern matching strutturale (structural pattern matching) usando le istruzioni match e case. Questa funzionalità offre un modo più pulito ed espressivo per gestire scenari complessi di presa di decisioni.

Il pattern matching va oltre i semplici confronti di valori. Ti consente di fare match rispetto alla struttura e alla forma dei dati, estrarre valori da oggetti complessi ed esprimere decisioni multi-ramo in una forma più leggibile. Sebbene le catene if-elif-else funzionino perfettamente bene in molte situazioni, le istruzioni match-case brillano quando hai a che fare con più casi distinti, soprattutto quando lavori con dati strutturati.

13.1) Introduzione alle istruzioni match e case (Basandosi su if-elif dal Capitolo 8)

13.1.1) La struttura di base di match-case

Un’istruzione match esamina un valore (chiamato il soggetto (subject)) e lo confronta con uno o più pattern (patterns) definiti nelle clausole case. Quando un pattern corrisponde, Python esegue il blocco di codice associato a quel case.

Ecco la struttura di base:

python
match subject:
    case pattern1:
        # Codice da eseguire se pattern1 corrisponde
    case pattern2:
        # Codice da eseguire se pattern2 corrisponde
    case pattern3:
        # Codice da eseguire se pattern3 corrisponde

Iniziamo con un esempio semplice che dimostra il concetto fondamentale:

python
# Gestore semplice dei codici di stato HTTP
status_code = 404
 
match status_code:
    case 200:
        print("Success: Request completed")
    case 404:
        print("Error: Page not found")
    case 500:
        print("Error: Server error")

Output:

Error: Page not found

In questo esempio, l’istruzione match esamina status_code (il soggetto). Python controlla ogni pattern case in ordine. Quando trova che status_code è uguale a 404, esegue il blocco di codice corrispondente e poi esce dall’istruzione match. I case rimanenti non vengono controllati.

13.1.2) In cosa match-case differisce da if-elif-else

Potresti chiederti: "Non potrei scriverlo con if-elif-else?" Sì, potresti:

python
status_code = 404
 
if status_code == 200:
    print("Success: Request completed")
elif status_code == 404:
    print("Error: Page not found")
elif status_code == 500:
    print("Error: Server error")

Output:

Error: Page not found

Entrambe le versioni producono lo stesso risultato. Tuttavia, match-case offre diversi vantaggi:

  1. Intento più chiaro: l’istruzione match mostra esplicitamente che stai controllando un valore rispetto a più possibilità
  2. Meno ripetizioni: non ripeti il nome della variabile in ogni confronto
  3. Pattern più potenti: come vedremo, match-case può fare molto più che semplici controlli di uguaglianza
  4. Migliore leggibilità: per alberi decisionali complessi, match-case è spesso più facile da capire

13.1.3) Quando nessun pattern corrisponde

Cosa succede se nessuno dei pattern corrisponde? L’istruzione match si completa semplicemente senza eseguire alcun blocco case:

python
# Verifica del ruolo utente
user_role = "guest"
 
match user_role:
    case "admin":
        print("Full system access granted")
    case "moderator":
        print("Content management access granted")
    case "editor":
        print("Editing access granted")
 
print("Role check complete")

Output:

Role check complete

Poiché "guest" non corrisponde a nessuno dei pattern, non viene eseguito alcun blocco case. Il programma continua con il codice dopo l’istruzione match. Questo comportamento è importante da capire: a differenza delle catene if-elif-else dove puoi aggiungere una clausola finale else per catturare tutti gli altri casi, un’istruzione match di base senza un pattern “prendi tutto” non farà nulla in modo silenzioso se nessun pattern corrisponde.

13.1.4) Esempio pratico: sistema di selezione del menu

Costruiamo un esempio più completo che dimostra la chiarezza di match-case per gestire le scelte dell’utente:

python
# Sistema di ordinazione al ristorante
menu_choice = 3
 
match menu_choice:
    case 1:
        item = "Caesar Salad"
        price = 8.99
        print(f"You ordered: {item} - ${price}")
    case 2:
        item = "Grilled Chicken"
        price = 14.99
        print(f"You ordered: {item} - ${price}")
    case 3:
        item = "Vegetable Pasta"
        price = 12.99
        print(f"You ordered: {item} - ${price}")
    case 4:
        item = "Chocolate Cake"
        price = 6.99
        print(f"You ordered: {item} - ${price}")
 
print("Order submitted to kitchen")

Output:

You ordered: Vegetable Pasta - $12.99
Order submitted to kitchen

Questo esempio mostra come ogni case possa contenere più istruzioni. Quando menu_choice corrisponde a 3, Python esegue tutte e tre le righe in quel blocco case: assegnare item, assegnare price e stampare la conferma dell’ordine.

13.2) Usare il jolly _, i pattern letterali e i pattern multipli

13.2.1) Il pattern jolly: catturare tutto il resto

Il carattere di sottolineatura _ è un pattern speciale che corrisponde a qualsiasi cosa. Tipicamente viene usato come ultimo case per gestire tutti i valori che non hanno corrisposto ai pattern precedenti—simile a una clausola finale else in una catena if-elif-else:

python
# Gestore dei codici di stato HTTP con case predefinito
status_code = 403
 
match status_code:
    case 200:
        print("Success: Request completed")
    case 404:
        print("Error: Page not found")
    case 500:
        print("Error: Server error")
    case _:
        print(f"Unhandled status code: {status_code}")

Output:

Unhandled status code: 403

Il pattern _ agisce come un “prendi tutto”. Poiché 403 non corrisponde a nessuno dei case specifici, il pattern jolly corrisponde ed esegue il suo blocco. Il pattern jolly corrisponderà a qualsiasi valore, quindi dovrebbe essere sempre posizionato per ultimo—eventuali case dopo di esso non verrebbero mai eseguiti.

Ecco perché il jolly è utile nella pratica:

python
# Pianificatore dei giorni della settimana
day = "Saturday"
 
match day:
    case "Monday":
        print("Team meeting at 9 AM")
    case "Wednesday":
        print("Project review at 2 PM")
    case "Friday":
        print("Weekly report due")
    case _:
        print(f"{day}: No scheduled events")

Output:

Saturday: No scheduled events

Senza il pattern jolly, se day fosse "Saturday", "Sunday" o qualsiasi altro valore, l’istruzione match si completerebbe in modo silenzioso senza output. Il jolly assicura che tu gestisca i casi inattesi o non specificati in modo corretto.

13.2.2) Pattern letterali: fare match su valori specifici

I pattern letterali (literal patterns) corrispondono a valori esatti. Li stiamo già usando—numeri, stringhe e valori booleani sono tutti pattern letterali:

python
# Controller del semaforo
light_color = "yellow"
 
match light_color:
    case "green":
        print("Go")
    case "yellow":
        print("Caution: Light changing soon")
    case "red":
        print("Stop")
    case _:
        print("Invalid light color")

Output:

Caution: Light changing soon

Puoi usare pattern letterali di tipi diversi, e match confronta sia il valore sia il suo tipo:

python
# Validatore di configurazione (usando tipi letterali diversi)
setting_value = True
 
match setting_value:
    case True:        # letterale booleano
        print("Feature enabled")
    case False:       # letterale booleano
        print("Feature disabled")
    case None:        # letterale None
        print("Feature not configured")
    case 0:           # letterale intero
        print("Feature explicitly turned off")
    case "auto":      # letterale stringa
        print("Feature set to automatic mode")
    case _:
        print("Invalid configuration value")

Output:

Feature enabled

I pattern letterali funzionano con interi, float, stringhe, booleani e None. Python controlla l’uguaglianza usando le stesse regole dell’operatore ==.

13.2.3) Pattern multipli con l’operatore OR

A volte vuoi eseguire lo stesso codice per diversi valori. Puoi combinare più pattern usando l’operatore | (pipe), che significa "oppure":

python
# Rilevatore di weekend
day = "Saturday"
 
match day:
    case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
        print("It's a weekday - time to work!")
    case "Saturday" | "Sunday":
        print("It's the weekend - time to relax!")
    case _:
        print("Invalid day name")

Output:

It's the weekend - time to relax!

L’operatore | ti consente di specificare più pattern che dovrebbero attivare la stessa azione. Se il soggetto corrisponde a uno qualsiasi dei pattern separati da |, quel case viene eseguito. Questo è molto più pulito che scrivere case separati con blocchi di codice identici.

Puoi mescolare tipi diversi di pattern con |:

python
# Validatore di input per domande sì/no
response = "yes"
 
match response:
    case True | "yes":
        print("You confirmed the action")
    case False | "no":
        print("You cancelled the action")
    case _:
        print("Please answer yes or no")

Output:

You confirmed the action

13.2.4) Catturare quale alternativa ha corrisposto con as

Quando usi pattern multipli con |, potresti voler sapere quale valore specifico ha corrisposto. Puoi usare la parola chiave as per catturare il valore corrispondente:

python
# Gestore dei codici di stato con risposte raggruppate
status = 201
 
match status:
    case 200 | 201 | 202 | 204 as success_code:
        print(f"Success: {success_code}")
    case 400 | 401 | 403 | 404 as client_error:
        print(f"Client error: {client_error}")
    case 500 | 502 | 503 as server_error:
        print(f"Server error: {server_error}")
    case _:
        print("Unknown status code")

Output:

Success: 201

La parola chiave as crea una variabile di binding che cattura quale alternativa ha corrisposto. In questo esempio, success_code è associata a 201 perché è il valore specifico che ha corrisposto tra le alternative 200 | 201 | 202 | 204.

Ecco un altro esempio che mostra come questo sia utile per il logging:

python
# Processore dei livelli di log
log_level = "WARN"
 
match log_level:
    case "DEBUG" | "TRACE" as level:
        print(f"Verbose logging: {level}")
        print("Detailed diagnostic information will be recorded")
    case "INFO" | "NOTICE" as level:
        print(f"Informational: {level}")
        print("Normal operation messages will be recorded")
    case "WARN" | "WARNING" as level:
        print(f"Warning level: {level}")
        print("Potential issues detected")
    case "ERROR" | "FATAL" | "CRITICAL" as level:
        print(f"Error level: {level}")
        print("Immediate attention required")
    case _:
        print("Unknown log level")

Output:

Warning level: WARN
Potential issues detected

13.3) Estrarre valori con variabili di binding

13.3.1) Cosa sono le variabili di binding?

Finora, abbiamo fatto match su valori letterali. Ma il pattern matching diventa davvero potente quando puoi catturare (capture) o estrarre (extract) parti dei dati su cui stai facendo match. Una variabile di binding (binding variable) (chiamata anche pattern di cattura (capture pattern)) è un nome in un pattern che cattura il valore corrispondente e lo rende disponibile nel blocco case.

Ecco un esempio semplice:

python
# Cattura semplice di un valore
command = "save"
 
match command:
    case "quit":
        print("Exiting program")
    case action:  # Questa è una variabile di binding
        print(f"Executing action: {action}")

Output:

Executing action: save

Il pattern action è una variabile di binding. Corrisponde a qualsiasi valore (come il jolly _), ma a differenza di _, cattura quel valore e lo assegna al nome action. All’interno del blocco case, puoi usare action per riferirti al valore corrispondente.

Distinzione importante: una variabile di binding corrisponde a qualsiasi cosa, proprio come _. La differenza è che _ scarta il valore, mentre una variabile di binding lo cattura per usarlo nel blocco case.

13.3.2) Variabili di binding vs jolly

Confrontiamo direttamente variabili di binding e jolly:

python
# Uso del jolly - il valore non viene catturato
status = 403
 
match status:
    case 200:
        print("Success")
    case _:
        print("Some other status code")  # Non puoi accedere al valore reale

Output:

Some other status code

Ora con una variabile di binding:

python
# Uso della variabile di binding - il valore viene catturato
status = 403
 
match status:
    case 200:
        print("Success")
    case code:  # La variabile di binding cattura il valore
        print(f"Status code {code} received")

Output:

Status code 403 received

La variabile di binding code cattura il valore 403, rendendolo disponibile all’interno del blocco case. Questo è utile quando devi lavorare con il valore reale che non ha corrisposto ai tuoi pattern specifici.

13.3.3) Fare match su pattern di tuple ed estrarre i componenti

Il pattern matching diventa particolarmente potente con dati strutturati come le tuple. Puoi fare match sulla forma di una tupla ed estrarne i componenti simultaneamente. Anche se studieremo le tuple in dettaglio nel Capitolo 15, questo esempio si concentra solo su come funzionano i pattern di tuple nelle istruzioni match.

python
# Sistema di coordinate - match su un pattern di tupla
point = (3, 7)
 
match point:
    case (0, 0):
        print("Origin point")
    case (0, y):  # Corrisponde a qualsiasi punto sull'asse y
        print(f"On y-axis at y={y}")
    case (x, 0):  # Corrisponde a qualsiasi punto sull'asse x
        print(f"On x-axis at x={x}")
    case (x, y):  # Corrisponde a qualsiasi altro punto
        print(f"Point at coordinates ({x}, {y})")

Output:

Point at coordinates (3, 7)

Scomponiamo cosa sta succedendo:

  1. Il soggetto point è la tupla (3, 7)
  2. Python controlla ogni pattern case in ordine
  3. I primi tre pattern non corrispondono perché richiedono il valore 0 in una posizione specifica, e la tupla (3, 7) non ha alcun elemento uguale a 0
  4. Il pattern (x, y) corrisponde perché è una tupla di due elementi
  5. Python associa x a 3 e y a 7
  6. Il blocco case viene eseguito con questi valori catturati

Ecco un altro esempio che mostra diversi pattern di tuple:

python
# Analizzatore di colori RGB
color = (255, 0, 0)
 
match color:
    case (0, 0, 0):
        print("Black")
    case (255, 255, 255):
        print("White")
    case (r, 0, 0):  # Rosso puro con intensità variabile
        print(f"Pure red with intensity {r}")
    case (0, g, 0):  # Verde puro
        print(f"Pure green with intensity {g}")
    case (0, 0, b):  # Blu puro
        print(f"Pure blue with intensity {b}")
    case (r, g, b):  # Qualsiasi altro colore
        print(f"RGB color: red={r}, green={g}, blue={b}")

Output:

Pure red with intensity 255

Questo input corrisponde al pattern (r, 0, 0) perché la tupla ha tre elementi, gli ultimi due sono 0, e il primo valore viene associato a r.

13.3.4) Fare match su pattern di liste

Puoi anche fare match su pattern di liste ed estrarne gli elementi. Tratteremo le liste in dettaglio nel Capitolo 14; per ora, questo esempio si concentra su come funzionano i pattern di liste nelle istruzioni match:

python
# Comando con argomenti
command = ["move", "north", "5"]
 
match command:
    case ["quit"]:
        print("Exiting game")
    case ["look"]:
        print("You look around the room")
    case ["move", direction]:
        print(f"Moving {direction}")
    case ["move", direction, distance]:
        print(f"Moving {direction} for {distance} steps")
    case _:
        print("Unknown command")

Output:

Moving north for 5 steps

Il pattern ["move", direction, distance] corrisponde a una lista di tre elementi in cui il primo elemento è "move". Cattura il secondo elemento come direction e il terzo come distance.

Ecco un esempio pratico con lunghezze di lista variabili:

python
# Processore di elementi del carrello
item = ["laptop", 999.99, 2]
 
match item:
    case [name]:  # Elemento con solo un nome
        print(f"Item: {name} (no price or quantity specified)")
    case [name, price]:  # Elemento con nome e prezzo
        print(f"Item: {name}, Price: ${price}, Quantity: 1 (default)")
    case [name, price, quantity]:  # Informazioni complete dell'elemento
        total = price * quantity
        print(f"Item: {name}, Price: ${price}, Quantity: {quantity}")
        print(f"Subtotal: ${total}")
    case _:
        print("Invalid item format")

Output:

Item: laptop, Price: $999.99, Quantity: 2
Subtotal: $1999.98

Il case [name, price, quantity] viene eseguito perché la lista ha esattamente tre elementi, e ogni elemento viene associato alla sua variabile corrispondente.

13.3.5) Fare match su pattern di dizionari

Il pattern matching funziona anche con i dizionari, permettendoti di fare match su chiavi specifiche ed estrarne i valori. Anche se studieremo i dizionari in dettaglio nel Capitolo 16, questa sezione si concentra solo su come funzionano i pattern di dizionari nelle istruzioni match.

python
# Processore del profilo utente
user = {"name": "Alice", "role": "admin", "active": True}
 
match user:
    case {"role": "admin", "active": True}:
        print("Active administrator - full access granted")
    case {"role": "admin", "active": False}:
        print("Inactive administrator - access suspended")
    case {"role": role, "active": True}:  # Cattura il valore di role
        print(f"Active user with role: {role}")
    case {"role": role, "active": False}:
        print(f"Inactive user with role: {role}")
    case _:
        print("Invalid user profile")

Output:

Active administrator - full access granted

Il case {"role": "admin", "active": True} viene eseguito perché i pattern di dizionario richiedono la corrispondenza di coppie chiave–valore, e questa corrispondenza esatta viene controllata prima dei pattern più generali.

I pattern di dizionario sono flessibili: corrispondono se le chiavi specificate esistono con i valori specificati, anche se il dizionario ha chiavi aggiuntive:

python
# Gestore della risposta API
response = {"status": "success", "data": {"id": 123, "name": "Product"}, "timestamp": "2025-12-17"}
 
match response:
    case {"status": "error", "message": msg}:
        print(f"Error occurred: {msg}")
    case {"status": "success", "data": data}:
        print(f"Success! Data received: {data}")
    case _:
        print("Unknown response format")

Output:

Success! Data received: {'id': 123, 'name': 'Product'}

Il pattern {"status": "success", "data": data} corrisponde anche se il dizionario ha una chiave "timestamp" aggiuntiva. Il pattern richiede solo che le chiavi specificate esistano con i valori specificati (o pattern).

13.3.6) Combinare letterali e variabili di binding

Puoi mescolare pattern letterali e variabili di binding per creare logiche di matching sofisticate: A differenza degli esempi precedenti con tuple che si concentravano sul matching di struttura e posizione, questo esempio mostra come valori letterali e variabili di binding possano essere combinati per implementare una logica decisionale reale.

python
# Router di richieste HTTP
request = ("GET", "/api/users", 42)
 
match request:
    case ("GET", "/", None):
        print("Homepage request")
    case ("GET", path, None):
        print(f"GET request for: {path}")
    case ("POST", path, data):
        print(f"POST request to {path} with data: {data}")
    case ("GET", path, user_id):
        print(f"GET request for {path} with user ID: {user_id}")
    case _:
        print("Unsupported request type")

Output:

GET request for /api/users with user ID: 42

Questo esempio mostra come puoi fare match su valori specifici (come "GET") catturandone altri (come path e user_id) nello stesso pattern.

13.3.7) Esempio pratico: gestore di eventi

Costruiamo un esempio completo che dimostra la potenza delle variabili di binding:

python
# Gestore di eventi di gioco
event = ("player_move", {"x": 10, "y": 5, "speed": 2})
 
match event:
    case ("player_move", {"x": x, "y": y}):
        print(f"Player moved to position ({x}, {y})")
    case ("player_attack", {"target": target, "damage": damage}):
        print(f"Player attacked {target} for {damage} damage")
    case ("item_pickup", {"item": item_name}):
        print(f"Player picked up: {item_name}")
    case ("game_over", {"score": final_score}):
        print(f"Game ended. Final score: {final_score}")
    case (event_type, data):
        print(f"Unknown event type: {event_type}")
        print(f"Event data: {data}")

Output:

Player moved to position (10, 5)

Questo gestore di eventi fa match su tuple contenenti un tipo di evento e un dizionario di dati dell’evento. Estrae valori specifici dal dizionario in base al tipo di evento, rendendo semplice elaborare diversi tipi di eventi con codice pulito e leggibile.

13.4) Aggiungere condizioni extra con la guardia if

13.4.1) Cosa sono le guardie?

A volte devi fare match su un pattern e verificare una condizione aggiuntiva. Una guardia if (if guard) è una condizione extra che puoi aggiungere a un pattern case usando la parola chiave if. Il case corrisponde solo se sia il pattern corrisponde sia la condizione della guardia è vera.

Ecco la sintassi:

python
match subject:
    case pattern if condition:
        # Il codice viene eseguito solo se il pattern corrisponde E la condition è vera

Vediamo un esempio semplice:

python
# Controllo accessi basato sull'età
age = 16
 
match age:
    case age if age >= 18:
        print("Adult - full access granted")
    case age if age >= 13:
        print("Teen - limited access granted")
    case age if age >= 0:
        print("Child - parental supervision required")
    case _:
        print("Invalid age")

Output:

Teen - limited access granted

In questo esempio, la variabile di binding age cattura il valore e la guardia if age >= 13 aggiunge una condizione ulteriore. Il case corrisponde solo se il valore è 13 o maggiore. Poiché age è 16, il secondo case corrisponde e viene eseguito.

13.4.2) Come vengono valutate le guardie

Capire l’ordine di valutazione è importante. Ecco una visualizzazione dettagliata che mostra come le guardie interagiscono con il pattern matching:

No

No

No

Controlla il pattern del case

Il pattern corrisponde?

Prova il case successivo

Valuta la condizione della guardia

La guardia è True?

Esegui il blocco del case

Esci dall'istruzione match

Altri case?

Nessun match trovato - continua dopo match

Python controlla prima se il pattern corrisponde. Solo se il pattern corrisponde, Python valuta la condizione della guardia. Se la guardia è falsa, Python passa al case successivo—anche se il pattern corrispondeva.

Ecco un esempio che lo dimostra:

python
# Sistema di avvisi per la temperatura
temperature = 25
 
match temperature:
    case temp if temp > 35:
        print(f"Extreme heat warning: {temp}°C")
    case temp if temp > 30:
        print(f"High temperature alert: {temp}°C")
    case temp if temp > 20:
        print(f"Comfortable temperature: {temp}°C")
    case temp if temp > 10:
        print(f"Cool temperature: {temp}°C")
    case temp:
        print(f"Cold temperature: {temp}°C")

Output:

Comfortable temperature: 25°C

Ogni case usa la variabile di binding temp per catturare il valore della temperatura, poi applica una guardia per verificare se rientra in un certo intervallo. I case vengono controllati in ordine, quindi viene eseguito il primo case che corrisponde con una guardia vera.

13.4.3) Guardie con pattern letterali

Puoi combinare guardie con pattern letterali per creare matching più specifici:

python
# Calcolatore di sconti basato su tipo di articolo e quantità
item = ("book", 5)
 
match item:
    case ("book", quantity) if quantity >= 10:
        discount = 0.20  # Sconto del 20% per 10+ libri
        print(f"Bulk book order: {quantity} books, {discount*100}% discount")
    case ("book", quantity) if quantity >= 5:
        discount = 0.10  # Sconto del 10% per 5-9 libri
        print(f"Book order: {quantity} books, {discount*100}% discount")
    case ("book", quantity):
        discount = 0.0  # Nessuno sconto per meno di 5 libri
        print(f"Book order: {quantity} books, no discount")
    case (item_type, quantity):
        print(f"Order: {quantity} {item_type}(s)")

Output:

Book order: 5 books, 10.0% discount

Il pattern ("book", quantity) corrisponde a una tupla in cui il primo elemento è "book". La guardia if quantity >= 5 aggiunge la condizione che la quantità deve essere almeno 5.

13.4.4) Guardie con condizioni complesse

Le guardie possono usare qualsiasi espressione booleana, incluse condizioni complesse con and, or e not:

python
# Valutatore dei voti studenteschi con considerazione della frequenza
student = {"name": "Bob", "grade": 85, "attendance": 75}
 
match student:
    case {"grade": g, "attendance": a} if g >= 90 and a >= 90:
        status = "Excellent"
        print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
    case {"grade": g, "attendance": a} if g >= 80 and a >= 80:
        status = "Good"
        print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
    case {"grade": g, "attendance": a} if g >= 70 and a >= 70:
        status = "Satisfactory"
        print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
    case {"grade": g, "attendance": a} if g >= 60 or a >= 60:
        status = "Needs Improvement"
        print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
    case _:
        print("Failing - immediate intervention required")

Output:

Grade: 85, Attendance: 75% - Status: Satisfactory

La guardia if g >= 70 and a >= 70 richiede che sia il voto sia la frequenza siano almeno 70. Poiché Bob ha un voto di 85 e una frequenza del 75%, questo case corrisponde.

13.4.5) Esempio pratico: sistema di autenticazione utente

Costruiamo un esempio completo che usa guardie per implementare un sistema di autenticazione realistico:

python
# Autenticazione utente con accesso basato sul ruolo
user = {"username": "alice", "role": "admin", "active": True, "login_attempts": 0}
 
match user:
    case {"active": False}:
        print("Account suspended - contact administrator")
    case {"login_attempts": attempts} if attempts >= 3:
        print("Account locked due to too many failed login attempts")
    case {"role": "admin", "active": True}:
        print("Admin access granted - full system privileges")
    case {"role": "moderator", "active": True}:
        print("Moderator access granted - content management privileges")
    case {"role": role, "active": True} if role in ["editor", "author"]:
        print(f"{role.capitalize()} access granted - content creation privileges")
    case {"role": "user", "active": True}:
        print("User access granted - basic privileges")
    case _:
        print("Access denied - invalid user profile")

Output:

Admin access granted - full system privileges

Questo esempio dimostra come le guardie possano implementare una logica di business complessa. Il sistema controlla più condizioni: stato dell’account, tentativi di login e permessi basati sul ruolo. Ogni case gestisce uno scenario specifico, rendendo la logica di autenticazione chiara e manutenibile.

13.5) Fare match su forme semplici di sequenze e mapping

13.5.1) Fare match su sequenze a lunghezza variabile

A volte devi fare match su sequenze di lunghezze variabili. Il pattern matching di Python lo supporta con l’operatore *, che cattura zero o più elementi.

python
# Parser di comandi con argomenti variabili
command = ["copy", "file1.txt", "file2.txt", "file3.txt", "backup/"]
 
match command:
    case ["help"]:
        print("Available commands: copy, move, delete")
    case ["copy", source, destination]:
        print(f"Copying {source} to {destination}")
    case ["copy", *sources, destination]:
        print(f"Copying {len(sources)} files to {destination}")
        print(f"Source files: {sources}")
    case ["delete", *files]:
        print(f"Deleting {len(files)} file(s): {files}")
    case _:
        print("Unknown command")

Output:

Copying 3 files to backup/
Source files: ['file1.txt', 'file2.txt', 'file3.txt']

Il pattern ["copy", *sources, destination] corrisponde a una lista che inizia con "copy", termina con una destinazione e ha un numero qualsiasi di file sorgente nel mezzo. *sources cattura tutti gli elementi centrali come una lista.

Importante: puoi usare un solo pattern * per pattern di sequenza, e cattura gli elementi come una lista:

python
# Parser di voci di log
log_entry = ["2025-12-17", "10:30:45", "ERROR", "Database", "connection", "timeout"]
 
match log_entry:
    case [date, time, "ERROR", *error_details]:
        print(f"Error on {date} at {time}")
        print(f"Error details: {' '.join(error_details)}")
    case [date, time, "WARNING", *warning_details]:
        print(f"Warning on {date} at {time}")
        print(f"Warning details: {' '.join(warning_details)}")
    case [date, time, level, *message]:
        print(f"{level} on {date} at {time}: {' '.join(message)}")

Output:

Error on 2025-12-17 at 10:30:45
Error details: Database connection timeout

13.5.2) Combinare pattern di sequenza con guardie

Puoi usare guardie con pattern di sequenza per aggiungere condizioni aggiuntive:

python
# Analizzatore di liste di voti
grades = [85, 92, 78, 95, 88]
 
match grades:
    case []:
        print("No grades recorded")
    case [grade] if grade >= 90:
        print(f"Single excellent grade: {grade}")
    case [grade] if grade < 60:
        print(f"Single failing grade: {grade}")
    case [*all_grades] if len(all_grades) >= 5 and sum(all_grades) / len(all_grades) >= 90:
        average = sum(all_grades) / len(all_grades)
        print(f"Excellent performance! Average: {average:.1f}")
    case [*all_grades] if len(all_grades) >= 5:
        average = sum(all_grades) / len(all_grades)
        print(f"Performance review: {len(all_grades)} grades, Average: {average:.1f}")
    case [*all_grades]:
        print(f"Insufficient data: only {len(all_grades)} grade(s)")

Output:

Performance review: 5 grades, Average: 87.6

Il pattern [*all_grades] cattura tutti gli elementi nella lista, e la guardia controlla sia la lunghezza sia calcola la media per determinare il messaggio appropriato.


Il pattern matching con match e case offre un modo potente ed espressivo per gestire la presa di decisioni complessa in Python. Dal semplice matching di valori a sofisticati pattern strutturali con guardie, questa funzionalità ti consente di scrivere codice più pulito e più manutenibile per gestire più casi ed estrarre dati da strutture complesse.

Man mano che continui a imparare Python, scoprirai che il pattern matching completa la logica condizionale che hai imparato nel Capitolo 8, offrendo un’alternativa elegante quando devi gestire più casi distinti, soprattutto quando lavori con dati strutturati. La chiave è scegliere lo strumento giusto per ogni situazione: usa if-elif-else per condizioni semplici e logica booleana, e ricorri a match-case quando stai controllando un valore rispetto a più possibilità o quando lavori con dati strutturati che richiedono un’estrazione basata su pattern.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai