Comme tous les ans désormais au mois d'octobre, une nouvelle version de Python est de sorti. Ce cycle de développement annuel avait en effet démarré avec la version 3.9 (voir la PEP 602 à ce sujet).
Cette version 3.11 de Python était très attendu de la communauté notamment car elle apporte de nombreuses améliorations de performances. Sujet important pour Python qui a la réputation d'être un langage lent.
Des améliorations sur la performanceformat_paragraph
C'est donc la nouveauté la moins visible mais qui peut avoir le plus d'impact selon votre domaine. CPython (l'implémentation principale de Python) est désormais en moyenne 25% plus rapide avec Python 3.11 qu'avec Python 3.10.
La rapidité a été améliorée jusqu'à 60% dans certains cas de figure ce qui est loin d'être négligeable !
Personnellement, la "lenteur" relative de Python ne m'a jamais posé de problèmes en près de 10 ans d'utilisation du langage. Les problèmes de lenteurs étant bien plus souvent causés par le développeur que le langage lui-même (je prends souvent l'exemple d'un développeur qui trouvait son site développé avec Django trop lent et dont toutes les images sur le site étaient des PNG de 10mb et avec des requêtes SQL très mal optimisées).
Mais du code peu optimisé qui va 60% plus vite, c'est toujours bon à prendre 😁
Plus rapide au démarrageformat_paragraph
Python est aussi et surtout plus rapide au démarrage. Cela peut sembler anecdotique mais quand vous faites rouler des scripts des centaines de fois à la seconde ça peut avoir un gros impact.
Je n'ai personnellement vu que très peu de différence entre la version 3.10 et 3.11 de Python (la documentation parle d'une amélioration de l'ordre de 10%).
Mais si on fait la comparaison entre une version un peu plus ancienne de Python comme la 3.6 et la 3.11, là on commence à clairement voir l'amélioration au fil des versions.
Petit exemple avec 250 exécution d'un script Python qui contient une simple instruction pass
:
Avec Python 3.6
#!/bin/bash SECONDS=0 for i in {1..250} do /usr/bin/time python3.6 -c "pass" done duration=$SECONDS echo "$(($duration % 60)) seconds elapsed." $ speed-test36.sh 0.03 real 0.02 user 0.00 sys 0.03 real 0.02 user 0.00 sys 0.03 real 0.02 user 0.00 sys ... 0.03 real 0.02 user 0.00 sys 0.02 real 0.02 user 0.00 sys 0.03 real 0.02 user 0.00 sys 8 seconds elapsed.
Avec Python 3.11
#!/bin/bash SECONDS=0 for i in {1..250} do /usr/bin/time python3.11 -c "pass" done duration=$SECONDS echo "$(($duration % 60)) seconds elapsed." $ speed-test311.sh 0.02 real 0.01 user 0.00 sys 0.01 real 0.01 user 0.00 sys 0.02 real 0.01 user 0.00 sys ... 0.02 real 0.01 user 0.00 sys 0.02 real 0.01 user 0.00 sys 0.02 real 0.01 user 0.00 sys 5 seconds elapsed.
On passe de 8 à 5 secondes sur 250 exécutions, ce qui est presque 2x plus rapide !
Imaginez sur des scripts qui tournent des centaines de fois toute la journée, le gain de temps peut vite devenir conséquent.
Exception notesformat_paragraph
Il est désormais possible d'ajouter des notes aux exceptions avec la méthode add_note
:
import requests def exception_notes(): try: r = requests.get('http://www.google.comx') except requests.exceptions.RequestException as e: e.add_note("Couldn't fetch Google...") raise exception_notes()
Groupes d'exceptionsformat_paragraph
Python 3.11 apporte la possibilité de grouper des exceptions grâce à la classe ExceptionGroup
et à la nouvelle notation except*
.
Petit exemple simple pour montrer la syntaxe :
try: raise ExceptionGroup("Exception Group for multiple errors", ( ValueError("This is a value error"), TypeError("This is a type error"), KeyError("This is a Key error"), AttributeError('This is an Attribute Error'), AttributeError('This is another Attribute Error') )) except* AttributeError as err: raise err except* (ValueError, TypeError) as err: raise err except* KeyError as err: raise err
Et un exemple un peu plus complet pour montrer un cas d'usage avec également la nouvelle méthode add_note
:
import requests def test_links(urls: list) -> None: exceptions = [] for url in urls: try: requests.get(url) except Exception as e: e.add_note(url) exceptions.append(e) if exceptions: raise ExceptionGroup("Couldn't fetch some URLs", exceptions) def write_to_file(exceptions, file_name): with open(f"log/{file_name}.txt", "w") as f: for exception in exceptions: f.write(f"{exception.__notes__[0]}\n") try: test_links( urls=[ "ht://www.google.com", "http://www.google.comx", "http://www.google.coms", "http://www.google.coma", "www.google.com", ] ) except* requests.exceptions.InvalidSchema as e: write_to_file(e.exceptions, "invalid_schema") except* requests.exceptions.MissingSchema as e: write_to_file(e.exceptions, "missing_schema") except* requests.exceptions.ConnectionError as e: write_to_file(e.exceptions, "connection_error") else: print("All links are valid")
Typing de selfformat_paragraph
Du côté des annotations de type, il est désormais possible d'indiquer qu'une méthode retourne une instance de la classe avec le mot-clé Self
disponible dans le module typing
:
from typing import Self class CustomPath: def __init__(self, path: str): self.path = path # La méthode concat retourne une instance de la classe CustomPath def concat(self, other: str) -> Self: return CustomPath(f'{self.path}/{other}') def __str__(self): return self.path
Messages d'erreurs plus précisformat_paragraph
Les messages d'erreurs sont désormais plus précis en indiquant spécifiquement où se situe une erreur dans le traceback.
Prenez les trois exemples ci-dessous :
def example1(): d = {"uno": [1, [1, 2, 3], 3]} print(d["uno"][5][2]) def example2(): a, b, c, d, e, f = 1, 2, 0, 4, 5, 6 print(a / b / c / d / e / f) def example3(): a = None b = "" print(a.capitalize() + b.capitalize()) example1() example2() example3()
Pour l'exemple #2, impossible avec Python <3.10 de savoir où se situe le problème dans l'opération mathématique.
Avec Python 3.10
Traceback (most recent call last): File "/Users/thibh/python-311-new-features/errors_handling/errors_handling.py", line 18, in <module> example2() File "/Users/thibh/python-311-new-features/errors_handling/errors_handling.py", line 8, in example2 print(a / b / c / d / e / f) ZeroDivisionError: float division by zero
Avec Python 3.11
Traceback (most recent call last): File "/Users/thibh/python-311-new-features/errors_handling/errors_handling.py", line 18, in <module> example2() File "/Users/thibh/python-311-new-features/errors_handling/errors_handling.py", line 8, in example2 print(a / b / c / d / e / f) ~~~~~~^~~ ZeroDivisionError: float division by zero
TOML Supportformat_paragraph
Python utilise le format TOML depuis de nombreuses années comme fichiers de configurations dans de nombreux cas de figure mais il n'était jusqu'à présent pas possible de lire ces fichiers nativement.
C'est désormais possible avec l'ajout de la librairie tomllib qui permet de lire des fichiers de configuration .toml !
À noter que pour l'instant, seule la lecture est possible, cette librairie ne permet pas (encore ?) de créer des fichiers .toml.
# config.toml title = "TOML Example" [owner] name = "Tom Preston-Werner" dob = 1979-05-27T07:32:00-08:00 [database] enabled = true ports = [ 8000, 8001, 8002 ] data = [ ["delta", "phi"], [3.14] ] temp_targets = { cpu = 79.5, case = 72.0 }
import tomllib with open("setup.toml", "rb") as f: data = tomllib.load(f) print(data) print(data['owner']['name']) print(data['database']['ports'])
Python uses TOML, or Tom's Obvious Minimal Language, as a configuration format (as in pyproject.toml), but doesn't expose the ability to read TOML-format files as a standard library module. Python 3.11 adds tomllib to address that problem. Note that tomllib doesn't create or write TOML files; for that you need a third-party module like Tomli-W or TOML Kit.
AsyncIO Task Groupsformat_paragraph
L'ajout de la classe TaskGroup
permet de créer des groupes de tâches asynchrones. Auparavant, il fallait passer par asyncio.gather() pour effectuer cette opération.
import asyncio import math async def t1(): print(int("hello")) await asyncio.sleep(2) async def t2(): print(math.sqrt(-10)) await asyncio.sleep(1) async def main(): try: async with asyncio.TaskGroup() as tg: tg.create_task(t1()) tg.create_task(t2()) except* ValueError as e: print(e.exceptions) if __name__ == '__main__': asyncio.run(main())
Vous noterez également dans le code ci-dessus la possibilité d'utiliser les groupes d'exceptions pour récupérer les erreurs potentielles des tâches asynchrones.
C'est d'ailleurs l'ajout des TaskGroup qui a nécessité l'ajout des groupes d'exceptions. Comme quoi, une nouveauté bénéficie bien souvent à une autre 😉
Améliorations de la librairie standardformat_paragraph
Le module math
Nouvelles fonctions ajoutées au module math :
>>> import math >>> math.cbrt(27) 3.0000000000000004 >>> math.exp2(6) 64.0
Récupérer uniquement les dossiers avec pathlib
La méthode glob
de la classe Path
du module pathlib
permet désormais d'indiquer directement si l'on souhaite récupérer uniquement les dossiers à l'intérieur d'un dossier.
Il suffit pour cela d'ajouter un slash à la fin du chemin :
from pathlib import Path p = Path("/Users/thibh/python-311-new-features/standard_lib/paths_tests") print("Fichiers et dossiers") dirs = p.glob("*") for d in dirs: print(d) print("Dossiers seulement") dirs = p.glob("*/") for d in dirs: print(d)
Fichiers et dossiers /Users/thibh/python-311-new-features/standard_lib/paths_tests/document.pdf /Users/thibh/python-311-new-features/standard_lib/paths_tests/test1 /Users/thibh/python-311-new-features/standard_lib/paths_tests/test3 /Users/thibh/python-311-new-features/standard_lib/paths_tests/test2 /Users/thibh/python-311-new-features/standard_lib/paths_tests/image1.png Dossiers seulement /Users/thibh/python-311-new-features/standard_lib/paths_tests/test1 /Users/thibh/python-311-new-features/standard_lib/paths_tests/test3 /Users/thibh/python-311-new-features/standard_lib/paths_tests/test2
Les StrEnumformat_paragraph
Il est désormais possible d'utiliser la fonction auto pour créer des énumérations à partir de chaîne de caractères automatiquement grâce à la classe StrEnum
:
from enum import StrEnum, auto class Color(StrEnum): RED = auto() GREEN = auto() BLUE = auto()
Si on fait un print de la valeur de BLUE
, une chaîne de caractères en minuscule est automatiquement générée :
>>> print(Color.BLUE.value) "blue"
Dépréciationsformat_paragraph
La PEP 594 annonce la dépréciation à venir de nombreux modules de la librairie standard :
- aifc
- chunk
- msilib
- pipes
- Utilisez subprocess à la place
- telnetlib
- audioop
- crypt
- nis
- sndhdr
- uu
- cgi
- imghdr
- filetype, puremagic, python-magic
- nntplib
- spwd
- xdrlib
- cgitb
- mailcap
- ossaudiodev
- sunau
Ces modules seront définitivement supprimés de Python dans la version 3.13, à l'exception de asynchat, asyncore et smtpd qui seront dépréciés dès la version 3.12.