Vue d'ensemble
Python 3.10, publié le 4 octobre 2021, est l'une des versions les plus attendues de ces dernières années. Elle introduit le structural pattern matching (match/case), une fonctionnalité inspirée des langages fonctionnels qui enrichit considérablement les possibilités d'expression du langage.
Au-delà du pattern matching, cette version améliore significativement les messages d'erreur, autorise les gestionnaires de contexte entre parenthèses et renforce le système de typage avec TypeAlias et ParamSpec.
Nouveautés majeures
Pattern matching structurel (PEP 634/635/636)
Le pattern matching structurel est la nouveauté phare de Python 3.10. L'instruction match/case permet de comparer une valeur à une série de motifs, avec capture de variables, conditions de garde et déstructuration des données. C'est bien plus puissant qu'un simple switch/case d'autres langages.
# Exemple 1 : parseur de commandes
def executer_commande(commande):
"""Interprète une commande texte."""
match commande.split():
case ["quitter"]:
print("Au revoir !")
return False
case ["aide"]:
print("Commandes : quitter, aide, ouvrir <fichier>, chercher <motif> [dans <fichier>]")
case ["ouvrir", fichier]:
print(f"Ouverture de {fichier}")
case ["chercher", motif]:
print(f"Recherche de '{motif}' dans tous les fichiers")
case ["chercher", motif, "dans", fichier]:
print(f"Recherche de '{motif}' dans {fichier}")
case _:
print(f"Commande inconnue : {commande}")
return True
executer_commande("ouvrir rapport.txt")
# Ouverture de rapport.txt
executer_commande("chercher erreur dans journal.log")
# Recherche de 'erreur' dans journal.log
# Exemple 2 : traitement de données JSON structurées
def traiter_evenement(evenement):
"""Traite un événement selon sa structure."""
match evenement:
case {"type": "connexion", "utilisateur": nom}:
print(f"Connexion de {nom}")
case {"type": "achat", "produit": produit, "quantite": q} if q > 10:
print(f"Achat en gros : {q}x {produit}")
case {"type": "achat", "produit": produit, "quantite": q}:
print(f"Achat : {q}x {produit}")
case {"type": "erreur", "code": code, "message": msg}:
print(f"Erreur {code} : {msg}")
case _:
print(f"Événement non géré : {evenement}")
traiter_evenement({"type": "connexion", "utilisateur": "Alice"})
# Connexion de Alice
traiter_evenement({"type": "achat", "produit": "clavier", "quantite": 25})
# Achat en gros : 25x clavier
traiter_evenement({"type": "achat", "produit": "souris", "quantite": 3})
# Achat : 3x souris
# Exemple 3 : pattern matching sur des classes
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Cercle:
centre: Point
rayon: float
@dataclass
class Rectangle:
origine: Point
largeur: float
hauteur: float
def decrire_forme(forme):
"""Décrit une forme géométrique."""
match forme:
case Cercle(centre=Point(x=0, y=0), rayon=r):
print(f"Cercle centré à l'origine, rayon {r}")
case Cercle(centre=Point(x=cx, y=cy), rayon=r) if r > 100:
print(f"Grand cercle en ({cx}, {cy}), rayon {r}")
case Cercle(centre=c, rayon=r):
print(f"Cercle en ({c.x}, {c.y}), rayon {r}")
case Rectangle(largeur=l, hauteur=h) if l == h:
print(f"Carré de côté {l}")
case Rectangle(largeur=l, hauteur=h):
print(f"Rectangle {l}x{h}")
decrire_forme(Cercle(Point(0, 0), 5))
# Cercle centré à l'origine, rayon 5
decrire_forme(Rectangle(Point(1, 2), 10, 10))
# Carré de côté 10
Gestionnaires de contexte entre parenthèses (PEP 617)
Grâce au nouveau parseur PEG introduit en Python 3.9, il est désormais possible d'utiliser des parenthèses dans les instructions with pour répartir plusieurs gestionnaires de contexte sur plusieurs lignes de manière lisible.
# Avant Python 3.10 : obligation d'utiliser des backslashes
# with open('source.txt') as src, \
# open('dest.txt', 'w') as dst:
# dst.write(src.read())
# Python 3.10 : parenthèses autorisées
with (
open('source.txt') as src,
open('dest.txt', 'w') as dst,
):
dst.write(src.read())
# Très utile pour plusieurs fichiers
with (
open('config.json') as config,
open('donnees.csv') as donnees,
open('rapport.txt', 'w') as rapport,
):
# Traitement utilisant les trois fichiers
pass
Meilleurs messages d'erreur
Python 3.10 améliore considérablement les messages d'erreur. Les SyntaxError indiquent désormais plus précisément la position du problème, et les messages sont plus explicites. Les parenthèses, crochets et accolades non fermés sont mieux signalés, et les erreurs d'attribut ou de nom incluent des suggestions.
# Erreur de parenthèse non fermée :
# resultat = (1 + 2
# ^
# SyntaxError: '(' was never closed
# Oubli des deux-points dans un if :
# if x > 0
# ^
# SyntaxError: expected ':'
# Suggestion pour les erreurs de nom :
# nombre = 42
# print(nombe)
# NameError: name 'nombe' is not defined. Did you mean: 'nombre'?
# Suggestion pour les erreurs d'attribut :
# import collections
# collections.ordereddict
# AttributeError: module 'collections' has no attribute 'ordereddict'.
# Did you mean: 'OrderedDict'?
TypeAlias et ParamSpec (PEP 613, PEP 612)
TypeAlias permet de déclarer explicitement qu'une variable est un alias de type, levant l'ambiguïté avec une simple affectation. ParamSpec permet de typer des décorateurs qui préservent la signature de la fonction décorée.
from typing import TypeAlias, ParamSpec, Callable, TypeVar
import functools
import time
# TypeAlias : déclarer un alias de type explicite
Coordonnees: TypeAlias = tuple[float, float]
Matrice: TypeAlias = list[list[float]]
Resultat: TypeAlias = dict[str, list[int]]
def distance(a: Coordonnees, b: Coordonnees) -> float:
"""Calcule la distance entre deux points."""
return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** 0.5
print(distance((0, 0), (3, 4))) # 5.0
# ParamSpec : typer un décorateur préservant la signature
P = ParamSpec('P')
T = TypeVar('T')
def chronometre(func: Callable[P, T]) -> Callable[P, T]:
"""Mesure le temps d'exécution d'une fonction."""
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
debut = time.perf_counter()
resultat = func(*args, **kwargs)
duree = time.perf_counter() - debut
print(f"{func.__name__} : {duree:.4f}s")
return resultat
return wrapper
@chronometre
def calculer_somme(n: int) -> int:
return sum(range(n))
resultat = calculer_somme(1_000_000)
# calculer_somme : 0.0312s
Améliorations mineures
int.bit_count()renvoie le nombre de bits à 1 dans la représentation binaire d'un entier.zip()gagne un paramètrestrictpour vérifier que les itérables ont la même longueur (PEP 618).dataclassessupporte les champs avecslots=Trueetkw_only=True.- Les types
int,stretbytesgagnent en performance. - Le module
statisticss'enrichit de nouvelles fonctions de corrélation et de régression.
Dépréciations et suppressions
Les changements notables dans cette catégorie :
- Les alias de type du module
typing(typing.Dict,typing.List, etc.) sont dépréciés en faveur des types natifs. - Le module
distutilsest déprécié et prévu pour suppression dans Python 3.12. - Les encodages non textuels dans
str.encode()sont dépréciés.
