Skip to content

Python Match Case : explication du pattern matching structurel (Python 3.10+)

Updated on

Les développeurs Python se sont appuyés pendant des décennies sur des chaînes if-elif pour gérer la logique de branchement. Comparer une variable à plusieurs valeurs, inspecter la structure des données, ou router selon le type d’objets exige souvent des blocs conditionnels verbeux, de plus en plus difficiles à lire à mesure que les cas se multiplient. Une fonction qui dispatche sur dix types de commandes possibles devient un mur de elif, où la logique réelle est enfouie sous des comparaisons répétitives.

Ce n’est pas seulement un problème de lisibilité. Les longues chaînes if-elif sont propices à des bugs subtils — une condition mal placée, un cas oublié, ou un “fall-through” accidentel qui laisse passer silencieusement de mauvaises données. D’autres langages ont résolu cela depuis longtemps avec des mécanismes de pattern matching, mais Python n’a eu de solution native qu’à partir de la version 3.10.

Python 3.10 a introduit l’instruction match-case (PEP 634), apportant le pattern matching structurel au langage. Cela va bien au-delà d’une simple comparaison de valeurs : vous pouvez déstructurer des séquences, faire correspondre des “formes” de dictionnaires, lier des variables, appliquer des conditions de garde, et dispatcher selon des types de classes — le tout dans une syntaxe claire et déclarative. Ce guide couvre chaque type de pattern avec des exemples pratiques.

📚

Qu’est-ce que le pattern matching structurel ?

Le pattern matching structurel vous permet de comparer une valeur (le « sujet ») à une série de patterns, puis d’exécuter le bloc de code du premier pattern correspondant. Contrairement à un switch en C ou Java, le match-case de Python ne se contente pas de comparer des valeurs — il inspecte la structure des données.

Syntaxe de base :

match subject:
    case pattern1:
        # code for pattern1
    case pattern2:
        # code for pattern2
    case _:
        # default case (wildcard)

Caractéristiques clés :

  • Les patterns sont évalués de haut en bas. Le premier pattern correspondant l’emporte.
  • Le pattern joker _ correspond à tout et sert de cas par défaut.
  • Les patterns peuvent lier des variables : certaines parties de la valeur matchée sont capturées dans des noms utilisables dans le bloc du case.
  • Pas de fall-through. Un seul bloc case s’exécute.

Version de Python requise : match-case nécessite Python 3.10 ou une version ultérieure. Vérifiez votre version avec python --version. Si vous utilisez une version plus ancienne, vous devez mettre à niveau avant d’utiliser cette fonctionnalité.

Patterns littéraux : faire correspondre des valeurs exactes

L’usage le plus simple de match-case consiste à comparer une valeur à des constantes littérales :

def get_http_status_message(code):
    match code:
        case 200:
            return "OK"
        case 201:
            return "Created"
        case 301:
            return "Moved Permanently"
        case 400:
            return "Bad Request"
        case 403:
            return "Forbidden"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status code: {code}"
 
print(get_http_status_message(404))  # Not Found
print(get_http_status_message(418))  # Unknown status code: 418

Les patterns littéraux fonctionnent avec les entiers, les chaînes, les booléens et None :

def describe_value(value):
    match value:
        case True:
            return "Boolean true"
        case False:
            return "Boolean false"
        case None:
            return "None value"
        case "":
            return "Empty string"
        case 0:
            return "Zero"
        case _:
            return f"Other: {value}"

Important : True, False et None sont matchés par identité (comme is), pas par égalité. Cela signifie que match 1 ne matchera pas case True même si 1 == True vaut True en Python.

Or-patterns : faire correspondre plusieurs valeurs

Utilisez l’opérateur | pour faire correspondre l’un de plusieurs patterns dans un seul case :

def classify_day(day):
    match day.lower():
        case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
            return "Weekday"
        case "saturday" | "sunday":
            return "Weekend"
        case _:
            return "Invalid day"
 
print(classify_day("Saturday"))  # Weekend
print(classify_day("Wednesday")) # Weekday

