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
cases’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: 418Les 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")) # WeekdayLes 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 programDans 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 itemsPatterns 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, 4Patterns 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 foundLes 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 oddLes 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 10Pour 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 itemsMatch case vs if-elif : quand utiliser l’un ou l’autre
| Critère | match-case | if-elif |
|---|---|---|
| Version Python | 3.10+ uniquement | Toutes les versions |
| Comparaison de valeurs | Propre, une valeur par case | Verbeux avec répétition de la variable |
| Déstructuration | Intégrée (séquences, dicts, objets) | Unpacking manuel requis |
| Contrôle de type | Patterns de classe natifs | Nécessite des appels isinstance() |
| Conditions de garde | if après le pattern | if/elif standard |
| Liaison de variables | Capture automatique | Affectation manuelle |
| Performance | Dispatch de patterns optimisé | Évaluation séquentielle |
| Lisibilité | Meilleur pour 4+ cas structurés | Meilleur pour 1–3 conditions simples |
| Fall-through | Non (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 warningCorrectif : 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.