URL: https://linuxfr.org/news/python-3-9-est-disponible Title: Python 3.9 est disponible Authors: Cyrille Pontvieux BenoĂźt Sibaud, palm123, Davy Defaud, ariasuni, Ysabeau đ§¶, bobble bubble et Snark Date: 2020ćčŽ11æ03æ„T15:08:31+01:00 License: CC By-SA Tags: python et sortie_version Score: 49 Python 3.9 est sorti le 5 octobre 2020, aprĂšs dixâsept mois de dĂ©veloppement. Voyons ce que cette version apporte comme nouveautĂ©s...  ---- [Notes de versions](https://www.python.org/downloads/release/python-390/?ref=hvper.com) ---- Cette version comporte pas moins de **neuf nouveautĂ©s** par rapport Ă la version 3.8 sortie le 14 octobre 2019, soit presque un an aprĂšs. # Calendrier des sorties La premiĂšre chose quâon peut noter câest justement la date de sortie. Habituellement, les versions de Python sortent tous les dixâhuit mois. La [PEP 602](https://www.python.org/dev/peps/pep-0602/) dĂ©cide de changer ça et de faire des sorties tous les **douze mois** et ceci pour plusieurs raisons : - diminuer la taille des versions ; - apporter de nouvelles fonctionnalitĂ©s plus vite ; - avoir une politique de mise Ă jour plus lissĂ©e (obsolescence graduelle) ; - avoir un calendrier prĂ©visible des sorties (toujours en octobre) pour caler les rĂ©unions dĂ©veloppeurs, conventions, intĂ©grations dans les distributions (surtout Fedora, apparemment). Toutefois, le dĂ©veloppement se fait sur **dixâsept mois** : - les **cinq premiers mois** se font sans numĂ©ro de version et chevauchent les versions _bĂȘta_ et _rc_ de la version `n - 1` ; - les **sept mois** suivants sont consacrĂ©s aux versions _alpha_ (ajout de fonctionnalitĂ©s toujours possible) ; - les **trois mois** suivants sont consacrĂ©s aux versions _bĂȘta_ (plus dâajout de fonctionnalitĂ© possible) ; - les **deux derniers mois** sont pour les versions candidates. Les versions majeures (3._x_) sont maintenues pendant **cinq ans** : - corrections et sorties complĂštes pendant **un an et demi**, frĂ©quence de sortie mensuelle ; - corrections de sĂ©curitĂ© uniquement et sorties sous forme de source uniquement pendant **trois ans et demi**. # PEP 573 â AmĂ©lioration de performance pour les extensions C questionnant lâĂ©tat de leur module [PEP 573](https://www.python.org/dev/peps/pep-0573/) : les modules Python et les extensions Python ne sont pas gĂ©rĂ©s de la mĂȘme façon Ă ce jour. Les extensions : - nâont pas de moyen de libĂ©rer la mĂ©moire au dĂ©chargement de lâextension ; - ne peuvent pas savoir si elles ont dĂ©jĂ Ă©tĂ© chargĂ©es ; - nâont pas conscience de leur module et ne peuvent donc pas ĂȘtre chargĂ©es plusieurs fois. Il y avait quelques astuces pour pouvoir avoir un Ă©tat du module (en utilisant des variables globales par exemple), mais lâamĂ©lioration apportĂ©e dans Python 3.9 permet aux dĂ©veloppeurs et dĂ©veloppeuses de modules qui le souhaitent dâavoir directement les informations nĂ©cessaires (instance, classe, module...). Ăa devrait permettre de corriger les [PEP 3 121](https://www.python.org/dev/peps/pep-3121/) et [PEP 489](https://www.python.org/dev/peps/pep-0489/) et dâavoir des modules : - qui prennent moins de mĂ©moire ; - qui libĂšrent correctement leurs ressources au dĂ©chargement (mĂ©moire, fichier ouvert...) ; - qui accĂšdent plus facilement aux informations de contexte ; - qui sont moins difficiles Ă lire et maintenir ; - qui peuvent ĂȘtre instanciĂ©s plusieurs fois sans souci ou au contraire se comporter comme un [singleton](https://fr.wikipedia.org/wiki/Singleton_(patron_de_conception)). Pour bĂ©nĂ©ficier de ces avantages, les modules dâextension C doivent faire de menues modifications dans leur code source. Cela parait suffisamment petit/simple pour que ce soit gĂ©rĂ© par une macro qui gĂ©nĂšre du code compatible Python 3.8 â et Python 3.9. Les modules intĂ©grĂ©s â *builtâin* â ont dĂ©jĂ Ă©tĂ© modifiĂ©s en consĂ©quence. Dâautres optimisations viendront sâajouter en Python 3.10 pour les modules dâextension. # PEP 584 â OpĂ©rateurs dâunion pour les dictionnaires [PEP 584](https://www.python.org/dev/peps/pep-0584/) : « Comment fusionner deux dictionnaires en Python en une instruction ? » Câest une des [questions](https://stackoverflow.com/questions/38987/how-do-i-merge-two-dictionaries-in-a-single-expression-in-python-taking-union-o) les plus vieilles et plus lues sur _StackOverflow_ Ă propos de Python : vieille de plus de **douze ans**, votĂ©e Ă **plus de 5 000**, dans les favoris **plus de 1 000 fois**, contient **47 rĂ©ponses** et aucune nâest satisfaisante. Il nây a pas non plus une façon plus Ă©vidente qui se dĂ©tacherait des autres. En Python, on prĂ©fĂšre quâil nây ait quâune façon Ă©vidente de faire les choses si possible. Câest tellement courant comme besoin que les dĂ©veloppeurs Python ont fini par trouver une solution et, en plus, elle est Ă©lĂ©gante. Pour faire lâunion (ou la fusion) de deux dictionnaires, il a Ă©tĂ© dĂ©cidĂ© dâutiliser lâopĂ©rateur `|`, dĂ©jĂ utilisĂ© dans les opĂ©rations sur les bits ou sur les `set`. Pour fusionner `d1` avec `d2` et obtenir un nouveau dictionnaire, on peut donc faire : ```python d3 = d1 | d2 ``` Attention toutefois, lâopĂ©rateur nâest **pas commutatif**, câestâĂ âdire que `d1|d2` nâest pas forcĂ©ment Ă©quivalent Ă `d2|d1`. Les clĂ©s et valeurs de droite Ă©crasant celles de gauche. # PEP 585 â `Generics` dans les collections pour les `typehint` sans importation depuis `typing` [PEP 585](https://www.python.org/dev/peps/pep-0585/) : Python peut gĂ©rer du typage Ă lâanalyse de syntaxe (via des outils supplĂ©mentaires) mais aussi Ă lâexĂ©cution. La situation actuelle repose sur une succession de PEP ([484](https://www.python.org/dev/peps/pep-0484/), [526](https://www.python.org/dev/peps/pep-0526/), [544](https://www.python.org/dev/peps/pep-0544/), [560](https://www.python.org/dev/peps/pep-0560/) et [563](https://www.python.org/dev/peps/pep-0563/)) et a abouti Ă lâexistence dâune hiĂ©rarchie de types gĂ©nĂ©riques dupliquĂ©s, par exemple `typing.List` et le type interne `list`. Exemple fictif : ```python from typing import List def first_int_elem(l: List) -> int: return int(l[0]) if l else None s = ("1", "2", "3") print(f"{first_int_elem(list(s))=}") ``` On devait utiliser Ă la fois la `list` interne et la `List` de `typing`. Dans ce cas prĂ©cis, on pouvait toutefois utiliser directement `list` car on ne prĂ©cise pas une liste de quoi. Prenons alors un exemple plus prĂ©cis : ```python from typing import List def first_int_elem(l: List[int]) -> int: return l[0] if l else None s = (1, 2, 3) print(f"{first_int_elem(list(s))=}") ``` Ici, et jusquâĂ prĂ©cĂ©demment, il Ă©tait impossible de substituer le type `List` de `typing` par la `list` interne, la syntaxe `list[int]` nâĂ©tant pas acceptĂ©e (`TypeError: 'type' object is not subscriptable`). Ceci est maintenant possible en Python 3.9 pour les types internes suivants : - `tuple` ; - `list` ; - `dict` ; - `set` ; - `frozenset` ; - `type` ; - tout ce qui se trouve dans `collections` ; - `contextlib.AbstractContextManager` ; - `contextlib.AbstractAsyncContextManager` ; - `re.Pattern` ; - `re.Match`. ```Python def first_int_elem(l: list[int]) -> int: return l[0] if l else None s = (1, 2, 3) print(f"{first_int_elem(list(s))=}") ``` Un type peut accepter des paramĂštres gĂ©nĂ©riques sâil implĂ©mente la mĂ©thode `__class_getitem__`. Ces paramĂštres gĂ©nĂ©riques sont conservĂ©s par lâenvironnement dâexĂ©cution dans un attribut `__args__`. Lâinstanciation dâun type gĂ©nĂ©rique ne conserve pas les paramĂštres gĂ©nĂ©riques, ainsi `list()` et `list[int]()` dĂ©signe le mĂȘme type de liste et peuvent, Ă lâexĂ©cution, contenir des objets de tout type. Lâimportation des types Ă©quivalents depuis `typing` est dĂ©prĂ©ciĂ©e mais ne gĂ©nĂšre pas dâavertissement. # PEP 593 â PossibilitĂ© dâannoter un « typehint » avec une expression quelconque [PEP 593](https://www.python.org/dev/peps/pep-0593/) : avec `Annotated` du module `typing`, il est maintenant possible dâannoter une expression quelconque sur un type. Cette annotation peut ĂȘtre lue par un analyseur de type ou par lâenvironnement dâexĂ©cution. Cela permet par exemple Ă un cadriciel de dĂ©finir des informations supplĂ©mentaires sur des types primitifs (`int`, `str`...). Ă lâexĂ©cution `get_type_hints` a Ă©tĂ© enrichi pour pouvoir lire ces annotations avec `include_extras` : ```python @struct2.packed class Student(NamedTuple): name: Annotated[str, struct.ctype("<10s")] get_type_hints(Student) == {'name': str} get_type_hints(Student, include_extras=False) == {'name': str} get_type_hints(Student, include_extras=True) == { 'name': Annotated[str, struct.ctype("<10s")] } ``` `Annotated` peut prendre plusieurs annotations (lâordre est conservĂ©). Toutefois, si une annotation est de type `Annotated`, la liste rĂ©sultante sera aplatie : ```python Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[ int, ValueRange(3, 10), ctype("char") ] ``` On peut utiliser les annotations avec des paramĂštres gĂ©nĂ©riques : ```python Typevar T = ... Vec = Annotated[List[Tuple[T, T]], MaxLen(10)] V = Vec[int] V == Annotated[List[Tuple[int, int]], MaxLen(10)] ``` Ces annotations peuvent permettre de simplifier du code et voir Ă©merger de nouveaux cadriciels. # PEP 614 â Les dĂ©corateurs peuvent ĂȘtre des expressions complĂštes [PEP 614](https://www.python.org/dev/peps/pep-0614/) est une modification mineure, qui ne concernera probablement que ceux crĂ©ant ou utilisant certains cadriciels, mais ça peut permettre de largement simplifier du code. Quand on applique un dĂ©corateur en Python, la syntaxe autorisĂ©e Ă©tait :> `@` + `variable` (+ `.` + `attribut`)* + (`(` + parameters + `)`)? Ăa paraĂźt suffisant, mais en fait câĂ©tait hyper restrictif. Prenons lâexemple dâun cadriciel fictif qui permet de brancher des fonctions Ă des clics sur des boutons dâune interface graphique : ```python buttons = [QPushButton(f'Button {i}') for i in range(10)] # Do stuff with the list of buttons... button_0 = buttons[0] @button_0.clicked.connect def spam(): ... button_1 = buttons[1] @button_1.clicked.connect def eggs(): ... ``` On est obligĂ© dâutiliser une variable temporaire car la syntaxe est restrictive pour les dĂ©corateurs. Ă partir de cette version 3.9, on peut utiliser des expressions complĂštes pour dĂ©corer une fonction : ```python buttons = [QPushButton(f'Button {i}') for i in range(10)] # Do stuff with the list of buttons... @buttons[0].clicked.connect def spam(): ... @buttons[1].clicked.connect def eggs(): ... ``` Bref, une bonne chose qui peut simplifier le code. # PEP 615 â IntĂ©gration des fuseaux horaires de lâIANA dans Python [PEP 615](https://www.python.org/dev/peps/pep-0615/) : un nouveau module intĂ©grĂ© a Ă©tĂ© ajoutĂ© en Python 3.9 : `zoneinfo`. Le module se base sur les fuseaux horaires dĂ©finis dans le systĂšme, se reposant sur [les donnĂ©es](https://en.wikipedia.org/wiki/Tz_database) de lâ[IANA](https://fr.wikipedia.org/wiki/Internet_Assigned_Numbers_Authority "Internet Assigned Numbers Authority"). La plupart des systĂšmes ont ces informations et sont donc mis Ă jour rĂ©guliĂšrement. Pour dâautres (Windows), cette base de donnĂ©es nâest pas disponible. Dans ce cas, le paquet Python `tzdata` contient une base de donnĂ©es Ă jour et est utilisĂ© si installĂ©. Cela permet, sauf si jâai mal compris, de se passer du paquet `pytz` et de pouvoir jouer avec les dates et leur fuseau horaire plus simplement quâavant. Une variable dâenvironnement (`PYTHONTZPATH`) ainsi quâune fonction (`zoneinfo.reset_tzpath`) permettent de changer les chemins de recherche des donnĂ©es de lâIANA. Le module contient surtout une classe `ZoneInfo` qui permet : - de construire un fuseau horaire basĂ© sur son nom, par exemple `Europe/Paris` â les fuseaux horaires construits sont mis en cache ; - dâĂȘtre utilisĂ©e dans les arguments `tzinfo` des classes et fonctions du module `datetime`. ```python zone = ZoneInfo("Europe/Paris") dt = datetime(2020, 12, 3, 16, 15, tzinfo=zone) ``` # PEP 616 â `removeprefix` et `removesuffix` dans `str` [PEP 616](https://www.python.org/dev/peps/pep-0616/) : souvent, les dĂ©veloppeurs voulant supprimer un prĂ©fixe ou un suffixe dâune chaĂźne de caractĂšres utilisaient [`str.lstrip`](https://docs.python.org/3/library/stdtypes.html#str.lstrip) et [`str.rstrip`](https://docs.python.org/3/library/stdtypes.html#str.rstrip), et se retrouvaient surpris que le paramĂštre passĂ© soit interprĂ©tĂ© comme un ensemble de caractĂšres. ```python>>> "test_terrible_name".lstrip("test_") # enlĂšve tous les caractĂšres 't', 'e', 's' et '_' en dĂ©but de chaĂźne 'rrible_name' ``` De fait, cela rĂ©sultait soit en du code lourd soit Ă une implĂ©mentation souffrant souvent de bogues subtils autour de la gestion de chaĂźnes vides. Il Ă©tait donc nĂ©cessaire que Python rĂ©ponde Ă ce besoin courant avec une solution fiable. DĂ©sormais, [`str.removeprefix`](https://docs.python.org/3/library/stdtypes.html#str.removeprefix) et [`str.removesuffix`](https://docs.python.org/3/library/stdtypes.html#str.removesuffix) remplissent ce vide et permettent dâĂ©crire du code plus Ă©lĂ©gant. Par exemple, dans le code de Python luiâmĂȘme (`find_recursionlimit.py`) : ```python if test_func_name.startswith("test_"): print(test_func_name[5:]) else: print(test_func_name) ``` Ce code devient : ```python print(test_func_name.removeprefix("test_")) ``` Ou encore le code suivant (`cookiejar.py`) : ```python def strip_quotes(text): if text.startswith('"'): text = text[1:] if text.endswith('"'): text = text[:-1] return text ``` qui devient : ```python def strip_quotes(text): return text.removeprefix('"').removesuffix('"') ``` # PEP 617 â Nouvel analyseur PEG pour CPython [PEP 617](https://www.python.org/dev/peps/pep-0617/) : la grammaire de Python Ă©tait basĂ©e sur une grammaire [LL(1)](https://fr.wikipedia.org/wiki/Analyse_LL#Cas_g%C3%A9n%C3%A9ral_pour_une_analyse_LL(1)). Cependant, certaines fonctionnalitĂ©s de Python nâĂ©taient pas exprimables selon ce modĂšle, et nĂ©cessitaient des bidouillages qui compliquaient la maintenance. Les deux exemples bloquant Python sont les rĂšgles oĂč lâambiguĂŻtĂ© ne peut ĂȘtre rĂ©solue quâen regardant les symboles suivants, ou la rĂ©cursion par la gauche. [Un des problĂšmes](https://bugs.python.org/issue12782) qui sâest posĂ© (Ă partir de 2011 !) Ă©tait, par exemple, avec le code suivant : ```python with ( open("a_really_long_foo") as foo, open("a_really_long_bar") as bar, open("a_really_long_baz") as baz ): ``` Ce dernier Ă©tait considĂ©rĂ© comme invalide, contrairement au reste des constructions Python (il fallait utiliser `\` pour la continuation de ligne). En passant Ă une grammaire [PEG^([en])](https://en.wikipedia.org/wiki/Parsing_expression_grammar), les rĂšgles sont plus proches de la façon dont elles vont ĂȘtre analysĂ©es. Lors dâambiguĂŻtĂ©s, chaque possibilitĂ© est vĂ©rifiĂ©e et si elle Ă©choue, teste la variante possible suivante. Les grammaires PEG ne gĂšrent habituellement pas la rĂ©cursion par la gauche, mais cela a pu ĂȘtre implĂ©mentĂ© sans difficultĂ©. Le nouvel analyseur est lĂ©gĂšrement plus rapide et utilise un peu moins de mĂ©moire que lâancien. Il permet de rendre le code plus maintenable, ainsi que de rĂ©soudre des problĂšmes (tel que celui avec le `with` exposĂ© ciâdessus) qui nâavaient pu ĂȘtre rĂ©solus pendant des annĂ©es Ă cause de la complexitĂ© induite. Les deux analyseurs continuent de coexister et aucune rĂšgle de grammaire ne requiert le nouvel analyseur (utilisĂ© par dĂ©faut). On peut revenir Ă lâancien analyseur Ă tout moment avec [`python -X oldparser`](https://docs.python.org/3/using/cmdline.html#id5) ou en utilisant une variable dâenvironnement [`PYTHONOLDPARSER=1`](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONOLDPARSER). Si rien ne lâempĂȘche, lâancien analyseur sera retirĂ© dans Python 3.10. Les modules dĂ©prĂ©ciĂ©s de Python 3 qui Ă©taient maintenus pour que des personnes Ă©crivent plus facilement des bibliothĂšques et des programmes compatibles Python 2 et 3 sont supprimĂ©s de cette version.