Les or-patterns fonctionnent avec n’importe quel type de pattern, pas seulement les littéraux :

def categorize_error(code):
    match code:
        case 400 | 401 | 403 | 404 | 405:
            return "Client error"
        case 500 | 502 | 503 | 504:
            return "Server error"
        case _:
            return "Other"

Patterns de capture de variable

Les patterns peuvent capturer des parties de la valeur matchée dans des variables. Un nom nu dans un pattern agit comme une variable qui se lie à la valeur présente à cet emplacement :

def parse_command(command):
    match command.split():
        case ["quit"]:
            return "Exiting program"
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["add", x, y]:
            return f"Sum: {int(x) + int(y)}"
        case _:
            return "Unknown command"
 
print(parse_command("hello Alice"))  # Hello, Alice!
print(parse_command("add 3 5"))      # Sum: 8
print(parse_command("quit"))         # Exiting program

Dans case ["hello", name], la variable name capture la chaîne qui apparaît en deuxième position dans la liste. C’est fondamentalement différent d’un matching littéral — il n’y a pas de variable name existante contre laquelle on compare. À la place, le pattern crée une nouvelle liaison.

Le piège capture vs constante

Comme les noms nus sont des patterns de capture, vous ne pouvez pas matcher directement contre une variable :

QUIT_COMMAND = "quit"
 
match user_input:
    case QUIT_COMMAND:  # This captures into QUIT_COMMAND, not compare!
        print("This always matches!")

Pour matcher contre une constante, utilisez des noms qualifiés (avec un point) ou des valeurs littérales :

class Commands:
    QUIT = "quit"
    HELP = "help"
 
match user_input:
    case Commands.QUIT:  # Dotted name: compares against the value
        print("Quitting")
    case Commands.HELP:
        print("Showing help")

Les noms qualifiés (contenant un .) sont traités comme des accès à une valeur, pas comme des captures. C’est un choix de conception intentionnel dans la PEP 634.

Patterns de séquence : déstructurer listes et tuples

Match-case excelle pour déstructurer des séquences. Vous pouvez matcher la longueur et le contenu de listes ou de tuples :

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})"
        case _:
            return "Not a valid point"
 
print(process_point((0, 0)))   # Origin
print(process_point((5, 0)))   # On x-axis at 5
print(process_point((3, 7)))   # Point at (3, 7)

Patterns étoile pour les séquences de longueur variable

Utilisez * pour matcher des séquences de longueur variable :

def analyze_sequence(seq):
    match seq:
        case []:
            return "Empty sequence"
        case [single]:
            return f"Single element: {single}"
        case [first, second]:
            return f"Pair: {first}, {second}"
        case [first, *middle, last]:
            return f"First: {first}, Last: {last}, Middle has {len(middle)} items"
 
print(analyze_sequence([]))              # Empty sequence
print(analyze_sequence([42]))            # Single element: 42
print(analyze_sequence([1, 2, 3, 4, 5])) # First: 1, Last: 5, Middle has 3 items

Patterns de séquence imbriqués

Les patterns peuvent être imbriqués pour matcher des structures de données complexes :

def process_matrix_row(row):
    match row:
        case [[a, b], [c, d]]:
            return f"2x2 block: {a}, {b}, {c}, {d}"
        case [first_row, *rest]:
            return f"First row: {first_row}, remaining rows: {len(rest)}"
 
print(process_matrix_row([[1, 2], [3, 4]]))
# 2x2 block: 1, 2, 3, 4

Patterns de mapping : faire correspondre des dictionnaires

Les patterns de mapping matchent des objets de type dictionnaire en vérifiant la présence de clés spécifiques et, éventuellement, en capturant leurs valeurs :

def handle_api_response(response):
    match response:
        case {"status": "success", "data": data}:
            return f"Success! Data: {data}"
        case {"status": "error", "message": msg}:
            return f"Error: {msg}"
        case {"status": "error", "code": code, "message": msg}:
            return f"Error {code}: {msg}"
        case {"status": status}:
            return f"Unknown status: {status}"
        case _:
            return "Invalid response format"
 
