Les nouveautés de la version 3.12 de Python
- 2023-07-12
- Publié par : Christophe DELEUZE
- Catégorie : Python
Comme avant chaque release, je vous propose un petit tour d’horizon de ce qui vous attend pour Python 3.12 et qui apporte son lot d’optimisations et d’améliorations qui mettent surtout l’accent sur l’amélioration de la vitesse, des performances et de la stabilité de l’interpréteur. Cette nouvelle version est conçue pour faire de Python un outil plus puissant et plus efficace pour les développeurs, en particulier pour les applications volumineuses et complexes.
Étant donné la teneur de la release, il n’y aura pas beaucoup de code, mais par contre, il y aura beaucoup de blabla !
Vrai parallélisme multithread en vue !
Bien que le langage Python soit âgé de 32 ans, il n’a toujours pas de véritable mécanisme de parallélisme/concurrence qui permet d’exécuter du code réellement de manière simultanée.
Cela va bientôt changer, grâce à l’introduction d’un “Per-Interpreter GIL” (Global Interpreter Lock) qui débarquera dans Python 3.12 et qui nous permettra d’exécuter du code Python de manière simultanée à l’aide de l’API des sous-interpréteurs.
Expliquons d’abord comment ce “Per-Interpreter GIL” résout le manque de simultanéité de Python.
En termes simples, GIL ou Global Interpreter Lock est un mutex qui permet à un seul thread de contrôler l’interpréteur Python. L’avantage majeur de ce procédé est qu’il empêche les blocages (puisqu’il n’y a qu’un seul verrou) et n’introduit pas beaucoup de surcharge de performance. Mais en contrepartie, cela signifie que même si vous créez plusieurs threads en Python (par exemple en utilisant le module de threading), un seul thread à la fois s’exécutera.
Avec l’introduction de “Per-Interpreter GIL“, les interpréteurs Python individuels ne partagent plus le même GIL. Ce niveau d’isolement permet à chacun de ces sous-interpréteurs de s’exécuter réellement simultanément. Donc, nous allons maintenant pouvoir contourner les limitations de concurrence de Python en créant des sous-interpréteurs supplémentaires, où chacun d’eux aura son propre GIL (état global). Pour une explication plus détaillée, voir la PEP 684 qui décrit cette fonctionnalité/modification.
Malheureusement, même si Python 3.12 embarque cette fonctionnalité qui est très attendue depuis des années, nous n’en bénéficierons pas avant Python 3.13. Comme indiqué dans PEP 684 :
“… il s’agit d’une fonctionnalité avancée destinée à un groupe restreint d’utilisateurs de la C-API.”
Les fonctionnalités de Per-Interpreter GIL sont, pour l’instant, uniquement disponibles en utilisant C-API, il n’y a donc pas d’interface directe pour les développeurs Python. Une telle interface devrait venir avec la PEP 554 qui, si elle est acceptée, sera intégrée dans Python 3.13.
Amélioration de la Spécialisations des bytescodes
Commençons par un rappel, Python 3.11 bénéficiait d’une amélioration en performance (vitesse) non négligeable grâce à la spécialisation de certains bytescodes. Pour rappel, ce gain est permis grâce à la spécialisation des instructions d’un code dont les appels sont répétitifs (Voir article sur Python 3.11 pour plus de détails). Ainsi, certaines instructions sont remplacées automatiquement au moment de l’exécution par des versions spécialisées pour un type Python donné, évitant à l’interpréteur d’avoir à rechercher les types d’objets et accélérant ainsi énormément l’ensemble du processus. Par exemple, si une opération d’addition donnée prend régulièrement deux nombres entiers, cette instruction peut être remplacée par une autre qui suppose que les opérandes sont tous deux des nombres entiers.
Python 3.12 élargit son périmètre des spécialisations, ce qui entrainera encore un gain de performance par rapport à la version 3.11. Python 3.12 ajoute notamment la spécialisation des accesseurs pour les attributs dynamiques, qui sont des opérations lentes. La version 3.12 simplifie également le processus global de spécialisation, avec moins d’étapes impliquées.
En complément, en marge de Python 3.12, Un nouvel outil appelé specialist et développer par Brandt Bucher a été mis à disposition de la communauté pour aider à analyser où des optimisations sont possibles.
Améliorations diverses
Avec Python 3.11, un certain nombre de projets parallèles ont été lancés pour améliorer les performances de Python à pas de géant avec chaque nouvelle version. Les améliorations de performances dans Python 3.12 ne sont pas aussi spectaculaires que la 3.11 mais elles sont tout de même remarquables.
Compréhensions en ligne
Objets immortels
Chaque objet dans Python a un compteur de références qui suit le nombre de fois où d’autres objets y font référence, y compris des objets intégrés comme None. La PEP 683 permet aux objets d’être traités comme “immortels“, de sorte qu’ils ne voient jamais leur nombre de références modifié. Rendre les objets immortels a d’autres implications puissantes pour Python à long terme. Cela facilite la mise en œuvre de la mise à l’échelle multicœur et la mise en œuvre d’autres optimisations (comme éviter la copie sur écriture) qui auraient été difficiles à mettre en œuvre auparavant.
Objets plus compactes
Avec les versions antérieures de Python, la taille de base d’un objet était de 208 octets. Les objets ont été re-factorisés plusieurs fois au cours des dernières versions de Python pour les rendre plus petits, ce qui permet non seulement à plus d’objets d’exister en mémoire, mais aide également à la localisation du cache. Depuis Python 3.12, la taille de base d’un objet est désormais de 96 octets, soit moins de la moitié de ce qu’elle était auparavant.
Fonctionnalités supprimées et obsolètes
Depuis la PEP 632, et Python 3.10, distutils était marqué comme obsolète, mais avec la sortie de Python 3.12, distutils a finalement été supprimé.
distutils était autrefois le module privilégié pour la gestion des packages en Python, mais ses limitations ont conduit à l’essor de setuptools, qui est maintenant devenue la solution recommandée selon le Python Packaging User Guide. Setuptools utilise encore certaines fonctionnalités de distutils, mais il a intégré une copie de ce dernier et ne dépend plus de la bibliothèque standard. À noter que Pip a également remplacé distutils par setuptools depuis un certain temps à présent.
Python 3.12 supprime aussi les membres wstr et wstr_lenght d’Unicode, comme indiqué dans la PEP 623. La suppression de ces membres entraine une réduction de taille des objets str de 8 ou 16 octets sur les plates-formes 64 bits.
Enfin, d’autres modules sont aussi supprimés dans cette version telle que : asynchat, asyncore (tous deux remplacés par asyncio) et smtpd dont le remplaçant recommandé est aiosmtpd.
Rapports d'erreurs améliorés
Depuis quelques années déjà, la tendance de Python est à l’amélioration des messages d’erreurs et la version 3.12, n’échappe pas à son lot d’amélioration, en particulier pour le NameError où l’interpréteur fournira également des suggestions dans le message d’erreur. Exemple :
class Classe:
def __init__(self):
self.attribut = 1
def methode(self):
variable = attribut
Classe().methode()
File "<stdin>", line 1
variable = attribut
^^^^^^^^
NameError: name 'attribut' is not defined. Did you mean: 'self.attribut'?
Ajout de nouvelles fonctionnalités pour Typer
La syntaxe d’indication de type de Python, ajoutée dans Python 3.5, permet aux outils de filtrage de détecter à l’avance une grande variété d’erreurs. Avec chaque nouvelle version, cette syntaxe gagne en fonctionnalités pour couvrir une gamme plus large et plus granulaire de cas d’utilisation.
TypedDict
Dans Python 3.12, vous pouvez utiliser un TypedDict comme source de types pour suggérer les arguments de mots-clés utilisés dans une fonction. Pour cela, il faudra utiliser le type générique variadique Unpack, introduit dans la version 3.11 via la PEP 646 avec le TypedDict. Voici un exemple concret de ce que cela donne :
from typing import TypedDict
class Film(TypedDict):
nom: str
annee: int
def function(**kwargs: Unpack[Film]) -> None:
...
function peut prendre des arguments de mots-clés (kwargs) de noms et de types qui correspondent au contenu de Film, c’est-à-dire : nom: str et annee: int. Cela permet par exemple d’apporter des indications complémentaires à une fonction qui accepterait des arguments facultatifs contenant uniquement des mots-clés sans valeurs par défaut.Syntaxe du paramètre de type
Une nouvelle syntaxe du paramètre de type est aussi introduite dans Python 3.12 avec la PEP 695. Elle fournit un moyen plus simple de spécifier des types dans une classe générique, une fonction ou un alias de type. Voici un exemple :
# Ancienne façon de faire
from typing import TypeVar
_T = TypeVar("_T")
def func(a: _T, b: _T) -> _T:
...
# Nouvelle façon de faire
def func[T](a: T, b: T) -> T:
...
Avec la nouvelle méthode, il n’est pas nécessaire d’importer TypeVar. On peut simplement utiliser la syntaxe func[T] pour indiquer des références de type génériques. Il est également possible de spécifier des limites de type, par exemple si un type donné fait partie d’un groupe de types, bien que ces types ne puissent pas eux-mêmes être génériques. Un exemple est func[T: (str,int)].
@override
Enfin, la PEP 698 propose d’ajouter un décorateur @override aux indications de types en Python. Cela permettra aux vérificateurs de type de soulever des erreurs sur une classe lorsque celle-ci modifiera les méthodes héritées par les classes dérivées. Exemple concret :
class Parent0:
def foo() -> int:
return 1
class Parent1:
def bar() -> int:
return 1
class Enfant(Parent0, Parent1):
@override(Parent0) # Ok, Parent0 definit foo
def foo() -> int:
return 2
@override(Parent0) # Erreur de type, Parent0 ne définit pas bar
def bar() -> int:
return 2
Support du profiler de performance Linux
L’outil de profilage Linux largement utilisé : perf, fonctionne avec Python, mais ne renvoie que des informations sur ce qui se passe au niveau C dans l’environnement d’exécution Python. Les informations sur les fonctions réelles du programme Python ne s’affichent pas. Python 3.12 active un mode opt-in pour permettre à perf de récolter des détails sur les programmes Python. L’opt-in peut être fait au niveau de l’environnement ou à l’intérieur d’un programme Python avec la fonction sys.activate_stack_trampoline.
Debug et Profilage de code plus rapide
L’exécution d’un profileur ou l’association d’un débogueur à un programme Python nous donne une visibilité et un aperçu de ce que fait le programme. Cela a également un coût en termes de performances qui peut être très important.
La PEP 669 propose, tout simplement, de réduire ce coût en ajoutant des évènements sur les objets auxquels les profileurs et les débogueurs peuvent s’attacher, comme le début ou la fin d’une fonction.
Garbage Collection
Le mécanisme automatique de récupération de place (mémoire) de Python (GC : Garbage Collection) pouvait s’exécuter chaque fois qu’un objet était alloué. Depuis Python 3.12, le Garbage Collection ne s’exécute que sur le mécanisme “eval breaker” dans la boucle de bytecode Python, c’est-à-dire entre l’exécution d’un bytecode et d’un autre. Il s’exécute également chaque fois que le mécanisme de vérification du gestionnaire de signaux de CPython est invoqué. Cela permet d’exécuter périodiquement le Garbage Collection sur un appel de longue durée vers une extension C en dehors de la durée d’exécution, ce qui améliore la gestion mémoire.
Changements dans CPython
Un projet clé qui a abouti et qui est livré avec Python 3.12 est la refonte des composants internes de CPython de sorte que moins de fonctions de bas niveau de CPython soient exposées. Python 3.12 introduit un nouveau niveau d’API appelé : API instable (unstable) et qui sert à marquer spécifiquement les modules comme étant susceptible de changer entre les versions. Il n’est pas destiné à être utilisé par la plupart des extensions C, mais par des outils de bas niveau tels que des débogueurs ou des compilateurs JIT.
Petit ajout sympathique dans path et pathlib
os.path
L’introduction de os.path.isjunction() dans le module os de la bibliothèque standard permet maintenant aux utilisateurs de vérifier si un chemin est une jonction.
pathlib
Une nouvelle méthode fait son apparition dans pathlib : pathlib.Path.walk() qui permet de parcourir les arborescences de répertoires de la même façon que os.walk().
Le mot de la fin
Python 3.12 inclut plusieurs améliorations et modifications du langage. On retiendra principalement que c’est une version axée sur la performance et qui laisse présager une version 3.13 qui sera dans la même lignée !
En attendant de nous revoir dans un prochain article, comme d’habitude, voici le lien officiel, en anglais, vers l’ensemble des nouveautés apportées par python 3.12 :
Article complet sans superflu d’informations inutiles. J’ai plutôt hâte à vrai dire pour la nouvelle annotation de type générique, j’aime bien la manière dont python effectue des changements et ajouts de syntaxe assez régulièrement bien que j’apprécie moins l’absence de rétro-compatibilité (par exemple la syntaxe match n’est utilisable qu’à partir de la version 3.10 si je ne trompe pas et c’est assez dommage pour les projets open source)