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... ![Logo Python](https://www.python.org/static/img/python-logo.png) ---- [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.

AltStyle ă«ă‚ˆăŁăŠć€‰æ›ă•ă‚ŒăŸăƒšăƒŒă‚ž (->ă‚ȘăƒȘă‚žăƒŠăƒ«) /