print(handle_api_response({"status": "success", "data": [1, 2, 3]}))
# Success! Data: [1, 2, 3]
 
print(handle_api_response({"status": "error", "message": "Not found"}))
# Error: Not found

Les patterns de mapping matchent si le dictionnaire contient les clés spécifiées — les clés supplémentaires sont ignorées. Cela les rend idéaux pour travailler avec des données JSON et des réponses d’API.

Utilisez **rest pour capturer les clés restantes :

def extract_config(config):
    match config:
        case {"host": host, "port": port, **rest}:
            return f"Server: {host}:{port}, extra config: {rest}"
        case _:
            return "Missing required config"
 
print(extract_config({"host": "localhost", "port": 8080, "debug": True}))
# Server: localhost:8080, extra config: {'debug': True}

Clauses de garde : ajouter des conditions aux patterns

Les gardes ajoutent une condition if qui doit être vraie pour que le pattern soit considéré comme correspondant. Le pattern est vérifié d’abord ; s’il match, l’expression de garde est évaluée :

def classify_number(n):
    match n:
        case x if x < 0:
            return "Negative"
        case 0:
            return "Zero"
        case x if x % 2 == 0:
            return "Positive even"
        case x if x % 2 == 1:
            return "Positive odd"
 
print(classify_number(-5))  # Negative
print(classify_number(0))   # Zero
print(classify_number(4))   # Positive even
print(classify_number(7))   # Positive odd

Les gardes sont essentielles lorsque le pattern seul ne peut pas exprimer toute la condition de matching :

def validate_age(data):
    match data:
        case {"name": name, "age": age} if age < 0:
            return f"Invalid: {name} has negative age"
        case {"name": name, "age": age} if age < 18:
            return f"{name} is a minor (age {age})"
        case {"name": name, "age": age} if age >= 18:
            return f"{name} is an adult (age {age})"
        case _:
            return "Invalid data format"
 
print(validate_age({"name": "Alice", "age": 25}))
# Alice is an adult (age 25)

Patterns de classe : matcher les types d’objets

Les patterns de classe vérifient si une valeur est une instance d’une classe et peuvent, en option, déstructurer ses attributs :

from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float
 
@dataclass
class Circle:
    center: Point
    radius: float
 
@dataclass
class Rectangle:
    top_left: Point
    width: float
    height: float
 
def describe_shape(shape):
    match shape:
        case Circle(center=Point(x=0, y=0), radius=r):
            return f"Circle at origin with radius {r}"
        case Circle(center=center, radius=r) if r > 100:
            return f"Large circle at ({center.x}, {center.y})"
        case Circle(center=center, radius=r):
            return f"Circle at ({center.x}, {center.y}) with radius {r}"
        case Rectangle(width=w, height=h) if w == h:
            return f"Square with side {w}"
        case Rectangle(top_left=tl, width=w, height=h):
            return f"Rectangle at ({tl.x}, {tl.y}), {w}x{h}"
        case _:
            return "Unknown shape"
 
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
 
print(describe_shape(Rectangle(Point(1, 2), 10, 10)))
# Square with side 10

Pour les classes sans __match_args__, utilisez des arguments nommés. Les dataclasses et les named tuples définissent automatiquement __match_args__, ce qui permet des patterns positionnels :

@dataclass
class Color:
    r: int
    g: int
    b: int
 
def describe_color(color):
    match color:
        case Color(0, 0, 0):
            return "Black"
        case Color(255, 255, 255):
            return "White"
        case Color(r, 0, 0):
            return f"Red shade (r={r})"
        case Color(0, g, 0):
            return f"Green shade (g={g})"
        case Color(0, 0, b):
            return f"Blue shade (b={b})"
        case Color(r, g, b):
            return f"RGB({r}, {g}, {b})"
 
print(describe_color(Color(255, 0, 0)))   # Red shade (r=255)
print(describe_color(Color(128, 64, 32))) # RGB(128, 64, 32)

Matcher des types intégrés (built-in)

Vous pouvez matcher des types built-in pour faire du contrôle de type :

