Python 3.10 est disponible. Les développeurs les plus audacieux sont encouragés à tester leur code avec cette version, en prenant les précautions nécessaires (par exemple, en utilisant un environnement virtuel). Cette itération comporte peu de nouvelles fonctionnalités majeures, mais l'une d'entre elles appelée filtrage structurel de motifs ou correspondance de motifs, est sans doute l’apport le plus significatif à la syntaxe du langage depuis async.

Voici un récapitulatif de toutes les dernières fonctionnalités majeures de Python 3.10, et en quoi elles contribuent à améliorer le code.

Filtrage ou correspondance de motifs

L’ajout du filtrage de motifs (pattern matching) intervient après plusieurs tentatives infructueuses d'ajouter à Python une syntaxe de type switch/case. Ce filtrage, appelé aussi correspondance de motifs, fait correspondre des variables à un ensemble de valeurs possibles (comme avec switch/case dans d'autres langages). Mais il établit également une correspondance avec des modèles de valeurs - par exemple, un objet dont une certaine propriété a une certaine valeur. La fonction élargit considérablement l'éventail des possibilités et permet d'écrire du code qui englobe rapidement de nombreux scénarios.

Par exemple :

command = input() correspond à command.split() :

   case ["quit"] :

       quit()

   case ["load", filename] :

       load_from(filename)

   case ["save", filename] :

       save_to(filename)

   case _ :

       print (f "Command '{command}' not understood")

Des rapports d'erreurs plus précis

Les rapports d'erreurs de Python ont longtemps subi les caprices de son analyseur syntaxique. Déjà, Python 3.9 avait accueilli un tout nouveau parseur - plus rapide, plus robuste, plus facile à maintenir pour l'équipe Python et avec moins de bidouillages internes. Le dernier analyseur délivre notamment aux développeurs des messages d'erreur beaucoup plus précis et utiles. Dans Python 3.8, le code suivant génère une erreur de syntaxe.

print ("Hello"

print ("What's going on?")

 File ".\test.py", line 2

   print ("What's going on?")

   ^

SyntaxError: invalid syntax

Ce qui n’est pas très utile, car le véritable problème se situe une ligne au-dessus. Python 3.10 génère une erreur bien plus utile :

 File ".\test.py", line 1

   print ("Hello"

         ^

SyntaxError : '(' was never closed

De nombreuses erreurs produites par l'analyseur syntaxique ont été améliorées dans ce sens, en fournissant non seulement des informations plus précises sur l'erreur, mais aussi sur l'endroit où l'erreur se produit réellement.

Capture de variables des paramètres

Le module de typage de Python, utilisé pour annoter le code avec des informations de type, permet de décrire les types d'un appelable, les callables, (par exemple, une fonction). Mais ces informations de type ne peuvent pas être propagées entre les appelables. Ce qui complique l'annotation de choses comme les décorateurs de fonctions. Deux nouveaux ajouts au typage, typing.ParamSpec et typing.Concatenate, permettent d'annoter les callables avec des informations de définition de type plus abstraites.

Voici un exemple tiré du document PEP sur cette fonctionnalité.

from typing import Await Awaitable, Callable, TypeVar

R = TypeVar("R")

def add_logging(f : Callable[..., R]) -> Callable[..., Awaitable[R]] :

  async def inner(*args : objet, **kwargs : objet) -> R :

    await log_to_database()

    return f(*args, **kwargs)

  return inner

@add_logging

def takes_int_str(x : int, y : str) -> int :

  return x + 7

await takes_int_str(1, "A")

await takes_int_str("B", 2) # fails at runtime

Parce qu'il n'est pas possible de fournir au linter les détails appropriés sur les types passés aux fonctions qui sont traitées par le décorateur, le linter ne peut pas attraper les types invalides dans la deuxième instance de takes_int_str.

Voici comment ce code fonctionnerait avec la syntaxe de capture de variables d’un paramètre.

from typing import Await Awaitable, Callable, ParamSpec, TypeVar

P = ParamSpec("P")

R = TypeVar("R")

def add_logging(f : Callable[P, R]) -> Callable[P, Awaitable[R]] :

  async def inner(*args : P.args, **kwargs : P.kwargs) -> R :

    await log_to_database(

    return f(*args, **kwargs)

  return inner

@add_logging

def takes_int_str(x : int, y : str) -> int :

  return x + 7

await takes_int_str(1, "A") # Accepted

await takes_int_str("B", 2) # Correctly rejected by the type checker

ParamSpec permet d'indiquer où capturer les arguments positionnels et les mots-clés. Concatenate peut être utilisé pour indiquer comment les arguments sont ajoutés ou supprimés, ce qui est fait couramment avec les décorateurs.

Autres changements majeurs de Python 3.10

- Les unions de types peuvent désormais être exprimés comme X|Y, au lieu de Union[X,Y], pour des raisons de concision (PEP 604).

- Le built-in zip, qui combine ensemble les résultats de plusieurs itérables, a maintenant un mot-clé strict. Quand le résultat est True, zip lève une exception si l'un des itérables est épuisé avant les autres (PEP 618), de façon à ajouter une vérification optionnelle des longueurs dans zip.

- Les instructions with supportent désormais la syntaxe parenthétique sur plusieurs lignes (BPO-12782). La fonction permet de grouper des gestionnaires de contexte avec des parenthèses.

- Les variables peuvent désormais être déclarées en tant qu'alias de type explicites, pour permettre de définir des références directes, des erreurs plus robustes impliquant des types, et de meilleures distinctions entre les déclarations de type dans les champs (PEP 613).

- L’API OpenSSL 1.1.1 ou une version plus récente est désormais requise pour construire CPython. Cette exigence contribue à moderniser l'une des dépendances clés de CPython (PEP 644).