Alors que Python 3.9 arrive en fin de vie, Python 3.14 est officiellement sorti le 7 octobre. Bien que vous puissiez voir la liste de toutes les nouveautés qu'apporte cette version sur le site officiel, nous en avons sélectionné quelques-unes pour vous.
Les templates strings
Une nouveauté qui a largement fait parler d'elle : les t-strings.
Vous connaissez déjà bien les f-strings apparues avec Python 3.6, la référence pour formater les chaînes de caractères. Mais avec la version 3.14, les t-strings font leur apparition.
Avec une syntaxe semblable aux f-strings, vous ne serez pas dépaysés :
nom = "Patrick" phrase = t"Salut {nom}, comment ça va ?" print(phrase) # Template(strings=('Salut ', ', comment ça va ?'), interpolations=(Interpolation('Patrick', 'nom', None, ''),))
Sauf qu'ici, une t-string retourne un objet Template qui sépare le texte statique des variables.
nom = "Patrick" site = "docstring.fr" phrase = t"Salut {nom}, comment ça va ? Bienvenue sur {site}" print(phrase.strings) # ('Salut ', ', comment ça va ? Bienvenue sur ', '') print(phrase.interpolations) # (Interpolation('Patrick', 'nom', None, ''), Interpolation('docstring.fr', 'site', None, '')) print(phrase.interpolations[0]) # Interpolation('Patrick', 'nom', None, '')
On retrouve cet objet Template dans from string.templatelib import Template, Interpolation.
L'intérêt des t-strings est de pouvoir manipuler le template avant sa transformation finale.
Ce qui est très pratique pour :
-
Sécuriser des requêtes SQL
-
Générer des logs mieux structurés
-
Utiliser la logique des t-strings dans la construction de frameworks
Une t-string est une structure que l'on peut facilement analyser en accédant aux valeurs interpolées, au texte statique, et au nom des variables.
Prenons un exemple concret d'injection SQL :
def creer_requete_fstring(nom_utilisateur): # On colle directement la variable dans la chaîne return f"SELECT * FROM users WHERE name = '{nom_utilisateur}'" # Un utilisateur malveillant (tentative d'injection SQL) nom_malveillant = "' OR '1'='1" requete2 = creer_requete_fstring(nom_malveillant) print(f"Requête piégée : {requete2}") # Affiche : SELECT * FROM users WHERE name = '' OR '1'='1' # La condition est toujours vraie, la requête renverra TOUS les utilisateurs !
Ici la requête renverra la liste de tous les utilisateurs, ce qui n'est pas génial 😅.
Utilisons maintenant une t-string :
# Fonction de nettoyage def creer_requete_sure(template): requete_finale = [] for partie in template: if isinstance(partie, str): requete_finale.append(partie) # C'est du texte fixe, on garde else: # C'est une variable, on la nettoie ! # Ici, on remplace ' par '', ce qui neutralise l'injection SQL print(type(partie)) # <class 'string.templatelib.Interpolation'> valeur_nettoyee = str(partie.value).replace("'", "''") requete_finale.append(f"'{valeur_nettoyee}'") return "".join(requete_finale) def creer_template_tstring(nom_utilisateur): # La future requête return t"SELECT * FROM users WHERE name = {nom_utilisateur}" # Tentons une injection nom_malveillant = "' OR '1'='1" template_requete = creer_template_tstring(nom_malveillant) # Fonction de nettoyage requete_securisee = creer_requete_sure(template_requete) print(f"Requête sécurisée : {requete_securisee}") # SELECT * FROM users WHERE name = ''' OR ''1''=''1'
Ici la base cherchera un utilisateur dont le nom est littéralement "' OR '1'='1'".
La plupart du temps nous utiliserons les f-strings, mais dans certains cas les t-strings peuvent avoir un réel intérêt !
Messages d'erreur
Qui n'a jamais eu droit à ce message : SyntaxError: invalid syntax ?
Python 3.14 nous simplifie la vie ! Terminé le message d'erreur générique, Python vous donne maintenant des indices :
whil True: print("Hello, World!") break
File "/Users/gabrieltrouve/Pro/sandbox/main.py", line 1 whil True: ^^^^ SyntaxError: invalid syntax. Did you mean 'while'?
Même principe pour les blocs conditionnels :
if True: ... else: ... elif: ...
File "/Users/gabrieltrouve/Pro/sandbox/main.py", line 5 elif: ^^^^ SyntaxError: 'elif' block follows an 'else' block
Maintenant on ne sait pas uniquement où est l'erreur, mais pourquoi c'est une erreur. Même si avec l'IA ce type de problème arrive de moins en moins souvent, c'est un excellent moyen de corriger plus vite.
Nous avons vu deux exemples, mais ce principe d'améliorations s'étend à d'autres erreurs de syntaxe.
Coloration syntaxique dans l'interpréteur (REPL)
Dans l'interpréteur de base le code est maintenant coloré.
Coloration syntaxique de Python 3.14
Terminé le texte monochrome, bienvenue à la coloration syntaxique en temps réel. Les mots-clés, les chaînes de caractères, les commentaires, chacun sa couleur.
L'évaluation différée des annotations
Un apport de Python 3.14 qui intéressera ceux qui utilisent le type hinting : l'évaluation différée des annotations est activée par défaut.
En effet, Python ne vérifie plus immédiatement ce que signifie un type lorsque vous définissez une fonction. Il le stocke comme du simple texte et ne l'évaluera que plus tard.
Avant Python 3.14, si vous utilisiez un type avant de l'avoir défini, Python levait une erreur :
def create_user(name: str) -> User: return User(name) class User: def __init__(self, name: str) -> None: self.name = name user = create_user("Patrick") print(user.name)
Traceback (most recent call last): File "/Users/gabrieltrouve/Pro/sandbox/main.py", line 1, in <module> def create_user(name: str) -> User: ^^^^ NameError: name 'User' is not defined
Pour pallier ce problème, il fallait importer annotations de __future__ :
from __future__ import annotations def create_user(name: str) -> User: return User(name) class User: def __init__(self, name: str) -> None: self.name = name user = create_user("Patrick") print(user.name)
Avec l'évaluation différée par défaut, cet import n'est plus nécessaire. En Python 3.14, ce code est donc tout à fait valide :
def create_user(name: str) -> User: return User(name) class User: def __init__(self, name: str) -> None: self.name = name user = create_user("Patrick") print(user.name)
Avancée sur le GIL
La version sans GIL est désormais officiellement supportée, ce qui ouvre les portes à un vrai parallélisme en Python. La version sans GIL permet à plusieurs threads de s'exécuter en même temps sur différents cœurs.
Mais il faut bien connaître le statut de cette nouveauté :
-
C'est officiel et supporté, CPython s'engage à maintenir cette fonctionnalité
-
Le GIL reste activé par défaut
Il ne suffit pas d'installer la version standard de Python 3.14, mais une version spécifique identifiée par le suffixe "t".
Exemple avec pyenv
Cependant, sans le GIL, un code qui n'utilise qu'un seul thread peut s'exécuter de 5 à 10% plus lentement. Aussi, il peut y avoir une incompatibilité avec de nombreuses bibliothèques.
Un compilateur Just-In-Time
Apparu pour la première fois avec Python 3.13, le compilateur Just-In-Time (JIT) était utilisé en compilant soi-même Python. Il n'était donc pas inclus dans les installations standards.
Avec Python 3.14 il devient accessible à tous. En effet, il est disponible dans les versions standards de Python pour Windows et MacOS.
Si l'interpréteur Python classique exécute le code ligne par ligne, le compilateur JIT va plus loin. Il va lire votre code et le traduire en code machine, ce qui est beaucoup plus rapide.
Cependant, il ne faut pas oublier que le statut du JIT est expérimental :
-
Il faut l'activer manuellement en utilisant la variable d'environnement
PYTHON_JIT=1 -
Les performances peuvent être variables, il est donc déconseillé de l'utiliser en production
À noter
À savoir que le compilateur JIT et le mode sans GIL sont mutuellement exclusifs.
Les multi-interpreteurs
Vous connaissez certainement threading et multiprocessing ? Ces deux modules permettent d'exécuter plusieurs tâches en même temps :
-
threadingutilise des threads qui partagent la même mémoire, mais il existe un risque de conflits d'accès -
multiprocessingpermet de lancer des processus séparés, chacun avec sa propre mémoire, ce qui peut consommer des ressources
Python 3.14 introduit un nouveau module : concurrent.interpreters. L'idée est d'exécuter plusieurs interpréteurs Python dans un même processus, et chaque interpréteur possède sa propre mémoire et son propre état.
Exceptions : une syntaxe plus légère
Jusqu'à présent, pour capturer plusieurs types d'exceptions, il fallait utiliser les parenthèses pour les regrouper dans un tuple.
try: with open("config.txt", "r") as f: data = f.read() except (FileNotFoundError, PermissionError): print("Le fichier de configuration est introuvable ou inaccessible.")
Si vous n'avez pas besoin de capturer l'exception dans une variable avec le mot-clé as, vous n'avez plus besoin de mettre les parenthèses.
try: with open("config.txt", "r") as f: data = f.read() except FileNotFoundError, PermissionError: print("Le fichier de configuration est introuvable ou inaccessible.")
Arrivée de Zstandard
Tout d'abord, Python 3.14 réorganise les bibliothèques de compression dans le nouveau module parent compression. Les anciens modules comme gzip ou bz2 sont accessibles via ce nouveau chemin (ex: compression.gzip).
Mais la grande nouveauté, c'est que la bibliothèque standard comprend maintenant Zstandard, un outil de compression moderne. Cet outil est réputé pour sa vitesse de compression et décompression. Son utilisation est très simple :
from compression import zstd # Des données d'exemple (en bytes) donnees_originales = b"Le futur de la compression en Python est rapide, tres rapide !" * 10000 # Compression des données donnees_compressees = zstd.compress(donnees_originales) print(f"Taille originale : {len(donnees_originales)} octets") print(f"Taille compressee : {len(donnees_compressees)} octets") donnees_decompressees = zstd.decompress(donnees_compressees) print(f"Taille decompressee : {len(donnees_decompressees)} octets") # Taille originale : 620000 octets # Taille compressee : 127 octets # Taille decompressee : 620000 octets
De plus, ce nouveau format est supporté par les modules tarfile, zipfile et shutil de la bibliothèque standard.
Le débogage en production
La dernière version de Python apporte avec elle une interface de débogage externe. En effet, jusqu'à présent, déboguer une application en cours d'exécution était une opération qui risquait de redémarrer l'application. Ce qui dans certains cas, n'est pas acceptable.
Désormais, les débogeurs peuvent s'attacher à un processus Python en cours d'exécution sans arrêter l'application.
Par exemple, imaginons une application tournant avec le PID (Process ID) 5379 que l'on voudrait inspecter pour telle ou telle raison. On voudrait exécuter un script qui s'attache à ce processus pour faire des vérifications :
# Ce script EXTERNE s'exécute dans VOTRE processus import sys pid_cible = 5379 chemin_script_debug = "/tmp/mon_code_de_debug.py" # On demande au processus 5379 d'exécuter mon_code_de_debug.py sys.remote_exec(pid_cible, chemin_script_debug) print(f"Le code a été injecté dans le processus {pid_cible}.")
Le processus 5379 exécutera mon_code_de_debug.py au prochain point d'exécution sûr.
Ça ne s'arrête pas là ...
Nous avons vu quelques nouveautés pour cette version de Python 3.14. On ne peut pas TOUT citer, mais vous pouvez vous rendre sur la page de la release note pour consulter l'ensemble des modifications.
Par exemple, il y a eu pas mal d'améliorations au niveau de la bibliothèque standard, notamment pathlib qui se voit doté de nouvelles méthodes comme copy() ou move().