def process_value(value):
    match value:
        case int(n):
            return f"Integer: {n}"
        case float(n):
            return f"Float: {n}"
        case str(s):
            return f"String: '{s}'"
        case list(items):
            return f"List with {len(items)} items"
        case dict(d):
            return f"Dict with keys: {list(d.keys())}"
        case _:
            return f"Other type: {type(value).__name__}"
 
print(process_value(42))          # Integer: 42
print(process_value("hello"))     # String: 'hello'
print(process_value([1, 2, 3]))   # List with 3 items

Match case vs if-elif : quand utiliser l’un ou l’autre

Critèrematch-caseif-elif
Version Python3.10+ uniquementToutes les versions
Comparaison de valeursPropre, une valeur par caseVerbeux avec répétition de la variable
DéstructurationIntégrée (séquences, dicts, objets)Unpacking manuel requis
Contrôle de typePatterns de classe natifsNécessite des appels isinstance()
Conditions de gardeif après le patternif/elif standard
Liaison de variablesCapture automatiqueAffectation manuelle
PerformanceDispatch de patterns optimiséÉvaluation séquentielle
LisibilitéMeilleur pour 4+ cas structurésMeilleur pour 1–3 conditions simples
Fall-throughNon (par design)Possible si elif manquant

Quand match-case est clairement meilleur :

# Parsing structured data -- match-case
def handle_event(event):
    match event:
        case {"type": "click", "x": x, "y": y}:
            process_click(x, y)
        case {"type": "keypress", "key": key, "modifiers": [*mods]}:
            process_key(key, mods)
        case {"type": "scroll", "delta": delta} if delta != 0:
            process_scroll(delta)

L’équivalent avec if-elif :

# Same logic with if-elif -- more verbose
def handle_event(event):
    if event.get("type") == "click" and "x" in event and "y" in event:
        process_click(event["x"], event["y"])
    elif event.get("type") == "keypress" and "key" in event and "modifiers" in event:
        process_key(event["key"], event["modifiers"])
    elif event.get("type") == "scroll" and event.get("delta", 0) != 0:
        process_scroll(event["delta"])

Quand if-elif reste meilleur :

# Simple range checks -- if-elif is clearer
if temperature > 100:
    status = "boiling"
elif temperature > 50:
    status = "hot"
elif temperature > 20:
    status = "warm"
else:
    status = "cold"

Match-case ne supporte pas directement les comparaisons d’intervalles dans les patterns. Il faudrait utiliser des gardes pour chaque cas, ce qui annule l’avantage de lisibilité.

Cas d’usage réels

Parsing d’arguments en ligne de commande

import sys
 
def main():
    match sys.argv[1:]:
        case ["help"]:
            print("Available commands: help, version, run, test")
        case ["version"]:
            print("v1.0.0")
        case ["run", filename]:
            print(f"Running {filename}")
        case ["run", filename, "--verbose"]:
            print(f"Running {filename} with verbose output")
        case ["test", *test_files] if test_files:
            print(f"Testing: {', '.join(test_files)}")
        case ["test"]:
            print("Running all tests")
        case [unknown, *_]:
            print(f"Unknown command: {unknown}")
        case []:
            print("No command provided. Use 'help' for usage.")

Implémentation d’une machine à états

from dataclasses import dataclass
from typing import Optional
 
@dataclass
class State:
    name: str
    data: Optional[dict] = None
 
def transition(state, event):
    match (state.name, event):
        case ("idle", "start"):
            return State("loading", {"progress": 0})
        case ("loading", "progress"):
            progress = state.data.get("progress", 0) + 25
            if progress >= 100:
                return State("ready", {"progress": 100})
            return State("loading", {"progress": progress})
        case ("loading", "cancel"):
            return State("idle")
        case ("ready", "process"):
            return State("processing", state.data)
        case ("processing", "complete"):
            return State("done", {"result": "success"})
        case ("processing", "error"):
            return State("error", {"message": "Processing failed"})
        case (_, "reset"):
            return State("idle")
        case (current, unknown_event):
            print(f"No transition from '{current}' on '{unknown_event}'")
            return state
 
