Python 3.15 sort le 1er octobre 2026 et concentre des améliorations très concrètes : démarrage accéléré grâce aux lazy imports, JIT plus rapide, nouvelles structures de données immuables et typage plus précis. Voici ce qui change vraiment pour votre code.
Python 3.15 arrive le 1er octobre 2026, et pour une fois, la liste des nouveautés ne se résume pas à des optimisations internes sans effet tangible sur votre code. La version est déjà en beta 3 depuis le 23 juin 2026, le gel des fonctionnalités est passé, et plusieurs ajouts touchent directement au quotidien : le temps de démarrage, la performance d’exécution, le typage et même de nouvelles structures de données.
Plusieurs de ces nouveautés ont un impact concret sur la façon d’écrire du code au quotidien. Voici un tour d’horizon des principales nouveautés, avec des exemples concrets et ce qu’elles changent pour votre code.
Les imports paresseux, la vraie tête d’affiche (PEP 810)
S’il fallait retenir une seule nouveauté, ce serait celle-ci, et c’est de loin ma préférée de cette version. Python 3.15 introduit les imports “paresseux” explicites via un nouveau mot-clé contextuel, lazy.
Le principe est simple : un module marqué lazy n’est chargé et exécuté qu’au moment où vous utilisez réellement le nom importé, pas au démarrage du programme.
lazy import json
lazy from pathlib import Path
# json n'est pas encore chargé ici
def write_config(data):
# c'est seulement à cet appel que json est réellement importé
return json.dumps(data)
Pourquoi c’est génial ? Parce que le temps de démarrage de Python a longtemps été plombé par des arbres d’imports gigantesques. Un outil en ligne de commande qui importe un gros framework, qui lui-même importe vingt dépendances, peut mettre plus d’une seconde avant d’afficher un simple --help. Les imports paresseux règlent ce problème sans réécrire toute l’architecture du projet.
La fonctionnalité est opt-in et explicite, et c’est un choix assumé. Une précédente tentative d’imports paresseux globaux (PEP 690) avait été rejetée, justement parce qu’un comportement implicite généralisé était trop risqué. Ici, l’idée est de décider import par import.
Plusieurs leviers de contrôle accompagnent le mot-clé : un drapeau -X lazy_imports en ligne de commande, une variable d’environnement PYTHON_LAZY_IMPORTS, et une API sys.set_lazy_imports() pour un réglage programmatique.
À retenir : les imports paresseux prennent tout leur sens pour les CLI, l’outillage de développement et les stacks IA aux dépendances tentaculaires. Seul piège à surveiller : si un de vos imports comptait sur un effet de bord exécuté au chargement, ce comportement se déclenchera désormais plus tard, au premier usage.
Le JIT monte enfin en régime
Le compilateur JIT est apparu dans Python 3.13, mais ses gains étaient alors trop faibles pour qu’on les remarque. Python 3.15 est la première version où le bénéfice devient tangible.
Les chiffres officiels, mesurés sur la suite pyperformance, dépendent de la plateforme :
| Plateforme | Référence comparée | Gain (moyenne géométrique) |
|---|---|---|
| x86-64 Linux | interpréteur standard | 8 à 9 % |
| AArch64 macOS | interpréteur tail-calling | 12 à 13 % |
Deux points méritent toutefois d’être précisés. D’abord, ce ne sont pas deux bornes d’une même fourchette mais deux mesures distinctes, contre des références différentes. Ensuite, la documentation CPython elle-même indique que ces résultats ne sont pas encore définitifs. Le détail par benchmark s’étale d’un léger ralentissement (environ 15 %) à des accélérations supérieures à 100 %.
Non, cela ne transforme pas Python en Rust. Mais un gain automatique de l’ordre de 10 %, sans toucher à une seule ligne de code, sur un langage aussi répandu, ce n’est pas anecdotique. Le JIT reste pour l’instant une fonctionnalité expérimentale que l’on active explicitement, et son rodage se poursuit jusqu’à la version finale.
frozendict, le dictionnaire immuable enfin natif (PEP 814)
Python ajoute rarement de nouvelles structures de données natives. Quand il le fait, c’est généralement que l’écosystème a recréé la même chose dans son coin un nombre incalculable de fois. C’est exactement l’histoire de frozendict.
config = frozendict({"host": "localhost", "port": 5432})
config["port"] = 5433 # TypeError : l'objet est immuable
Ce nouveau type vit dans le module builtins, au même titre que dict ou list. Trois caractéristiques à connaître :
- il est immuable : aucune modification possible après création ;
- il est hachable dès lors que ses clés et ses valeurs le sont, donc utilisable comme clé de dictionnaire ou dans un
set; - il n’hérite pas de
dict(il dérive directement d’object), si bien queisinstance(config, dict)renvoieFalse.
Ce dernier point est important pour éviter les mauvaises surprises dans du code qui teste les types. Plusieurs modules de la bibliothèque standard ont été adaptés pour l’accepter, parmi lesquels copy, json, pickle, pprint, decimal et xml.etree.ElementTree.
sentinel, des valeurs sentinelles officielles (PEP 661)
Voici une de ces petites améliorations dont on ne mesure l’intérêt qu’après avoir écrit beaucoup de Python. Combien de fois avez-vous croisé ceci ?
MISSING = object()
def get(key, default=MISSING):
...
if default is MISSING:
raise KeyError(key)
On crée un objet anonyme unique parce que None peut être une valeur parfaitement légitime, et qu’il faut donc un marqueur distinct pour signifier absence de valeur. Le souci, c’est que cet objet est opaque, ne se documente pas, et s’affiche de façon illisible dans les logs comme dans les messages d’erreur.
Python 3.15 officialise le motif avec une fonction sentinel :
MISSING = sentinel("MISSING")
def get(key, default=MISSING):
if default is MISSING:
raise KeyError(key)
Le résultat est plus clair, s’affiche de façon lisible plutôt que sous forme d’adresse mémoire, et s’intègre mieux avec les outils de typage. Rien de révolutionnaire, juste une bizarrerie de moins, ce que Python sait plutôt bien faire au fil des versions.
L’unpacking dans les comprehensions (PEP 798)
Jusqu’ici, aplatir des itérables imbriqués dans une comprehension demandait une double boucle peu agréable à lire :
# Avant
flat = [x for sub in lists for x in sub]
Python 3.15 autorise les opérateurs d’unpacking * et ** en tête d’expression, dans les comprehensions de listes, d’ensembles, de dictionnaires et dans les expressions génératrices :
# Avec Python 3.15
flat = [*sub for sub in lists] # aplatit une liste de listes
uniques = {*s for s in sets} # fusionne des ensembles
fusion = {**d for d in dicts} # fusionne des dictionnaires
gen = (*sub for sub in lists) # version génératrice
La syntaxe est plus directe à lire et évite la double boucle. À utiliser avec mesure, cela dit : les comprehensions Python ont déjà une fâcheuse tendance à virer au casse-tête quand on en abuse.
Le typage gagne en précision (PEP 728 et 747)
Le système de typage de Python poursuit son expansion, et deux PEP débarquent dans 3.15.
PEP 728 ajoute deux paramètres à TypedDict : closed et extra_items. Le premier interdit les clés non déclarées, le second impose un type à toutes les clés supplémentaires.
from typing import TypedDict
class Config(TypedDict, closed=True):
host: str
port: int
# une clé non déclarée est désormais signalée par le vérificateur de types
PEP 747 introduit TypeForm, qui permet d’annoter les expressions de type manipulées comme des valeurs. C’est surtout utile aux bibliothèques qui acceptent un type en argument (validation, sérialisation, ORM) et veulent le typer correctement.
Ces ajouts ne changent rien à l’exécution, mais ils permettent aux vérificateurs comme mypy ou pyright d’attraper davantage d’erreurs en amont, avant même de lancer le code.
Tachyon, un profiler par échantillonnage (PEP 799)
Le profilage en Python a longtemps imposé un compromis : soit une mesure précise mais coûteuse avec cProfile, qui ralentit fortement le programme, soit un suivi léger mais approximatif.
Python 3.15 réorganise le profilage dans un module profiling et y ajoute un profiler par échantillonnage statistique à haute fréquence, dont le nom de code est Tachyon (PEP 799). On y trouve désormais profiling.tracing (l’ancien cProfile) et profiling.sampling (Tachyon), capable d’échantillonner jusqu’à environ un million de fois par seconde.
Plutôt que de tracer chaque appel, l’échantillonnage relève périodiquement l’état d’exécution et estime où le temps est passé. Moins précis, bien moins coûteux, et souvent largement suffisant pour de l’optimisation réelle, y compris dans des conditions proches de la production.
Le ramasse-miettes revient à sa version générationnelle
Un point mérite d’être clarifié, car beaucoup de confusion circule à son sujet. Python 3.14 avait introduit un ramasse-miettes incrémental, censé réduire les temps de pause. En pratique, plusieurs retours ont signalé une hausse importante de la consommation mémoire en production.
La décision a donc été prise de faire machine arrière et de revenir au ramasse-miettes générationnel de Python 3.13. Attention au détail qui compte : ce retour en arrière a eu lieu dans la version corrective 3.14.5 (le 10 mai 2026), et non dans 3.15. Python 3.15 ne fait qu’hériter de ce collecteur éprouvé.
Au-delà de la mécanique, c’est plutôt un bon signal. Une optimisation qui dégrade la mémoire ne mérite pas d’être conservée au seul prétexte qu’elle était sophistiquée. Le collecteur incrémental pourra revenir plus tard, une fois retravaillé.
Quand et comment passer à Python 3.15
Le calendrier est fixé par la PEP 790 :
- 7 mai 2026 : beta 1 (gel des fonctionnalités) ;
- 23 juin 2026 : beta 3 (version actuelle) ;
- 18 juillet 2026 : beta 4 ;
- 4 août 2026 : première release candidate ;
- 1er octobre 2026 : sortie finale.
Vous pouvez déjà tester la beta sans attendre, à condition de ne pas la mettre en production. Les bêtas et release candidates sont disponibles sur python.org, et la plupart des gestionnaires de versions les proposent :
# avec pyenv
pyenv install 3.15.0b3
# avec uv
uv python install 3.15
Une réserve toutefois : jusqu’à la première release candidate du 4 août, certaines fonctionnalités peuvent encore être ajustées, voire retirées dans de rares cas. Pour de l’expérimentation et la préparation de vos projets, c’est parfait. Pour du code critique, mieux vaut attendre la version finale d’octobre.
Points clés à retenir
- Imports paresseux (PEP 810) : le mot-clé
lazydiffère le chargement d’un module jusqu’à son utilisation, pour un démarrage plus rapide. - JIT plus mûr : de l’ordre de 8 à 9 % de gain sur Linux x86-64 et 12 à 13 % sur macOS ARM, sans toucher au code, mais chiffres encore non définitifs.
frozendict(PEP 814) : un dictionnaire immuable et hachable natif, qui n’hérite pas dedict.sentinel(PEP 661) : des valeurs sentinelles officielles, en remplacement du classiqueobject().- Unpacking dans les comprehensions (PEP 798) :
*et**en tête d’expression pour aplatir et fusionner plus lisiblement. - Typage plus précis (PEP 728 et 747) :
TypedDictfermé etTypeForm. - Profiler Tachyon (PEP 799) : échantillonnage statistique à faible coût.
- Ramasse-miettes : retour au générationnel, acté dès la 3.14.5.
Python 3.15 ne réinvente pas le langage. Il s’attaque à de vrais points de friction du quotidien, et c’est précisément ce qui en fait l’une des mises à jour les plus utiles de ces dernières années.
Ressources
- What’s New in Python 3.15 (documentation officielle)
- PEP 810 - Explicit lazy imports
- PEP 814 - frozendict built-in type
- PEP 661 - Sentinel Values
- PEP 798 - Unpacking in Comprehensions
- PEP 728 - TypedDict with Typed Extra Items
- PEP 747 - Annotating Type Forms
- PEP 799 - Tachyon, profiler par échantillonnage
- PEP 790 - Python 3.15 Release Schedule
