
Un langage qui gagne en popularité
Depuis un an déjà, Python occupe la première place de l'index TIOBE. Lors de son arrivée en pole position, Paul Jansen, PDG de Tiobe, notait déjà que c'était une première en 20 ans :
« Pour la première fois depuis plus de 20 ans, nous avons un nouveau chef de file : le langage de programmation Python. L'hégémonie de longue date de Java et C est terminée. Python, qui a commencé comme un simple langage de script, comme alternative à Perl, est devenu mature. Sa facilité d'apprentissage, son énorme quantité de bibliothèques et son utilisation répandue dans toutes sortes de domaines en ont fait le langage de programmation le plus populaire d'aujourd'hui. Félicitations Guido van Rossum ! Proficiat ! »
Python est un langage de programmation interprété, multi-paradigme et multi-plateformes. Il favorise la programmation impérative structurée, fonctionnelle et orientée objet. Il est doté d'un typage dynamique fort, d'une gestion automatique de la mémoire par récupérateur de mémoire et d'un système de gestion d'exceptions ; il ressemble ainsi à Perl, Ruby, Scheme, Smalltalk et Tcl.
Python gagne en popularité ces temps-ci, en partie à cause de l'essor de la science des données et de son écosystème de bibliothèques logicielles d'apprentissage automatique comme NumPy, Pandas, TensorFlow de Google et PyTorch de Facebook.
En effet, Python continuerait d'être la norme et la compétence la plus recherchée dans le domaine de la science des données, dépassant de loin les autres technologies et outils, comme R, SAS, Hadoop et Java. C'est ce que suggère une analyse réalisée par Terence Shin, un spécialiste des données, qui a indiqué que l'adoption de Python pour la science des données continue de croître alors même que le langage R, plus spécialisé, est en déclin. Bien entendu, cela ne veut pas dire que les spécialistes des données vont abandonner R de sitôt. L'on continuera probablement à voir Python et R utilisés pour leurs forces respectives.
Python 3.11 gagne en rapidité grâce à un interpréteur adaptatif spécialisé
Au cours de l'année écoulée, Microsoft a financé une équipe - dirigée par les principaux développeurs Mark Shannon et Guido van Rossum - pour travailler à plein temps sur l'accélération de CPython, la nouvelle version de l'interpréteur Python standard. Avec un financement supplémentaire de Bloomberg et l'aide d'un large éventail d'autres contributeurs de la communauté, les résultats ont porté leurs fruits. Sur les benchmarks pyperformance au moment de la sortie de la version bêta, Python 3.11 était environ 1,25 fois plus rapide que Python 3.10, une réalisation phénoménale.
Mais il reste encore beaucoup à faire. Lors du Python Language Summit 2022, Mark Shannon a présenté la prochaine étape du projet Faster CPython. L'avenir est rapide.
Le premier problème soulevé par Shannon était un problème de mesures. Afin de savoir comment rendre Python plus rapide, nous devons savoir à quel point Python est actuellement lent. Mais lent à faire quoi, exactement ? Et à quel point ?
De bons benchmarks sont essentiels pour un projet qui vise à optimiser Python pour une utilisation générale. Pour cela, l'équipe Faster CPython a besoin de l'aide de la communauté dans son ensemble. Le projet « a besoin de plus de repères », a déclaré Shannon - il doit comprendre plus précisément pourquoi la base d'utilisateurs dans son ensemble utilise Python, comment ils le font et ce qui le rend lent pour le moment (si c'est lent !) .
Une référence, a expliqué Shannon, est « juste un programme que nous pouvons chronométrer ». Toute personne ayant une référence - ou même simplement une suggestion de référence ! – qu'elle pense être représentatif d'un projet plus vaste sur lequel elle travaille est invitée à les soumettre au suivi des problèmes du référentiel python/pyperformance sur GitHub.
Néanmoins, l'équipe Faster CPython a de quoi s'occuper entre-temps.
Une grande partie du travail d'optimisation dans 3.11 a été réalisée grâce à la mise en œuvre de PEP 659, un « interpréteur adaptatif spécialisé ». L'interpréteur adaptatif que Shannon et son équipe ont introduit suit les bytecodes individuels à différents moments de l'exécution d'un programme. Lorsqu'il repère une opportunité, un bytecode peut être « accéléré » : cela signifie qu'un bytecode lent, qui peut faire beaucoup de choses, est remplacé par l'interpréteur par un bytecode plus spécialisé qui est très bon pour faire une chose spécifique.
Shannon a noté que Python a également essentiellement la même consommation de mémoire en 3.11 qu'en 3.10. C'est quelque chose sur lequel il aimerait travailler : une surcharge de mémoire plus petite signifie généralement moins d'opérations de comptage de références dans la machine virtuelle, une surcharge de collecte de mémoire plus faible et des performances plus fluides en conséquence.
Une autre grande piste d'optimisation restante est la question des extensions C. L'interface simple de CPython avec C est son principal avantage par rapport aux autres implémentations Python telles que PyPy, où les incompatibilités avec les extensions C sont l'un des plus grands obstacles à l'adoption par les utilisateurs. Le travail d'optimisation qui a été fait dans CPython 3.11 a largement ignoré la question des modules d'extension, mais Shannon veut maintenant ouvrir la possibilité d'exposer des API de fonction de bas niveau à la machine virtuelle, réduisant ainsi le temps de communication entre le code Python et Code C.
Meilleure gestion des erreurs
Python 3.10 nous a donné de meilleurs messages d'erreur à divers égards, mais Python 3.11 vise à les améliorer encore plus. Certaines des choses les plus importantes qui sont ajoutées aux messages d'erreur dans Python 3.11 sont :
Emplacements exacts des erreurs dans les retraçages
Jusqu'à présent, dans un traçage, la seule information que vous obteniez sur l'endroit où une exception a été déclenchée était la ligne. Le problème aurait pu être n'importe où sur la ligne, donc parfois cette information n'était pas suffisante.
Voici un exemple :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def get_margin(data): margin = data['profits']['monthly'] / 10 + data['profits']['yearly'] / 2 return margin data = { 'profits': { 'monthly': 0.82, 'yearly': None, }, 'losses': { 'monthly': 0.23, 'yearly': 1.38, }, } print(get_margin(data)) |
Ce code génère une erreur, car l'un de ces champs dans le dictionnaire est None. Voici ce que nous obtenons :

