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:
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 corrispondeIniziamo con un esempio semplice che dimostra il concetto fondamentale:
# 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 foundIn 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:
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 foundEntrambe le versioni producono lo stesso risultato. Tuttavia, match-case offre diversi vantaggi:
- Intento più chiaro: l’istruzione
matchmostra esplicitamente che stai controllando un valore rispetto a più possibilità - Meno ripetizioni: non ripeti il nome della variabile in ogni confronto
- Pattern più potenti: come vedremo,
match-casepuò fare molto più che semplici controlli di uguaglianza - 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:
# 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 completePoiché "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:
# 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 kitchenQuesto 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:
# 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: 403Il 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:
# 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 eventsSenza 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:
# 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 soonPuoi usare pattern letterali di tipi diversi, e match confronta sia il valore sia il suo tipo:
# 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 enabledI 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":
# 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 |:
# 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 action13.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:
# 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: 201La 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:
# 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 detected13.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:
# 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: saveIl 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:
# 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 realeOutput:
Some other status codeOra con una variabile di binding:
# 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 receivedLa 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.
# 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:
- Il soggetto
pointè la tupla(3, 7) - Python controlla ogni pattern case in ordine
- I primi tre pattern non corrispondono perché richiedono il valore
0in una posizione specifica, e la tupla(3, 7)non ha alcun elemento uguale a0 - Il pattern
(x, y)corrisponde perché è una tupla di due elementi - Python associa
xa3eya7 - Il blocco case viene eseguito con questi valori catturati
Ecco un altro esempio che mostra diversi pattern di tuple:
# 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 255Questo 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:
# 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 stepsIl 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:
# 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.98Il 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.
# 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 grantedIl 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:
# 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.
# 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: 42Questo 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:
# 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:
match subject:
case pattern if condition:
# Il codice viene eseguito solo se il pattern corrisponde E la condition è veraVediamo un esempio semplice:
# 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 grantedIn 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:
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:
# 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°COgni 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:
# 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% discountIl 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:
# 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: SatisfactoryLa 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:
# 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 privilegesQuesto 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.
# 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:
# 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 timeout13.5.2) Combinare pattern di sequenza con guardie
Puoi usare guardie con pattern di sequenza per aggiungere condizioni aggiuntive:
# 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.6Il 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.