# Usage
state = State("idle")
for event in ["start", "progress", "progress", "progress", "progress", "process", "complete"]:
    state = transition(state, event)
    print(f"Event: {event} -> State: {state.name}, Data: {state.data}")

Gestionnaire de réponse d’API JSON

def process_api_result(response):
    match response:
        case {"data": {"users": [*users]}, "meta": {"total": total}}:
            print(f"Found {total} users, received {len(users)}")
            for user in users:
                match user:
                    case {"name": name, "role": "admin"}:
                        print(f"  Admin: {name}")
                    case {"name": name, "role": role}:
                        print(f"  {role.title()}: {name}")
 
        case {"data": {"users": []}}:
            print("No users found")
 
        case {"error": {"code": code, "message": msg}} if code >= 500:
            print(f"Server error ({code}): {msg}")
            raise RuntimeError(msg)
 
        case {"error": {"code": code, "message": msg}}:
            print(f"Client error ({code}): {msg}")
 
        case _:
            print(f"Unexpected response format: {type(response)}")
 
# Example
process_api_result({
    "data": {"users": [
        {"name": "Alice", "role": "admin"},
        {"name": "Bob", "role": "editor"}
    ]},
    "meta": {"total": 2}
})

Parseur de fichier de configuration

def apply_config(settings):
    for key, value in settings.items():
        match (key, value):
            case ("database", {"host": host, "port": int(port), "name": db}):
                print(f"DB connection: {host}:{port}/{db}")
            case ("database", _):
                raise ValueError("Invalid database config: need host, port, name")
            case ("logging", {"level": level}) if level in ("DEBUG", "INFO", "WARNING", "ERROR"):
                print(f"Log level set to {level}")
            case ("logging", {"level": level}):
                raise ValueError(f"Invalid log level: {level}")
            case ("features", {"enabled": list(features)}):
                print(f"Enabled features: {', '.join(features)}")
            case ("features", {"enabled": _}):
                raise TypeError("Features must be a list")
            case (key, _):
                print(f"Unknown config key: {key}")
 
apply_config({
    "database": {"host": "localhost", "port": 5432, "name": "mydb"},
    "logging": {"level": "INFO"},
    "features": {"enabled": ["auth", "cache", "metrics"]}
})

Pipeline de transformation de données

def transform_record(record):
    """Transform raw data records into standardized format."""
    match record:
        case {"timestamp": ts, "value": float(v) | int(v), "unit": str(unit)}:
            return {"time": ts, "measurement": float(v), "unit": unit.lower()}
 
        case {"timestamp": ts, "value": str(v), "unit": str(unit)}:
            try:
                return {"time": ts, "measurement": float(v), "unit": unit.lower()}
            except ValueError:
                return {"time": ts, "measurement": None, "unit": unit.lower(), "error": f"Cannot parse: {v}"}
 
        case {"timestamp": ts, "value": v}:
            return {"time": ts, "measurement": float(v), "unit": "unknown"}
 
        case {"values": [*values]} if values:
            return [transform_record({"timestamp": None, "value": v, "unit": "batch"}) for v in values]
 
        case _:
            return {"error": f"Unrecognized format: {record}"}
 
# Examples
print(transform_record({"timestamp": "2026-01-01", "value": 42.5, "unit": "Celsius"}))
print(transform_record({"timestamp": "2026-01-01", "value": "98.6", "unit": "F"}))

Techniques avancées de patterns

Pattern AS : lier l’ensemble du match

Utilisez as pour capturer la valeur entière matchée tout en la déstructurant :

def process_item(item):
    match item:
        case {"name": str(name), "price": float(price)} as product if price > 100:
            print(f"Premium product: {product}")
        case {"name": str(name), "price": float(price)} as product:
            print(f"Standard product: {product}")

Combiner plusieurs types de patterns

Les patterns se composent naturellement. Vous pouvez mélanger patterns de séquence, de mapping et de classe :

@dataclass
class Order:
    items: list
    customer: dict
 