Sur la version 3.11 cependant :

Pour pouvoir restituer ces informations, les données end_line et end_col ont été ajoutées aux objets de code Python. Vous pouvez également accéder à ces informations directement via la méthode obj.__code__.co_positions().
Les notes pour les exceptions
Pour rendre les traces encore plus riches en contexte, Python 3.11 vous permet d'ajouter des notes aux objets d'exception, qui sont stockées dans les exceptions et affichées lorsque l'exception est déclenchée.
Prenez ce code par exemple, où nous ajoutons des informations importantes sur une logique de conversion de données d'API :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def get_seconds(data): try: milliseconds = float(data['milliseconds']) except ValueError as exc: exc.add_note( "The time field should always be a number, this is a critial bug. " "Please report this to the backend team immediately." ) raise # re-raises the exception, instead of silencing it seconds = milliseconds / 1000 return seconds get_seconds({'milliseconds': 'foo'}) # 'foo' is not a number! |
Cette note ajoutée est imprimée juste en dessous du message d'exception :

La bibliothèque standard a maintenant un support intégré pour lire les fichiers TOML, en utilisant le module tomllib :
Code Python : | Sélectionner tout |
1 2 3 4 | import tomllib with open('.deepsource.toml', 'rb') as file: data = tomllib.load(file) |
tomllib est en fait basé sur une bibliothèque d'analyse TOML open source appelée tomli. Et actuellement, seule la lecture des fichiers TOML est prise en charge. Si vous devez plutôt écrire des données dans un fichier TOML, envisagez d'utiliser le package tomli-w.
Groupes de travail asynchrones
Lorsque vous faites de la programmation asynchrone, vous rencontrez souvent des situations où vous devez déclencher de nombreuses tâches à exécuter simultanément, puis prendre des mesures lorsqu'elles sont terminées. Par exemple, télécharger un tas d'images en parallèle, puis les regrouper dans un fichier zip à la fin.
Pour ce faire, vous devez collecter des tâches et les transmettre à asyncio.gather. Voici un exemple simple de tâches exécutées en parallèle avec la fonction de gather :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import asyncio async def simulate_flight(city, departure_time, duration): await asyncio.sleep(departure_time) print(f"Flight for {city} departing at {departure_time}PM") await asyncio.sleep(duration) print(f"Flight for {city} arrived.") flight_schedule = { 'boston': [3, 2], 'detroit': [7, 4], 'new york': [1, 9], } async def main(): tasks = [] for city, (departure_time, duration) in flight_schedule.items(): tasks.append(simulate_flight(city, departure_time, duration)) await asyncio.gather(*tasks) print("Simulations done.") asyncio.run(main()) |
Mais devoir maintenir une liste des tâches soi-même pour pouvoir les attendre est un peu maladroit. Aussi, une nouvelle API est ajoutée à asyncio appelée Groupes de tâches :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import asyncio async def simulate_flight(city, departure_time, duration): await asyncio.sleep(departure_time) print(f"Flight for {city} departing at {departure_time}PM") await asyncio.sleep(duration) print(f"Flight for {city} arrived.") flight_schedule = { 'boston': [3, 2], 'detroit': [7, 4], 'new york': [1, 9], } async def main(): async with asyncio.TaskGroup() as tg: for city, (departure_time, duration) in flight_schedule.items(): tg.create_task(simulate_flight(city, departure_time, duration)) print("Simulations done.") asyncio.run(main()) |
Lorsque le gestionnaire de contexte asyncio.TaskGroup() se ferme, il s'assure que toutes les tâches créées à l'intérieur ont fini de s'exécuter.
Groupes d'exceptions
Une fonctionnalité similaire a également été ajoutée pour la gestion des exceptions dans les tâches asynchrones, appelées groupes d'exceptions.
Supposons que de nombreuses tâches asynchrones s'exécutent ensemble et que certaines d'entre elles génèrent des erreurs. Actuellement, le système de gestion des exceptions de Python ne fonctionne pas bien dans ce scénario.
Voici une courte démo de ce à quoi cela ressemble avec 3 tâches simultanées qui ont planté :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | import asyncio def bad_task(): raise ValueError("oops") async def main(): tasks = [] for _ in range(3): tasks.append(asyncio.create_task(bad_task())) await asyncio.gather(*tasks) asyncio.run(main()) |
Lorsque vous exécutez ce code :

