W3docs

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 matched

Regole da ricordare:

  • match e case sono soft keyword — sono keyword solo in questo contesto e possono ancora essere usate come nomi di variabili altrove nel codice.
  • Ogni blocco case viene 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 status

Pattern 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"))   # True

I 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.

Attenzione

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 event

Punto 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 5

Nota 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 type

La 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 odd

Le 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 entered

Leggendo 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

ScenarioScelta migliore
Semplice uguaglianza contro poche costantiEntrambi; match è leggermente più pulito
Matching sulla struttura/forma dei datimatch — molto più pulito
Destrutturare valori durante il matchingmatch — non è possibile con if
Logica che coinvolge solo condizioni calcolateif/elif
Python 3.9 o versioni precedentiif/elif (nessun match disponibile)
Esprimere chiaramente una tabella decisionalematch

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.

Tipo di patternEsempio di sintassiCosa corrisponde
Letteralecase 42:Valore esatto
ORcase "yes" | "y":Qualunque delle alternative
Wildcardcase _:Qualsiasi cosa (scarta il valore)
Catturacase x:Qualsiasi cosa, associa a x
Sequenzacase [a, b, *rest]:Una sequenza con almeno 2 elementi
Mappingcase {"key": val}:Un dict contenente le chiavi indicate
Classecase Point(x=0, y=y):Un'istanza con attributi corrispondenti
AScase int() as n:Corrisponde e associa l'intero valore
Guardiacase x if x > 0:Pattern + condizione boolean aggiuntiva

Esercitati

Pratica
Which Python version first introduced the match statement?
Which Python version first introduced the match statement?
Pratica
In a match block, what does a bare variable name in a case clause do?
In a match block, what does a bare variable name in a case clause do?
Pratica
Which pattern type would you use to match a dict that contains at least a 'type' key and extract its value?
Which pattern type would you use to match a dict that contains at least a 'type' key and extract its value?

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.

Was this page helpful?