def process_order(order):
    match order:
        case Order(items=[single_item], customer={"vip": True}):
            print(f"VIP single-item order: {single_item}")
        case Order(items=[_, _, *rest], customer={"name": name}) if len(rest) > 0:
            print(f"{name} ordered {2 + len(rest)} items")
        case Order(items=[], customer=_):
            print("Empty order")

Utiliser match-case dans des notebooks Jupyter

Le pattern matching est particulièrement utile dans des workflows d’analyse de données où vous devez gérer différents formats de données ou des réponses d’API. En explorant des datasets de manière interactive dans des notebooks Jupyter, match-case fournit une manière propre de gérer la variété de “formes” de données rencontrées.

def classify_cell_output(output):
    """Classify Jupyter cell output by type."""
    match output:
        case {"output_type": "stream", "text": text}:
            return f"Text output: {len(text)} chars"
        case {"output_type": "error", "ename": name, "evalue": value}:
            return f"Error: {name}: {value}"
        case {"output_type": "display_data", "data": {"image/png": _}}:
            return "Image output"
        case {"output_type": "execute_result", "data": {"text/html": html}}:
            return "HTML table output"
        case _:
            return "Unknown output type"

Pour les data scientists qui veulent une assistance IA tout en travaillant avec le pattern matching et d’autres fonctionnalités Python 3.10+ dans des notebooks, RunCell (opens in a new tab) propose un agent IA directement dans Jupyter, capable d’aider à écrire, déboguer et optimiser des instructions match-case selon vos structures de données réelles.

Erreurs courantes et comment les éviter

Erreur 1 : utiliser match-case avec Python < 3.10

# This fails with SyntaxError on Python 3.9 and earlier
match value:
    case 1:
        print("one")

Correctif : vérifiez sys.version_info >= (3, 10) ou mettez Python à niveau.

Erreur 2 : traiter les noms de case comme des comparaisons

expected = 200
 
match status_code:
    case expected:  # WRONG: creates a new variable 'expected'
        print("OK")

Correctif : utilisez un nom qualifié, un littéral, ou une garde :

match status_code:
    case code if code == expected:
        print("OK")

Erreur 3 : oublier le cas joker

Sans case _, les valeurs non matchées ne font rien, silencieusement :

match command:
    case "start":
        run()
    case "stop":
        halt()
    # If command is "pause", nothing happens -- no error, no warning

Correctif : incluez toujours un cas joker pour un traitement explicite :

match command:
    case "start":
        run()
    case "stop":
        halt()
    case _:
        raise ValueError(f"Unknown command: {command}")

Erreur 4 : patterns dépendants de l’ordre sans spécificité

Les patterns sont évalués de haut en bas. Un pattern trop général placé avant un pattern spécifique le masque :

# WRONG: the first case always matches
match point:
    case (x, y):       # Captures everything
        print("generic")
    case (0, 0):       # Never reached
        print("origin")

Correctif : placez les patterns spécifiques en premier :

match point:
    case (0, 0):
        print("origin")
    case (x, y):
        print(f"point at ({x}, {y})")

Considérations de performance

Match-case est compilé en bytecode efficace. Pour le matching littéral simple, le compilateur Python l’optimise de manière similaire à des lookup de dictionnaire. Pour les patterns structurels, il génère un arbre de décision qui évite des vérifications redondantes.

Les benchmarks montrent que match-case offre des performances comparables aux chaînes if-elif pour un petit nombre de cas (moins de 5). Pour des tables de dispatch plus grandes (10+ cas), match-case peut être plus rapide grâce à l’optimisation du bytecode, en particulier avec des patterns littéraux.

Cependant, le pattern matching ne remplace pas un dispatch via dictionnaire lorsque vous souhaitez simplement mapper des valeurs vers des fonctions :

# For simple value-to-action mapping, a dict is cleaner and faster
handlers = {
    "click": handle_click,
    "scroll": handle_scroll,
    "keypress": handle_keypress,
}
 
handler = handlers.get(event_type, handle_unknown)
handler(event_data)

Utilisez match-case lorsque vous avez besoin d’inspection structurelle, de déstructuration, ou de conditions de garde — des choses qu’un lookup de dictionnaire ne peut pas exprimer.