Mais en Python 3.11, le comportement est un peu meilleur :
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | import asyncio async def bad_task(): raise ValueError("oops") async def main(): async with asyncio.TaskGroup() as tg: for _ in range(3): tg.create_task(bad_task()) asyncio.run(main()) |

La gestion des exceptions avec ces groupes d'exceptions est également intéressante, vous pouvez soit faire except ExceptionGroup pour intercepter toutes les exceptions en une seule fois :
Code Python : | Sélectionner tout |
1 2 3 4 | try: asyncio.run(main()) except ExceptionGroup as eg: print(f"Caught exceptions: {eg}") |

Code Python : | Sélectionner tout |
1 2 3 4 | try: asyncio.run(main()) except* ValueError as eg: print(f"Caught ValueErrors: {eg}") |

Le module typing a vu beaucoup de mises à jour intéressantes dans cette version. Voici quelques-uns des plus intéressantes :
Génériques variadiques
La prise en charge des génériques variadiques a été ajoutée au module typing dans Python 3.11.
Cela signifie que vous pouvez désormais définir des types génériques pouvant contenir un nombre arbitraire de types. Il est utile pour définir des méthodes génériques pour les données multidimensionnelles.
Par exemple:
Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from typing import Generic from typing_extensions import TypeVarTuple, Unpack Shape = TypeVarTuple('Shape') class Array(Generic[Unpack[Shape]]): ... # holds 1 dimensional data, like a regular list items: Array[int] = Array() # holds 3 dimensional data, for example, X axis, Y axis and value market_prices: Array[int, int, float] = Array() # This function takes in an `Array` of any shape, and returns the same shape def double(array: Array[Unpack[Shape]]) -> Array[Unpack[Shape]]: ... # This function takes an N+2 dimensional array and reduces it to an N dimensional one def get_values(array: Array[int, int, *Shape]) -> Array[*Shape]: ... # For example: vector_space: Array[int, int, complex] = Array() reveal_type(get_values(vector_space)) # revealed type is Array[complex] |
Les génériques variadiques peuvent être très utiles pour définir des fonctions qui mappent sur des données à N...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.