La structure match / case
Introduction
Disponible depuis la version 3.10 de Python, la structure match / case permet de sélectionner un traitement selon une valeur, un type ou la structure d’une donnée. Ce mécanisme est appelé filtrage structurel par motifs (structural pattern matching).
Pour un développeur C#, elle correspond à l’instruction switch et les possibilités de pattern matching.
La syntaxe générale
La structure s'implémente de la manière suivante :
match expression:
case motif_1:
# Instructions du premier cas
...
case motif_2:
# Instructions du deuxième cas
...
case _:
# Instructions du cas général
...
L’expression placée après match est évaluée une seule fois. Python examine ensuite les clauses case dans leur ordre d’implémentation. Chaque clause contient un motif décrivant les valeurs ou les structures qu’elle accepte. Lorsqu’un motif correspond, Python exécute le bloc de code associé, puis poursuit après la structure match.
Contrairement à l’instruction switch du langage C#, aucun break n’est nécessaire pour terminer une clause case.
Les deux-points : introduisent les blocs, et l’indentation délimite les instructions qui leur appartiennent.
Comparer des valeurs
Commençons par sélectionner un traitement selon une chaîne de caractères :
command = "save"
match command:
case "open":
print("Ouverture du fichier")
case "save":
print("Enregistrement du fichier")
case "quit":
print("Fermeture de l’application")
case _:
print("Commande inconnue")
Les motifs peuvent notamment contenir des chaînes, des nombres, des booléens ou None.
Résultat de l'exécution :
La valeur de command correspondant au motif "save", seul le bloc de code associé est exécuté. Voici un exemple comparable en C# :
string command = "save";
switch (command)
{
case "open":
Console.WriteLine("Ouverture du fichier");
break;
case "save":
Console.WriteLine("Enregistrement du fichier");
break;
case "quit":
Console.WriteLine("Fermeture de l’application");
break;
default:
Console.WriteLine("Commande inconnue");
break;
}
Définir un cas général avec _
Le motif _, appelé motif joker (wildcard pattern), est exécuté si aucun cas précédemment évalué est exécuté. Il doit être placé en dernière position :
status_code = 418
match status_code:
case 200:
print("Succès")
case 404:
print("Ressource introuvable")
case _:
print("Autre code de statut")
Résultat de l'exécution :
Ce cas est facultatif. Si aucun cas ne correspond, Python poursuit après le bloc match, sans lever automatiquement d’exception :
status_code = 418
match status_code:
case 200:
print("Succès")
case 404:
print("Ressource introuvable")
print("Fin du traitement")
Résultat de l'exécution :
Regrouper plusieurs motifs avec l'opérateur |
L'opérateur | permet de regrouper plusieurs possibilités au sein d'un même cas :
command = "exit"
match command:
case "quit" | "exit" | "close":
print("Fermeture de l’application")
case "save" | "export":
print("Enregistrement des données")
case _:
print("Commande inconnue")
Résultat de l'exécution :
Dans une expression, l'opérateur | représente une alternative. Il ne faut pas le remplacer par l’opérateur binaire or. En C#, nous pouvons utiliser cet opérateur :
string command = "exit";
string message = command switch
{
"quit" or "exit" or "close" => "Fermeture de l’application",
"save" or "export" => "Enregistrement des données",
_ => "Commande inconnue"
};
Console.WriteLine(message);
Capturer une valeur dans une variable
Un nom utilisé dans une expression capture la valeur examinée :
command = "print"
match command:
case "save":
print("Enregistrement du fichier")
case other:
print(f"Commande non prise en charge : {other}")
Résultat de l'exécution :
other est une variable qui contient la valeur examinée. elle se distingue de _ qui perme d'ignorer la valeur.
En C#, une capture de la données examinée est implémentée avec l'inférence de type (var) :
string command = "print";
string message = command switch
{
"save" => "Enregistrement du fichier",
var other => $"Commande non prise en charge : {other}"
};
Console.WriteLine(message);
Ajouter une condition avec une garde
Une garde est une expression conditionnelle positionnée après la valeur examinée à l’aide du mot-clé if :
age = 20
match age:
case value if value < 0:
print("Âge invalide")
case value if value < 18:
print("Personne mineure")
case _:
print("Personne majeure")
Résultat :
Python vérifie d’abord la valeur examinée. Lorsqu’elle correspond, le bloc de code correspondant est exécuté.
En C#, l'équivalent consiste à utiliser le mot-clé when :
int age = 20;
string message = age switch
{
var value when value < 0 => "Âge invalide",
var value when value < 18 => "Personne mineure",
_ => "Personne majeure"
};
Console.WriteLine(message);
Reconnaître une séquence
Les motifs de séquence permettent de reconnaître une organisation d’éléments et d’en extraire les valeurs :
position = (10, 20)
match position:
case (0, 0):
print("Origine")
case (x, 0):
print(f"Axe horizontal : x = {x}")
case (0, y):
print(f"Axe vertical : y = {y}")
case (x, y):
print(f"Position : x = {x}, y = {y}")
case _:
print("Position non reconnue")
Résultat :
Dans cet exemple :
(0, 0)exige deux éléments égaux à zéro.(x, 0)capture le premier élément et exige que le second soit égal à zéro.(0, y)exige que le premier élément soit égal à zéro et capture le second.(x, y)capture les deux éléments.
L’ordre des cas est essentiel : le motif général (x, y) doit venir après les motifs plus précis.
Listes et tuples
Les écritures (x, y) et [x, y] reconnaissent toutes deux des séquences de deux éléments. Elles n’imposent pas respectivement un tuple ou une liste :
Résultat :
Les chaînes de caractères, les objets bytes et les objets bytearray ne sont pas décomposés par ces motifs. Un itérateur ou un générateur n’est pas non plus reconnu comme une séquence par ce mécanisme.
En C#, les motifs de tuples offrent une possibilité comparable pour les tuples :
var position = (10, 20);
string message = position switch
{
(0, 0) => "Origine",
(var x, 0) => $"Axe horizontal : x = {x}",
(0, var y) => $"Axe vertical : y = {y}",
(var x, var y) => $"Position : x = {x}, y = {y}"
};
Console.WriteLine(message);
Capturer les éléments restants avec *
Dans un motif de séquence, * permet de capturer plusieurs éléments dans une liste :
numbers = [10, 20, 30, 40]
match numbers:
case []:
print("Séquence vide")
case [first, *remaining]:
print(f"Premier élément : {first}")
print(f"Éléments restants : {remaining}")
Résultat :
Le motif [first, *remaining] accepte une séquence contenant au moins un élément. Si elle n’en contient qu’un, remaining reçoit une liste vide.
Nous pouvons aussi capturer les éléments intermédiaires :
numbers = [10, 20, 30, 40]
match numbers:
case [first, *middle, last]:
print(first)
print(middle)
print(last)
Résultat :
Un motif de séquence ne peut contenir qu’une seule capture préfixée par *.
Reconnaître un dictionnaire
Un motif de dictionnaire permet de rechercher certaines clés et de capturer leurs valeurs :
message = {
"action": "create",
"name": "Clavier",
"price": 69.90
}
match message:
case {"action": "create", "name": name}:
print(f"Création du produit : {name}")
case {"action": "delete", "id": product_id}:
print(f"Suppression du produit : {product_id}")
case _:
print("Message non reconnu")
Résultat :
Le premier motif exige :
- Une clé
"action"dont la valeur est"create". - Une clé
"name"dont la valeur est capturée dansname.
La clé supplémentaire "price" n’empêche pas la correspondance.
Capturer les autres entrées
La syntaxe **remaining récupère les entrées non mentionnées dans le motif :
Résultat :
Reconnaître un type
Un motif de classe permet de sélectionner un traitement selon le type d’une valeur :
value = 42
match value:
case bool() as flag:
print(f"Booléen : {flag}")
case int() as number:
print(f"Entier : {number}")
case str() as text:
print(f"Chaîne : {text}")
case _:
print("Autre type")
Résultat :
Dans ce contexte, int() est un motif de classe : il ne réalise pas de conversion.
Le cas bool() est placé avant int() parce qu’en Python, bool est une sous-classe de int. Sans cette précaution, une valeur booléenne serait également reconnue par le motif int().
En C#, une sélection comparable peut s’écrire ainsi :
object value = 42;
string message = value switch
{
bool flag => $"Booléen : {flag}",
int number => $"Entier : {number}",
string text => $"Chaîne : {text}",
_ => "Autre type"
};
Console.WriteLine(message);
Examiner les attributs d’un objet
Définissons une classe représentant un produit :
Nous pouvons reconnaître une instance de cette classe et examiner ses attributs :
product = Product("Clavier", 69.90)
match product:
case Product(name=name, price=price) if price >= 100:
print(f"Produit coûteux : {name}")
case Product(name=name):
print(f"Produit : {name}")
case _:
print("Objet non pris en charge")
Résultat :
Dans Product(name=name, price=price) :
Productdésigne la classe recherchée.- Les noms à gauche des signes
=désignent les attributs. - Les noms à droite capturent les valeurs de ces attributs.
Cette écriture ne construit pas d’objet et n’appelle pas son constructeur.
Une classe ordinaire peut également être utilisée avec ces motifs nommés : le décorateur @dataclass n’est pas obligatoire.
En C#, nous pouvons utiliser un motif de propriétés :
object product = new Product("Clavier", 69.90);
string message = product switch
{
Product { Name: var name, Price: >= 100 }
=> $"Produit coûteux : {name}",
Product { Name: var name }
=> $"Produit : {name}",
_ => "Objet non pris en charge"
};
Console.WriteLine(message);
Utiliser un motif positionnel
Une classe décorée avec @dataclass fournit, par défaut, les informations nécessaires aux motifs positionnels :
L’ordre correspond ici à celui des champs de la classe.
Pour une classe ordinaire, les motifs positionnels reposent sur l’attribut de classe __match_args__. Les motifs nommés évitent de dépendre de cet ordre.
Conserver la valeur reconnue avec as
Le mot-clé as permet de capturer une valeur qui correspond à un motif :
command = "export"
match command:
case ("save" | "export") as action:
print(f"Action demandée : {action}")
case _:
print("Action inconnue")
Résultat :
Le motif vérifie que la valeur est "save" ou "export", puis la capture dans action.
Utiliser une énumération
Les valeurs d’une énumération doivent être référencées avec leur nom qualifié :
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
SHIPPED = 2
CANCELLED = 3
status = OrderStatus.SHIPPED
match status:
case OrderStatus.PENDING:
print("Commande en attente")
case OrderStatus.SHIPPED:
print("Commande expédiée")
case OrderStatus.CANCELLED:
print("Commande annulée")
Résultat :
L’écriture OrderStatus.SHIPPED désigne une valeur à comparer. Un nom isolé tel que SHIPPED serait interprété comme une capture.
Produire une valeur
En Python, match est une instruction et ne peut pas être directement utilisé à droite d’une affectation comme une expression switch en C#.
Nous pouvons placer le traitement dans une fonction :
def describe_status(status_code: int) -> str:
match status_code:
case 200:
return "Succès"
case 404:
return "Ressource introuvable"
case 500:
return "Erreur du serveur"
case _:
return "Autre statut"
description = describe_status(404)
print(description)
Résultat :
En C#, une expression switch permet une affectation directe :
int statusCode = 404;
string description = statusCode switch
{
200 => "Succès",
404 => "Ressource introuvable",
500 => "Erreur du serveur",
_ => "Autre statut"
};
Console.WriteLine(description);
Exemple : analyser des commandes
La combinaison des motifs permet de traiter des commandes de différentes formes :
def execute_command(command: str) -> None:
match command.split():
case ["help"]:
print("Commandes : help, add <nom>, delete <id>, quit")
case ["add", name]:
print(f"Ajout du produit : {name}")
case ["delete", identifier] if identifier.isdecimal():
print(f"Suppression du produit : {int(identifier)}")
case ["delete", _]:
print("L’identifiant doit être un entier positif ou nul.")
case ["quit"] | ["exit"]:
print("Fermeture de l’application")
case _:
print("Commande inconnue")
execute_command("add Clavier")
execute_command("delete 42")
execute_command("delete abc")
Résultat :
Ajout du produit : Clavier
Suppression du produit : 42
L’identifiant doit être un entier positif ou nul.
La méthode split() produit une liste de mots. Les motifs vérifient leur nombre et leur contenu, puis capturent les arguments utiles.
Dans cet exemple, le nom du produit doit tenir en un seul mot.