🔒 Connexion

Python

Migrer de version Python : ce qui change, de 2.7 à 3.14

9 août 2026 · par Romain RICHER

« Python 2, c’est fini depuis 2020, on est tous en Python 3. » C’est vrai, et c’est aussi ce qui rend la question intéressante : la vraie migration Python d’aujourd’hui ne se fait plus de 2 vers 3, mais de 3.9 vers 3.12, ou de 3.11 vers 3.14. Et ces migrations-là ont une particularité déroutante — elles ne cassent presque jamais la syntaxe. Votre code reste parfaitement lisible par l’interpréteur. Il ne démarre simplement plus, parce qu’un module a disparu. Cet article passe en revue ce qui change réellement à chaque version, et comment le repérer avant la mise en production.

Les dates de fin de support reflètent la situation en août 2026. Les suppressions décrites, elles, sont définitives.

Le calendrier, et pourquoi il compte

Python sort une version mineure chaque année en octobre. Chacune reçoit deux ans de corrections de bugs, puis trois ans de correctifs de sécurité seulement — cinq ans en tout. Ensuite, plus rien : une faille découverte après cette date reste ouverte indéfiniment.

En août 2026, cinq branches sont encore maintenues : 3.10, 3.11, 3.12, 3.13 et 3.14. La dernière version stable est la 3.14. Et une échéance approche : Python 3.10 s’arrête le 31 octobre 2026, dans un peu plus de deux mois.

Version Sortie Fin de support Ce qu’elle change
2.7 2010 jan. 2020 Dernière de la branche 2
3.8 2019 oct. 2024 Opérateur morse, f"{x=}"
3.9 2020 oct. 2025 Fusion de dictionnaires par |
3.10 2021 oct. 2026 match, messages d’erreur lisibles
3.11 2022 oct. 2027 Jusqu’à 60 % plus rapide, groupes d’exceptions
3.12 2023 oct. 2028 distutils supprimé, f-strings assouplies
3.13 2024 oct. 2029 Dix-neuf modules retirés, mode sans GIL expérimental
3.14 oct. 2025 oct. 2030 Annotations différées, mode sans GIL officiel

Python 2 vers 3 : le cas d’école

Beaucoup de code ancien traîne encore dans des scripts d’administration, des traitements par lots, des outils internes que personne n’ose toucher. La conversion est bien documentée et largement mécanique.

print devient une fonction : print "x" s’écrit print("x"). La syntaxe des exceptions change : except ValueError, e devient except ValueError as e. raw_input() devient input() — et l’ancienne input() de Python 2, qui évaluait la saisie de l’utilisateur comme du code, disparaît, ce qui est une excellente nouvelle. xrange() devient range(), iteritems() devient items(), dict.has_key(k) s’écrit k in dict, et l’opérateur <> laisse place à !=.

Une seule rupture mérite qu’on s’y arrête longuement, parce qu’elle ne produit aucune erreur.

# Python 2        Python 3
7 / 2    # 3               3.5
7 // 2   # 3               3

La division rend désormais un flottant, même entre deux entiers. Le programme continue de tourner, sans avertissement, avec des résultats faux partout où l’on attendait une division entière — un calcul d’index, une pagination, un découpage en lots. La correction consiste à écrire //, mais elle dépend de l’intention : aucun outil ne peut deviner si total / nb voulait dire « combien exactement » ou « combien de paquets entiers ». C’est au relecteur de trancher.

Un détail qui a son importance en 2026 : l’outil officiel 2to3, livré avec Python depuis toujours, a été supprimé en Python 3.13. La migration depuis Python 2 est donc devenue un peu plus solitaire qu’avant.

3.9 vers 3.12 : la disparition de distutils

C’est le blocage numéro un des migrations Python actuelles. Le module distutils, présent depuis les origines pour empaqueter et installer du code, a été supprimé en Python 3.12 après une longue dépréciation. Le remplacement passe par setuptools, et par la bibliothèque packaging pour tout ce qui touche à la comparaison de numéros de version.

Le piège n’est pas tant votre code que celui de vos dépendances : un paquet ancien qui importe distutils dans son setup.py fera échouer l’installation entière. D’où un réflexe utile avant toute migration — créer un environnement virtuel avec la version visée et réinstaller les dépendances. Chaque échec devient une tâche identifiée.

Deux autres suppressions accompagnent la 3.12 : le module imp, remplacé par importlib, et le trio asynchat, asyncore, smtpd, remplacé par asyncio.

La 3.12 apporte aussi une dépréciation discrète et sournoise : datetime.utcnow(). Elle rendait un objet sans fuseau horaire, ce qui produit des comparaisons fausses dès qu’on la confronte à une date qui en porte un. Le remplacement s’écrit datetime.now(timezone.utc) — mais il change le type de l’objet rendu, donc tout le code qui consomme la valeur doit être relu.

3.12 vers 3.13 : les « piles usagées »

Python s’est longtemps vanté d’être livré « piles incluses » — une bibliothèque standard si fournie qu’on pouvait tout faire sans rien installer. Certaines de ces piles dataient des années 1990 et ne servaient plus à grand-chose. Le PEP 594 les a retirées d’un coup en Python 3.13 :