FAQ

De quelle version de Python ai-je besoin pour match-case ?

Python 3.10 ou une version ultérieure est requise pour l’instruction match-case. Elle a été introduite dans la PEP 634 dans le cadre de la release Python 3.10 en octobre 2021. Tenter d’utiliser match-case avec Python 3.9 ou une version antérieure entraîne une SyntaxError. Vous pouvez vérifier votre version en exécutant python --version dans votre terminal.

Est-ce que match-case en Python est identique à switch-case dans d’autres langages ?

Non. Le match-case de Python est du pattern matching structurel, plus puissant qu’un switch-case traditionnel. Alors que switch-case en C ou Java ne fait que comparer des valeurs, match-case en Python peut déstructurer des séquences et des dictionnaires, matcher des instances de classes via leurs attributs, lier des variables à partir des données matchées, et appliquer des conditions de garde. Il est plus proche du pattern matching de Rust, Scala ou Haskell.

Est-ce que match-case a un fall-through comme le switch en C ?

Non. Le match-case de Python exécute uniquement le premier bloc case correspondant, puis sort de l’instruction match. Il n’y a pas de fall-through et il n’y a pas besoin de break. Si vous voulez que plusieurs patterns exécutent le même code, utilisez des or-patterns avec l’opérateur pipe : case "a" | "b" | "c": matchera n’importe laquelle de ces valeurs.

Puis-je utiliser match-case avec des expressions régulières ?

Pas directement. Les patterns de match-case sont structurels, pas basés sur des regex. En revanche, vous pouvez utiliser des clauses de garde pour appliquer un matching regex : case str(s) if re.match(r"pattern", s): combine un contrôle de type structurel avec une validation regex dans la garde.

Comment match-case gère-t-il les valeurs None ?

None est matché comme un pattern littéral via case None:. Comme None est un singleton en Python, le match utilise une comparaison par identité (équivalente à is None). Cela signifie que case None: ne matchera que l’objet None lui-même, pas d’autres valeurs falsy comme 0, False, ou des chaînes vides.

Que se passe-t-il si aucun case ne correspond ?

Si aucun case ne correspond et qu’il n’y a pas de pattern joker (case _:), l’instruction match ne fait rien silencieusement — l’exécution continue après le bloc match. Aucune exception n’est levée. C’est pourquoi il est recommandé d’inclure systématiquement un cas joker, soit pour fournir une action par défaut, soit pour lever une exception en cas de valeur inattendue.

Conclusion

L’instruction match-case de Python apporte le pattern matching structurel à un langage qui s’est longtemps appuyé sur des chaînes if-elif pour la logique de branchement complexe. Elle offre une façon déclarative d’inspecter la “forme” des données, de déstructurer des séquences et des dictionnaires, de dispatcher selon des types d’objets et de lier des variables — le tout dans une syntaxe plus lisible que le code conditionnel équivalent.

L’idée clé est que match-case n’est pas simplement un switch. C’est un outil conçu pour travailler avec des données structurées : parser des commandes, gérer des réponses d’API, implémenter des machines à états et router selon des types d’objets. Lorsque votre logique de branchement consiste à vérifier la structure des données plutôt qu’à faire de simples comparaisons de valeurs, match-case produit du code à la fois plus court et plus maintenable.

Commencez avec des patterns littéraux simples, puis intégrez progressivement la déstructuration de séquences, les patterns de mapping et les patterns de classe au fur et à mesure de vos besoins. Incluez toujours un cas joker pour gérer explicitement les entrées inattendues, placez les patterns spécifiques avant les patterns généraux, et souvenez-vous que les noms nus capturent des valeurs plutôt que de comparer à des constantes.

Pour explorer interactivement des patterns match-case avec vos propres données, les notebooks Jupyter offrent un environnement idéal pour tester les patterns de manière incrémentale. RunCell (opens in a new tab) améliore ce workflow avec une assistance IA qui comprend les fonctionnalités Python 3.10+ et peut suggérer des structures de patterns en fonction des formes de vos données.

📚