Thème d'affichage

Python 3.15 vient de sortir : imports lazy, frozendict et UTF⁠-⁠8 par défaut

Nicolas Lecointre · 10 Oct 2026 à 18h23
Python 3.15 vient de sortir : imports lazy, frozendict et UTF⁠-⁠8 par défaut

Python 3.15 est disponible depuis ce vendredi, et plusieurs de ses nouveautés touchent directement le code que vous écrivez au quotidien, pas seulement les entrailles de l'interpréteur.

Vos imports s'offrent une petite grasse mat'

C'est la nouveauté phare de cette version : le mot-clé lazy (PEP 810) repousse le chargement d'un module jusqu'à sa première utilisation réelle.

Pour rappel, un import classique oblige Python à trouver le fichier, à le lire, à le compiler en bytecode puis à exécuter son contenu. Sur une application aux dépendances touffues, la facture peut atteindre plusieurs secondes au démarrage, même pour des modules qui ne serviront jamais.

lazy import json
lazy from pathlib import Path

Avec ces deux lignes, json et pathlib ne sont réellement chargés qu'au premier appel. Les CLI et les scripts qui importent la moitié de PyPI pour n'en utiliser qu'une poignée sont les premiers à y gagner.

Fini aussi les contorsions pour esquiver un import coûteux en le planquant dans une fonction ou derrière un if TYPE_CHECKING: : tous les imports peuvent rester en haut du fichier, comme le recommande la PEP 8, le guide de style officiel de Python.

Pour du code existant, l'option -X lazy_imports=all (ou la variable d'environnement PYTHON_LAZY_IMPORTS=all) rend tous les imports lazy sans toucher une seule ligne.

Astral, l'éditeur de uv et Ruff dont OpenAI a annoncé le rachat en mars, s'en sert déjà dans une option expérimentale de uv qui accélère jusqu'à 18% la construction des paquets, selon ses propres mesures.

Attention quand même : si un module déclaré en lazy n'arrive pas à se charger, l'exception tombe à sa première utilisation, et non plus au démarrage. Et lazy n'est accepté qu'au niveau du module : dans une fonction, une classe ou un bloc try, c'est la SyntaxError assurée.

frozendict, le dictionnaire immuable

Autre arrivée attendue de longue date, frozendict (PEP 814) rejoint les types natifs : c'est un dictionnaire immuable. Une fois qu'il est créé, impossible d'y ajouter, d'y modifier ou d'y retirer une clé.

De quoi protéger une configuration ou une table de constantes contre une fonction un peu trop zélée. Il sert aussi de valeur par défaut à un argument sans tomber dans le piège classique du {} partagé entre tous les appels.

Comme frozenset, son cousin pour les ensembles présent depuis Python 2.4, il est hachable tant que ses clés et ses valeurs le sont. Il peut donc servir de clé à un autre dictionnaire ou finir dans un set.

Une première proposition avait été rejetée en 2012, faute de cas d'usage convaincants. Quatorze ans plus tard, la donne a changé : Python propose désormais un mode sans GIL (Global Interpreter Lock). Ce verrou global empêche par défaut plusieurs threads d'exécuter du code Python en même temps.

Ce mode et les sous-interpréteurs (plusieurs interpréteurs isolés qui cohabitent dans un même processus, chacun avec son propre GIL) permettent d'exécuter du code Python sur plusieurs cœurs à la fois. Sans eux, il faut répartir le travail entre plusieurs processus, avec le module multiprocessing par exemple.

Un frozendict, que personne ne peut modifier, se partage alors entre threads sans verrou ni risque de conflit.

Ça ne paie pas de mine

Le changement le plus discret de cette version risque pourtant d'en concerner beaucoup : Python utilise désormais l'UTF-8 par défaut, quel que soit l'environnement du système (PEP 686).

Jusqu'ici, un open("fichier.txt") sans paramètre encoding utilisait l'encodage local de la machine, ce qui pouvait réserver son lot de caractères accentués cassés. Un script qui dépendait de l'ancien comportement peut le retrouver en définissant PYTHONUTF8=0 (ou avec l'option -X utf8=0), et encoding="locale" corrige le problème proprement dans le code.

Les compréhensions de liste acceptent aussi l'opérateur (PEP 798) : [x for x in listes] aplatit une liste de listes, là où il fallait passer par itertools.chain() ou par un double for dont personne ne retient jamais l'ordre.

Enfin, les messages d'erreur pensent désormais aux devs qui jonglent entre plusieurs langages : un [1, 2, 3].push(4) vous souffle d'utiliser .append, 'hello'.toUpperCase() renvoie vers .upper et {}.put("a", 1) vous rappelle qu'en Python, on écrit d[k] = v.

Les devs JavaScript et Java se reconnaîtront. 👀

Le JIT passe la seconde

Le compilateur JIT, qui compile à la volée en code machine les portions les plus sollicitées du programme, a été largement remanié depuis son arrivée dans Python 3.13.

Sur la suite de benchmarks pyperformance, il affiche un gain moyen de 7 à 8% sur Linux x86-64 et de 11 à 12% sur les Mac à puce Apple, par rapport à l'interpréteur sans JIT.

Cette moyenne cache de gros écarts d'un benchmark à l'autre : avec le JIT, certains tournent environ 15% moins vite, d'autres plus de deux fois plus vite. Rappelons qu'il reste officiellement expérimental.

Les utilisateurs de Windows, eux, gagnent en vitesse sans rien avoir à activer : les binaires officiels 64 bits passent à l'interpréteur tail-calling apparu avec Python 3.14, une autre façon d'enchaîner les instructions que le compilateur C optimise mieux. Python annonce 15 à 20% de gain moyen sur pyperformance.

Pour savoir où votre propre code passe son temps, la bibliothèque standard accueille aussi Tachyon, un profileur statistique.

Plutôt que d'instrumenter chaque appel de fonction comme cProfile, quitte à ralentir franchement le programme, il relève la pile d'appels jusqu'à un million de fois par seconde, pour un surcoût quasi nul.

Il peut même s'attacher à un processus en cours sans le redémarrer : de quoi ausculter un service qui rame directement en production.

Le Python nouveau est arrivé

Si vos projets tournent encore sous Python 3.10, c'est le bon moment de prévoir une montée de version : la 3.10 est arrivée au bout de ses cinq ans de support le 1er octobre et ne recevra plus aucun correctif de sécurité.

Et pour découvrir les nouveautés autrement qu'en épluchant les notes de version, Barry Warsaw, contributeur historique de CPython, a codé whatsnewt, un jeu d'aventure textuel qui se lance dans le terminal avec uvx --python 3.15 whatsnewt.

Capture du jeu d'aventure texte whatsnewt dans un terminal, avec score et carte des pièces à explorer

Ses dix-huit énigmes se résolvent toutes en écrivant du vrai code Python 3.15, et le jeu cache des easter eggs.

Venant de l'homme qui a droit à son propre easter egg dans CPython (from __future__ import barry_as_FLUFL), le contraire aurait été étonnant.

À propos de l'auteur

Nicolas Lecointre

Nicolas Lecointre

Fondateur des Joies du Code, je suis l'actualité et la culture des développeurs depuis 2012. Ancien développeur, je me consacre aujourd'hui entièrement au média.

Voir sa page auteur

À lire également

Articles similaires

Plus de contenu

Charger +