Istruzione match in Python
Impara il pattern matching strutturale in Python con match/case: letterali, sequenze, mapping, classi, guardie e wildcard — con esempi.
Python 3.10 ha introdotto il pattern matching strutturale tramite l'istruzione match — un modo potente per ramificare in base alla forma e al contenuto dei dati, non solo all'uguaglianza. Questo capitolo copre tutto, dalla sintassi base di match/case a pattern avanzati come il destructuring di sequenze, i pattern di mapping, i pattern di classe, le guardie e i casi d'uso reali.
Prima di leggere questo capitolo dovresti avere familiarità con if/else in Python, le funzioni Python e le strutture dati di base (liste, tuple, dizionari).
Cos'è il Pattern Matching Strutturale?
Il pattern matching strutturale ti permette di ispezionare la struttura di un oggetto — il suo tipo, i valori dei suoi campi, la forma di una sequenza — e di eseguire codice diverso a seconda del pattern che corrisponde. Va ben oltre un semplice controllo if x == y.
Considera il routing di un codice di stato HTTP. Con catene if/elif si scrive:
if status == 200:
print("OK")
elif status == 404:
print("Not Found")
elif status == 500:
print("Internal Server Error")
else:
print("Unknown status")Con match l'intenzione è più chiara:
match status:
case 200:
print("OK")
case 404:
print("Not Found")
case 500:
print("Internal Server Error")
case _:
print("Unknown status")Il vero vantaggio emerge quando il soggetto è un oggetto complesso — una tupla, un dizionario o una dataclass — e vuoi destrutturarlo mentre effettui il matching.
Sintassi di Base
match subject:
case pattern1:
# runs if subject matches pattern1
case pattern2:
# runs if subject matches pattern2
case _:
# wildcard — runs if nothing else matchedRegole da ricordare:
matchecasesono soft keyword — sono keyword solo in questo contesto e possono ancora essere usate come nomi di variabili altrove nel codice.- Ogni blocco
caseviene provato in ordine; il primo pattern che corrisponde vince e gli altri vengono saltati. - Il blocco
case _:è il wildcard — corrisponde sempre e funge da default catch-all. - È richiesto Python 3.10+. Eseguire questo codice su Python 3.9 o versioni precedenti solleva un
SyntaxError.
Pattern Letterali
Il pattern più semplice corrisponde a un valore concreto: un numero, una string, True, False o None.
def http_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Unknown status"
print(http_status(200)) # OK
print(http_status(404)) # Not Found
print(http_status(999)) # Unknown statusPattern OR (|)
Usa | all'interno di un case per corrispondere a uno qualsiasi tra diversi letterali:
def is_vowel(letter):
match letter.lower():
case "a" | "e" | "i" | "o" | "u":
return True
case _:
return False
print(is_vowel("a")) # True
print(is_vowel("b")) # False
print(is_vowel("E")) # TrueI pattern OR funzionano anche con numeri, None e altri tipi letterali.
Pattern di Cattura
Un pattern di cattura è un nome semplice (non un letterale string, non un nome con punti) che corrisponde a qualsiasi cosa e associa il valore corrispondente a quel nome per l'uso nel corpo:
def greet(name):
match name:
case "Alice":
return "Hello, Alice!"
case other: # captures whatever was passed
return f"Hello, {other}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob")) # Hello, Bob!other nell'esempio sopra è un pattern di cattura — associa il valore corrispondente alla variabile locale other. Assomiglia molto al wildcard _, ma _ scarta il valore mentre una cattura con nome lo mantiene.
A bare name in a case is always a capture, never a comparison. If you want to compare against a constant defined elsewhere, use a dotted name like Status.OK or wrap it in a guard (case x if x == my_constant:).
Pattern di Sequenza
Un pattern di sequenza corrisponde a liste, tuple o qualsiasi sequenza, e può destrutturare gli elementi in variabili allo stesso tempo.
def process_point(point):
match point:
case (0, 0):
return "Origin"
case (x, 0):
return f"On x-axis at {x}"
case (0, y):
return f"On y-axis at {y}"
case (x, y):
return f"Point at ({x}, {y})"
print(process_point((0, 0))) # Origin
print(process_point((5, 0))) # On x-axis at 5
print(process_point((0, 3))) # On y-axis at 3
print(process_point((2, 4))) # Point at (2, 4)Usare * per Catturare il Resto
Un *name all'interno di un pattern di sequenza raccoglie gli elementi rimanenti, proprio come l'unpacking iterabile:
def describe_list(items):
match items:
case []:
return "empty list"
case [single]:
return f"one item: {single}"
case [first, *rest]:
return f"starts with {first!r}, then {len(rest)} more item(s)"
print(describe_list([])) # empty list
print(describe_list([42])) # one item: 42
print(describe_list([1, 2, 3, 4])) # starts with 1, then 3 more item(s)Usa [first, *_] se vuoi catturare solo il primo elemento e scartare il resto.
Pattern di Mapping
Un pattern di mapping corrisponde ai dizionari (o a qualsiasi Mapping). Specifichi solo le chiavi che ti interessano — le chiavi extra nel soggetto vengono ignorate.
def process_event(event):
match event:
case {"type": "click", "button": button}:
return f"Mouse click: button {button}"
case {"type": "keypress", "key": key}:
return f"Key pressed: {key!r}"
case {"type": action}:
return f"Other event: {action}"
case _:
return "Unknown event"
print(process_event({"type": "click", "button": 1}))
# Mouse click: button 1
print(process_event({"type": "keypress", "key": "Enter"}))
# Key pressed: 'Enter'
print(process_event({"type": "resize", "width": 800}))
# Other event: resize
print(process_event({}))
# Unknown eventPunto chiave: un pattern di mapping non fallisce mai a causa di chiavi extra nel soggetto. {"type": "click", "button": button} corrisponde anche se l'evento contiene anche le coordinate "x" e "y".
Per catturare le coppie chiave/valore rimanenti, usa **rest:
match event:
case {"type": "click", **rest}:
print(f"Click event with extra data: {rest}")Pattern di Classe
Un pattern di classe corrisponde a un'istanza di una classe specifica ed estrae i suoi attributi. Questo è particolarmente utile con le dataclass, poiché i loro attributi sono esposti per nome automaticamente.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
def describe_shape(shape):
match shape:
case Point(x=0, y=0):
return "Point at origin"
case Point(x=x, y=y):
return f"Point at ({x}, {y})"
case Circle(center=Point(x=cx, y=cy), radius=r):
return f"Circle centered at ({cx}, {cy}) with radius {r}"
case _:
return "Unknown shape"
print(describe_shape(Point(0, 0))) # Point at origin
print(describe_shape(Point(3, 4))) # Point at (3, 4)
print(describe_shape(Circle(Point(1, 2), 5)))# Circle centered at (1, 2) with radius 5Nota il pattern di classe annidato nel caso Circle: Point(x=cx, y=cy) viene confrontato all'interno del pattern Circle. I pattern possono essere composti in modo arbitrariamente profondo.
Per i tipi built-in come int, str, float e bool puoi usare pattern posizionali con un singolo argomento:
def handle_input(value):
match value:
case (int() | float()) as number:
return f"Got a number: {number}"
case str() as text:
return f"Got text: {text!r}"
case _:
return "Unknown type"
print(handle_input(3.14)) # Got a number: 3.14
print(handle_input("hello")) # Got text: 'hello'
print(handle_input([1, 2])) # Unknown typeLa keyword as (il pattern AS) associa l'intero valore corrispondente a un nome, anche dopo un controllo del tipo.
Guardie
Una guardia è una condizione if aggiunta dopo un pattern. Il case corrisponde solo quando il pattern si adatta e la guardia restituisce True.
def classify_number(n):
match n:
case 0:
return "zero"
case x if x < 0:
return f"{x} is negative"
case x if x % 2 == 0:
return f"{x} is positive and even"
case x:
return f"{x} is positive and odd"
print(classify_number(0)) # zero
print(classify_number(-5)) # -5 is negative
print(classify_number(4)) # 4 is positive and even
print(classify_number(7)) # 7 is positive and oddLe guardie vengono valutate dopo che il pattern strutturale corrisponde, quindi le variabili catturate sono disponibili al loro interno. Una guardia fallita non impedisce ai casi successivi di essere provati.
Il Pattern Wildcard _
_ è il catch-all universale. Corrisponde a qualsiasi valore e non associa nulla (il valore viene scartato). Dovrebbe essere l'ultimo case in un blocco match. Senza di esso, un match che non trova nessun case corrispondente semplicemente non fa nulla — non viene sollevato nessun errore.
def describe(value):
match value:
case 0:
return "zero"
case _:
return f"something else: {value!r}"
print(describe(0)) # zero
print(describe(99)) # something else: 99
print(describe("hi")) # something else: 'hi'_ può comparire anche all'interno di un pattern per ignorare parti specifiche:
match point:
case (_, 0):
print("On the x-axis (x value doesn't matter)")
case (0, _):
print("On the y-axis (y value doesn't matter)")Combinare i Pattern: un Esempio Reale
I pattern sopra si compongono. Ecco un parser di comandi per un gioco testuale che combina pattern di sequenza, guardie e wildcard:
def run_command(command):
match command.split():
case ["quit"]:
return "Quitting"
case ["go", direction] if direction in ("north", "south", "east", "west"):
return f"Going {direction}"
case ["go", direction]:
return f"Cannot go {direction!r} — try north, south, east, or west"
case ["get", item]:
return f"Picking up {item}"
case ["drop", item]:
return f"Dropping {item}"
case ["inventory"]:
return "Checking inventory"
case [verb, *args]:
return f"Unknown command {verb!r} with args {args}"
case []:
return "No command entered"
print(run_command("go north")) # Going north
print(run_command("go up")) # Cannot go 'up' — try north, south, east, or west
print(run_command("get sword")) # Picking up sword
print(run_command("drop torch")) # Dropping torch
print(run_command("quit")) # Quitting
print(run_command("")) # No command enteredLeggendo dall'alto verso il basso puoi capire immediatamente ogni comando supportato — qualcosa che richiederebbe molte più righe di logica if/elif per ottenere la stessa chiarezza.
match vs. if/elif — Quando Usare Ciascuno
| Scenario | Scelta migliore |
|---|---|
| Semplice uguaglianza contro poche costanti | Entrambi; match è leggermente più pulito |
| Matching sulla struttura/forma dei dati | match — molto più pulito |
| Destrutturare valori durante il matching | match — non è possibile con if |
| Logica che coinvolge solo condizioni calcolate | if/elif |
| Python 3.9 o versioni precedenti | if/elif (nessun match disponibile) |
| Esprimere chiaramente una tabella decisionale | match |
match non è un sostituto per ogni catena if. Quando tutti i rami controllano condizioni boolean calcolate (es. if x > 10 and y < 5), una catena if/elif è naturale. match brilla quando la condizione riguarda la forma dei dati.
Errori Comuni
I nomi delle costanti non vengono confrontati per valore
Un nome semplice in un case è sempre una cattura, mai una ricerca:
STATUS_OK = 200
match response_code:
case STATUS_OK: # WRONG — this captures into STATUS_OK, not compares!
print("Success")Per confrontare con una costante con nome, usa un nome con punti (http.HTTPStatus.OK) o una guardia:
match response_code:
case x if x == STATUS_OK:
print("Success")match non è esaustivo per impostazione predefinita
A differenza di switch in alcuni altri linguaggi, un match che non ha nessun case corrispondente non fa silenziosamente nulla. Aggiungi un case _: se hai bisogno di un handler garantito.
match richiede Python 3.10+
Eseguire un blocco match su Python 3.9 o versioni precedenti solleva SyntaxError: invalid syntax. Controlla la tua versione con python3 --version. Consulta la guida introduttiva a Python se hai bisogno di configurare un ambiente Python moderno.
I pattern non sono espressioni boolean
Non puoi scrivere case x > 5: — quella è una guardia, non un pattern. La parte strutturale (case x) deve venire prima, seguita da un'espressione if guard_expression opzionale.
Riepilogo
| Tipo di pattern | Esempio di sintassi | Cosa corrisponde |
|---|---|---|
| Letterale | case 42: | Valore esatto |
| OR | case "yes" | "y": | Qualunque delle alternative |
| Wildcard | case _: | Qualsiasi cosa (scarta il valore) |
| Cattura | case x: | Qualsiasi cosa, associa a x |
| Sequenza | case [a, b, *rest]: | Una sequenza con almeno 2 elementi |
| Mapping | case {"key": val}: | Un dict contenente le chiavi indicate |
| Classe | case Point(x=0, y=y): | Un'istanza con attributi corrispondenti |
| AS | case int() as n: | Corrisponde e associa l'intero valore |
| Guardia | case x if x > 0: | Pattern + condizione boolean aggiuntiva |
Esercitati
Ora che sai come ramificare sulla struttura dei dati, esplora i cicli for in Python per iterare sulle sequenze, oppure gli enum Python per definire il tipo di costanti tipizzate che funzionano bene con i pattern di classe.