aifc, audioop, cgi, cgitb, chunk, crypt, imghdr, mailcap, msilib, nis, nntplib, ossaudiodev, pipes, sndhdr, spwd, sunau, telnetlib, uu, xdrlib.

Dix-neuf modules. La plupart ont un équivalent sur PyPI, mais aucun n’est plus fourni avec le langage. Et c’est là toute la difficulté de ce type de migration : un import telnetlib ne devient pas une erreur de syntaxe. Le fichier se lit parfaitement, les tests qui ne touchent pas cette branche du code passent au vert, et l’échec survient au premier appel réel — parfois en production, parfois trois semaines après la mise en ligne.

C’est exactement le genre de chose qu’une analyse statique trouve en une seconde et qu’une relecture humaine laisse passer.

3.13 vers 3.14 : les annotations changent de nature

La 3.14, sortie en octobre 2025, adopte l’évaluation différée des annotations (PEP 649). Concrètement, les annotations de type ne sont plus évaluées à la définition de la fonction, mais à la demande. L’import from __future__ import annotations, longtemps nécessaire, devient inutile — et le code qui inspectait __annotations__ en s’attendant à des chaînes de caractères doit être revu. Cela concerne surtout les bibliothèques qui font de l’introspection : validateurs de données, injecteurs de dépendances, générateurs de documentation.

Autre disparition : typing.ByteString, à remplacer par bytes ou par collections.abc.Buffer selon l’intention.

Et la nouveauté qui fera parler d’elle dans les années à venir : le mode sans GIL, expérimental en 3.13, officiellement pris en charge en 3.14. Il ouvre le parallélisme réel sur plusieurs cœurs, mais reste une configuration de compilation à part — ce n’est pas encore le Python que vous installez par défaut.

Ce qu’un outil peut faire, et ce qu’il ne peut pas

Une bonne part du travail est mécanique et vérifiable : print, xrange, iteritems(), collections.Mapping devenu collections.abc.Mapping, datetime.utcnow(). Ces transformations sont des équivalences, ou des corrections dont la forme ne fait aucun doute.

C’est ce que fait notre outil de migration de version. Vous collez votre code ou vous déposez une archive de votre projet, vous choisissez la version visée — 3.11, 3.12, 3.13, 3.14, ou « Python 3 » si vous partez de Python 2 — et il classe chaque point en quatre niveaux : corrigé (réécrit sans risque), à vérifier (réécrit, mais relisez), à reprendre (signalé, votre code n’est pas touché) et information. Tout se passe dans votre navigateur : aucun code n’est envoyé, ce qui compte quand il appartient à un client.

La troisième catégorie est la plus honnête. Un import telnetlib ne se remplace pas par substitution : il faut choisir une bibliothèque tierce, ou réécrire la fonctionnalité. Une division / ne se corrige pas sans savoir ce qu’elle calcule. Un outil qui produirait ici du code d’apparence correcte rendrait un très mauvais service — le pire résultat n’est pas celui qui échoue, c’est celui qui semble juste.

Une méthode qui tient

  • Commencer par les dépendances. Créez un environnement virtuel avec la version visée et réinstallez votre requirements.txt. Chaque échec est une tâche. C’est souvent là que se trouve le vrai travail, pas dans votre code.
  • Passer le code à l’analyse ensuite. Les modules disparus et les appels dépréciés se repèrent sans exécuter quoi que ce soit — c’est le grand avantage d’une migration Python sur une migration PHP.
  • Activer les avertissements de dépréciation. Lancez vos tests avec python -W error::DeprecationWarning : ce qui n’est aujourd’hui qu’un message deviendra une suppression à la version suivante.
  • Monter d’une version à la fois. 3.9 → 3.10 → 3.11 → 3.12. Chaque palier isole ses propres ruptures, et le diagnostic reste simple quand quelque chose casse.
  • Épingler la version dans le projet. pyproject.toml, .python-version, image Docker de base, configuration d’intégration continue : quatre endroits où une version traîne, et où l’oubli se paie plus tard.

En résumé

  • La vraie migration Python d’aujourd’hui va de 3.x à 3.y, pas de 2 à 3. Python 3.10 s’arrête le 31 octobre 2026.
  • Ces migrations ne cassent presque jamais la syntaxe : elles retirent des modules. Le code reste valide et échoue à l’exécution.
  • 3.12 supprime distutils, blocage numéro un — et souvent du fait des dépendances, pas du code propre.
  • 3.13 retire dix-neuf modules de la bibliothèque standard, dont cgi et telnetlib. Et supprime 2to3.
  • Le piège historique de Python 2 vers 3 reste la division, qui ne lève aucune erreur.

Une migration de version n’a rien de passionnant, mais elle a le mérite d’être un projet fini : la liste des suppressions est publiée, vérifiable, et se raccourcit à chaque palier franchi. Le plus dur est de commencer, et le meilleur moment reste avant que la branche que vous utilisez ne cesse d’être corrigée.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les commentaires sont modérés avant publication.