URL: https://linuxfr.org/news/python-3-10-est-disponible Title: Python 3.10 est disponible Authors: Cyrille Pontvieux Snark, Anonyme, Ysabeau đ§¶, ariasuni, Yves Bourguignon, BenoĂźt Sibaud, bobble bubble, dourouc05, pamputt et palm123 Date: 2021ćčŽ04æ30æ„T13:19:22+02:00 License: CC By-SA Tags: python et sortie_version Score: 50 Python 3.10 (_rc1_) est sorti le **2 aoĂ»t 2021**, aprĂšs quinze mois de dĂ©veloppement (dont cinq Ă cheval sur les *bĂȘta* et les *rc* de Python 3.9). Il reste deux mois avec des candidats (*RC*) avant la sortie dĂ©finitive, prĂ©vue le **4 octobre 2021**. Voyons ce que cette version apporte comme nouveautĂ©s...  ---- [Notes de version 3.10.0rc1](https://www.python.org/downloads/release/python-3100rc1/) [Notes de version 3.10.0](https://www.python.org/downloads/release/python-3100/) ---- Cette version comporte pas moins de **onze nouveautĂ©s** et **une correction** par rapport Ă la version 3.9 sortie le 5 octobre 2020. Une correction (changement de dĂ©faut) nâa finalement pas Ă©tĂ© incluse dans cette version. # PEP 623 â PrĂ©paration de la suppression de `wstr` dans `PyUnicodeObject` [PEP 623](https://www.python.org/dev/peps/pep-0623/) : il nây a plus aucune raison de garder la compatibilitĂ© avec les chaĂźnes Ă larges caractĂšres (*wide string*) dans lâimplĂ©mentation Unicode de Python. En effet, `wstr` et `wstr_length` avaient Ă©tĂ© introduits pour une meilleure compatibilitĂ© des extensions en `C` pour Python 2 et 3. Vu que Python 2 est maintenant dĂ©clarĂ© mort, il nây a plus besoin de garder ces API qui seront marquĂ©es comme dĂ©prĂ©ciĂ©es Ă partir de Python 3.10 et supprimĂ©es en version 3.12. Le gain en mĂ©moire est de 8 octets par chaĂźne de caractĂšres, ce qui nâest pas rien. # PEP 604 â Ăcrire les unions de types plus facilement [PEP 604](https://www.python.org/dev/peps/pep-0604/) : avant, pour Ă©crire lâunion de type en Python, il fallait Ă©crire `Union[X, Y]`. Il fallait donc importer `Union` de `typing` pour pouvoir faire une union, et la lecture nâĂ©tait pas limpide. Cette proposition permet dâĂ©crire `X | Y`, ce qui apporte Ă la fois les avantages dâune lecture simplifiĂ©e, et dâun import de moins. Cela parait simple au niveau grammaire, mais pour que ça fonctionne au niveau du code, il a fallu ajouter la mĂ©thode magique `__or__` Ă la super-classe/lâobjet `type`. Du coup on gagne aussi dans dâautres domaines, comme les Ă©critures suivantes : ```python isinstance(5, int | str) isinstance(None, int | None) ``` # PEP 612 â AmĂ©lioration du typage pour les dĂ©corateurs [PEP 612](https://www.python.org/dev/peps/pep-0612/) : la [PEP 484](https://www.python.org/dev/peps/pep-0484/) et la [PEP 544](https://www.python.org/dev/peps/pep-0544/) permettent de dĂ©crire des fonctions de retour dâappel (*callback*). Sur un dĂ©corateur, en dĂ©finissant un `TypeVar`, on peut dĂ©finir une valeur de retour pour le dĂ©corateur qui correspond au type de la fonction dĂ©corĂ©e. Il nâĂ©tait toutefois pas possible de faire la mĂȘme chose avec les arguments. La PEP 612 comble ce manque en introduisant `ParamSpec` qui englobe la dĂ©finition dâarguments. `ParamSpec` contient notamment les attributs `args` et `kwargs` qui correspondent respectivement aux types des arguments positionnels et nommĂ©s. Exemple : ```python from typing import Awaitable, Callable, ParamSpec, TypeVar P = ParamSpec("P") R = TypeVar("R") def add_logging(f: Callable[P, R]) -> Callable[P, Awaitable[R]]: async def inner(*args: P.args, **kwargs: P.kwargs) -> R: await log_to_database() return f(*args, **kwargs) return inner @add_logging def takes_int_str(x: int, y: str) -> int: return x + 7 await takes_int_str(1, "A") # acceptĂ© await takes_int_str("B", 2) # rejetĂ© par le vĂ©rifieur de type ``` # PEP 626 â NumĂ©ro de ligne prĂ©cis pour les dĂ©bugueurs et autres outils [PEP 626](https://www.python.org/dev/peps/pep-0626/) : les attributs `f_lineno` et `co_lnotab` dâune pile (*frame*) dâexĂ©cution ne donnent pas toujours le bon numĂ©ro de ligne du code source dans certains cas. La correction consiste Ă marquer des *bytecodes* comme virtuels et nâayant pas de ligne dans le code source. Afin de garantir la compatibilitĂ© avec les outils existants, rien nâest modifiĂ© sur `co_lnotab` mais une nouvelle mĂ©thode `co_lines()` est disponible. `co_lnotab` sera donc toujours disponible mais gĂ©nĂ©rĂ© Ă la demande. # PEP 618 â Ajout dâune vĂ©rification optionnelle des longueurs dans `zip` [PEP 618](https://www.python.org/dev/peps/pep-0618/) : sĂ©curisation optionnelle de lâutilisation de la fonction `zip`. La fonction `zip` permet dâitĂ©rer sur plusieurs sĂ©quences ou gĂ©nĂ©rateurs Ă la fois et de retourner un tuple de valeurs des diffĂ©rents itĂ©rateurs. Si un itĂ©rateur nâa plus dâĂ©lĂ©ment Ă itĂ©rer, `zip` sâarrĂȘte sans erreur, mĂȘme si un autre itĂ©rateur a encore des Ă©lĂ©ments Ă itĂ©rer. Cette PEP rajoute une option pour dĂ©clencher une erreur dans une telle situation. Exemple : ```python>>> list(zip(range(3), range(2, 5))) [(0, 2), (1, 3), (2, 4)]>>> list(zip(range(3), range(2, 4))) [(0, 2), (1, 3)]>>> list(zip(range(3), range(2, 4), strict=True)) ValueError: zip() argument 2 is shorter than argument 1 ``` # Bug 12782 â Groupement des gestionnaires de contexte avec des parenthĂšses [BPO 12782](https://bugs.python.org/issue12782) : grouper des gestionnaires de contexte avec des parenthĂšses est dĂ©sormais officiellement autorisĂ©. Contrairement aux autres mots-clĂ©s, il nâĂ©tait pas possible dâutiliser les parenthĂšses avec `with`: ```python with (open("a_really_long_foo") as foo, open("a_really_long_bar") as bar): pass ``` ``` Traceback (most recent call last): File "", line 1, in File "demo.py", line 19 with (open("a_really_long_foo") as foo, ^ SyntaxError: invalid syntax ``` Il fallait sâen remettre aux `\` pour sauter des lignes: ```python with open("a_really_long_foo") as foo, \ open("a_really_long_bar") as bar: pass ``` DĂ©sormais, la premiĂšre syntaxe sera valide, comme on peut lĂ©gitimement sây attendre, renforçant la cohĂ©rence du langage. Cette fonctionnalitĂ© a pu ĂȘtre introduite facilement grĂące au [nouveau PEG parser ajoutĂ© dans Python 3.9](https://www.python.org/dev/peps/pep-0617/). # PEP 632 â DĂ©prĂ©ciation du module `distutils` [PEP 632](https://www.python.org/dev/peps/pep-0632/) : le module `distutils` est dĂ©prĂ©ciĂ© car une version est maintenant [intĂ©grĂ©e dans `setuptools`](https://github.com/pypa/setuptools/issues/417) qui ne dĂ©pend plus de la librairie standard de Python. `distutils` nâĂ©tait pas trĂšs documentĂ© ni maintenu dans la librairie standard de toutes façons. Ăa laisse Ă©galement le champ libre pour la [PEP 517](https://www.python.org/dev/peps/pep-0517/) qui veut construire un systĂšme de construction indĂ©pendant. # PEP 613 â Alias de types explicites [PEP 613](https://www.python.org/dev/peps/pep-0613/) : ajout dâun type « Alias de type » pour permettre de dĂ©finir les alias de type de maniĂšre explicite. JusquâĂ prĂ©sent la diffĂ©rence entre une affectation dâune valeur Ă une variable et dâune dĂ©finition dâalias de type Ă©tait assez tĂ©nue et ambiguĂ«. Exemple dâambiguĂŻtĂ© : ```python MonType = "ClassName" def foo() -> MonType: pass ``` Ici, il est difficile de savoir si `MonType` est une variable dont la valeur est une string `ClassName` ou si câest un alias de type sur la classe `ClassName` qui sera dĂ©finie plus tard. Les vĂ©rifieurs de types se trompant souvent et rapportant de fausses erreurs, il a fallu se rendre Ă lâĂ©vidence : cette notation nâest pas assez explicite. Cette PEP, propose : ```python MonType: TypeAlias = "ClassName" def foo() -> MonType: pass ``` LĂ , plus dâambiguĂŻtĂ©. # PEP 634/635/636 â Ajout du mot-clĂ© `match` pour faire du filtrage par motif. [PEP 634](https://www.python.org/dev/peps/pep-0634/), [PEP 635](https://www.python.org/dev/peps/pep-0635/) et [PEP 636](https://www.python.org/dev/peps/pep-0636/): [filtrage par motif](https://fr.wikipedia.org/wiki/Filtrage_par_motif) grĂące au mot-clĂ© `match`. Cette PEP a Ă©tĂ© divisĂ©e en trois parties : [dĂ©finition](https://www.python.org/dev/peps/pep-0634/), [justification](https://www.python.org/dev/peps/pep-0635/), [tutoriel](https://www.python.org/dev/peps/pep-0636/). La lecture du tutoriel est un bon point de dĂ©part pour lire ces PEP. LâamĂ©lioration vient du constat suivant : il est souvent nĂ©cessaire de faire une suite de `if ... elif ... elif ... else` qui concerne un mĂȘme Ă©lĂ©ment. Ces tests peuvent porter sur du *duck-typing* (est-ce que cet objet couac comme un canard), sur de lâhĂ©ritage, sur une liste de valeurs (comparaison sur des *strings* ou des *numĂ©riques* par exemple), sur une taille de sĂ©quence (*liste*, *tuple*) et dâautres tests. Cela fonctionne trĂšs bien avec des `if` mais câest pas toujours super lisible parfois, et comme ce schĂ©ma se rĂ©pĂšte souvent dans les projets Python, il a Ă©tĂ© dĂ©cidĂ© dâaider les dĂ©veloppeurs (ceux qui Ă©crivent mais surtout ceux qui lisent) avec lâaide des mots-clĂ©s `match` et `case` (et copier ce quâon peut trouver sur dâautres langages) Les mots-clĂ©s `match` et `case` ne sont pas des termes rĂ©servĂ©s, on peut donc toujours les utiliser dans des noms de variables, le parseur faisant la diffĂ©rence de construction. Le code prĂ©-python-3.10 reste donc compatible. Python parle de *soft keywords*. Le concept de **filtrage par motifs** sâexplique par le fait que la variable (ou lâexpression) Ă tester est comparĂ©e Ă des motifs et permet de sĂ©lectionner exclusivement un bloc de code. Exemple de code gĂ©rant un jeu de rĂŽle par entrĂ©e textuelle : ```python command = input("Que voulez-vous faire ? ") match command.split(): case ["quitter"]: print("Au revoir !") quit_game() case ["regarder"] | ["scruter"]: current_room.describe() case ["aller", "cĂŽtĂ©", ("Nord" | "Sud" | "Est" | "Ouest") as direction] | [("Nord" | "Sud" | "Est" | "Ouest") as direction] if direction in current_room.exits: current_room = current_room.neighbor(direction) case [("Nord" | "Sud" | "Est" | "Ouest")] | [_]: print("DĂ©solĂ©, il nây a pas de sortie dans cette direction") case ["prendre", obj] | ["ramasser", obj] | ["mettre", obj, "dans", "le", "sac"]: character.get(obj, current_room) case ["lĂącher", *objects]: for obj in objects: character.drop(obj, current_room) case _: print(f"DĂ©solĂ©, je ne comprends pas ce que veut dire {command!r}") ``` On peut comprendre le code en un clin dâĆil, ce qui ne serait pas forcĂ©ment le cas avec des `if`. De plus on peut voir que la syntaxe de **dĂ©construction** permet de valider le format attendu, mais Ă©galement de rĂ©cupĂ©rer des **variables liĂ©es** (*bind* en anglais). Si des alternatives sont prĂ©sentes dans le motif, alors chaque alternative doit lier les mĂȘmes variables. Lâutilisation de lâexpression `if condition` aprĂšs le motif permet de **garder** le bloc sous une condition spĂ©ciale, supplĂ©mentaire au motif, et Ă©valuĂ©e aprĂšs la correspondance au motif et aprĂšs les *bind*. Enfin lâutilisation de la variable `_` permet de dĂ©finir des cas par dĂ©faut. En effet il est de convention en Python dâutiliser `_` pour dĂ©finir une variable que lâon ne va pas utiliser et qui sert de variable fictive (*placeholder*). On peut Ă©galement utiliser la notation avec une ou deux Ă©toiles pour rĂ©cupĂ©rer une liste dâĂ©lĂ©ments ou un dictionnaire. Notations `*lst` ou `**d` comme on peut le faire dans la syntaxe de dĂ©construction classique. Les comparaisons de motifs peuvent se faire en profondeur. LâĂ©galitĂ© (`==`) est utilisĂ©e pour comparer les objets et littĂ©raux, sauf pour `True`, `False` et `None` ou câest lâidentitĂ© (`is`). Il est Ă©galement possible de comparer avec des classes en utilisant cette syntaxe : ```python match event.get(): case Click(position=(x, y)): handle_click_at(x, y) case KeyPress(key_name="Q") | Quit(): game.quit() case KeyPress(key_name="up arrow"): game.go_north() ... case KeyPress(): pass # Ignore other keystrokes case other_event: raise ValueError(f"Unrecognized event: {other_event}") ``` Ici on va vĂ©rifier le type de retour de `event.get()` avec les types dâobjets indiquĂ©s dans les diffĂ©rents `case`. Attention, mĂȘme si la syntaxe donne lâimpression que des objets sont créés (`Click(position=(x, y))`, `Quit()`) il nâen est rien. Ce qui est donnĂ© entre parenthĂšses restreint le motif Ă une classe qui hĂ©rite de la classe indiquĂ©e (exemple `Click`) et qui contient un attribut `position` dont le motif correspond Ă un tuple de deux Ă©lĂ©ments qui seront liĂ©s aux variables `x` et `y`. On peut bien Ă©videmment mixer tous ces types de motifs. LâexpressivitĂ© créée par cette PEP est Ă©norme et des tests menĂ©s par Python montre une meilleure lisibilitĂ© sur une grosse partie du code source des modules Python et une rĂ©duction du nombre de lignes. Bref, aprĂšs des annĂ©es de demande pour avoir un `switch/case` en Python, en voilĂ un sous stĂ©roĂŻdes ! # PEP 644 â OpenSSL 1.1.1 ou plus rĂ©cent est maintenant requis [PEP 644](https://www.python.org/dev/peps/pep-0644/) : `OpenSSL` (version `1.1.1` ou supĂ©rieure) sera nĂ©cessaire pour Python 3.10. Il existe trop de diffĂ©rences entre les versions dâ`openssl` (`1.0.2`, `1.1.0` et `1.1.1`) pour que les *deux experts* qui gĂšrent ça chez Python puissent fournir un code compatible avec toutes. Câest la mĂȘme raison pour `libressl` qui ne sera plus pris en charge Ă partir de Python 3.10. Pour rĂ©sumer, seule lâAPI dâ`openssl 1.1.1` est prise en charge. Ăa ne devrait pas trop impacter les distributions selon la PEP 644 car `openssl 1.1.1` est la version par dĂ©faut sur la plupart des plateformes et distributions (Linux et BSD). Les rares distributions qui utilisent `LibreSSL` devraient probablement avoir migrĂ© sur `OpenSSL` dâici Ă ce que Python 3.10 sorte. # PEP 624 â Retrait des API dâencodage `Py_UNICODE` [PEP 624](https://www.python.org/dev/peps/pep-0624/) : planification du retrait des API inutilisĂ©e `PyUnicode_*` pour *Python 3.11*. Cette PEP sort donc pour Python 3.10 mais nâaura dâeffet que pour la version suivante. # PEP 597 â Ajout dâun `EncodingWarning` optionnel [PEP 597](https://www.python.org/dev/peps/pep-0597/) : ajout dâun `EncodingWarning` optionnel lors de lâouverture dâun fichier en mode texte. En effet sous Linux, BSD et MacOS, lâencodage par dĂ©faut des fichiers est trĂšs gĂ©nĂ©ralement `UTF-8`. Mais ce nâest pas toujours le cas. Quand on ouvre un fichier en mode texte, câest lâencodage par dĂ©faut du systĂšme qui est utilisĂ© (qui peut ĂȘtre surchargĂ© avec la variable dâenvironnement `LANG` par exemple). On peut prĂ©ciser/forcer lâencodage Ă utiliser pour les caractĂšres/octets en le spĂ©cifiant dans un paramĂštre `encoding`. Celui-ci existe depuis fort longtemps, mais force est de constater que la plupart des bibliothĂšques et codes en python ne le prĂ©cisent pas. Cette PEP vise Ă ajouter un avertissement `EncodingWarning` quand le paramĂštre `encoding` nâa pas Ă©tĂ© prĂ©cisĂ© pour un fichier ouvert en mode texte. Afin dâactiver cet avertissement, il faut invoquer Python avec les paramĂštres `-X warn_default_encoding` ou bien dĂ©finir la variable dâenvironnement `PYTHONWARNDEFAULTENCODING` Ă `1`. Illustration de code correct (ne gĂ©nĂ©rant pas le warning) : ```python with open(`my_file.txt`, encoding='utf-8') as f: content = f.read() ``` -- # Bug 38605 â Ăvaluation retardĂ©e des annotations (non-inclus dans Python 3.10) [BPO 38605](https://bugs.python.org/issue38605) : lâĂ©valuation retardĂ©e des annotations, qui est disponible depuis Python 3.7 en utilisant une ligne `from __future__ import annotations` est maintenant disponible par dĂ©faut. Pour rappel, la [PEP 563](https://www.python.org/dev/peps/pep-0563/) permet dâĂ©valuer les annotations au lancement et pas simplement lors de la dĂ©finition/compilation. Les annotations sont prĂ©sentes dans `__annotations__`. On peut les lire avec `get_type_hints` de `typing`. Les annotations sont toujours prĂ©sentĂ©es comme du code python valide. On peut par exemple les lire Ă lâexĂ©cution comme suit : ```python>>> import typing>>> def x(a: 'int'): pass>>> typing.get_type_hints(x) {'a': ForwardRef('int')} ``` ** /!\ Cette modification a Ă©tĂ© repoussĂ©e Ă Python 3.11 en raison de certaines incompatibilitĂ©s ** [Plus dâinfo](https://mail.python.org/archives/list/python-dev@python.org/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/) # Pour tester Pas envie dâattendre que ce soit disponible dans votre distribution ? Deux options : utiliser un conteneur ou compiler depuis les sources. ## Utilisation dâun conteneur ```sh $ docker run --rm -it python:3.10.0rc1-slim ``` ## Compilation depuis les sources En fait câest pas si compliquĂ© du tout et ça se compile assez vite, donc câest une option viable, mĂȘme pour tester. DĂ©pendances nĂ©cessaires : `expat`, `bzip2`, `gdbm`, `openssl`, `libffi`, `zlib`, `tk`, `sqlite`, `bluez-libs`, `mpdecimal` Sous Debian/Ubuntu câest : `expat libbz2-dev liblzma-dev libgdbm-dev libdb5.3-dev libdb5.3++-dev libssl-dev libffi-dev zlib1g-dev tk-dev libsqlite3-dev libbluetooth-dev libncurses-dev libreadline-dev` Ensuite il suffit de rĂ©cupĂ©rer le tarball et de lancer la compilation et lâinstallation (câest tirĂ© du paquet AUR sur ArchLinux) : ```sh $ wget 'https://www.python.org/ftp/python/3.10.0/Python-3.10.0rc1.tar.xz' $ tar xf Python-3.10.0rc1.tar.xz $ cd Python-3.10.0rc1 $ rm -rf Modules/{expat,zlib} Modules/_ctypes/{darwin,libffi}* $ # Vous pouvez vous passer de enable-optimizations pour compiler plus vite si vous voulez $ ./configure --prefix=/usr \ --enable-shared \ --with-computed-gotos \ --enable-ipv6 \ --with-system-expat \ --enable-loadable-sqlite-extensions \ --without-ensurepip \ --enable-optimizations $ make $ mkdir -p pkg $ sudo make DESTDIR="$PWD/pkg" altinstall maninstall $ sudo rm -f "$PWD/pkg/usr/lib/libpython3.so" "$PWD/pkg/usr/share/man/man1/python3.1" $ sudo ln -sf ../../libpython3.10m.so \ "$PWD/pkg/usr/lib/python3.10/config-3.10-$(uname -m)-linux-gnu/libpython3.10m.so" $ sudo ln -sf python3.10m-config "$PWD/pkg/usr/bin/python3.10-config" $ sudo install -dm755 "$PWD/pkg/usr/lib/python3.10/Tools"/{i18n,scripts} $ sudo install -m755 Tools/i18n/{msgfmt,pygettext}.py "$PWD/pkg/usr/lib/python3.10/Tools/i18n/" $ sudo install -m755 Tools/scripts/{README,*py} "$PWD/pkg/usr/lib/python3.10/Tools/scripts/" $ sudo cp -a pkg/* / $ python3.10 ``` # CompatibilitĂ© Python 3.10 est grandement compatible avec les versions prĂ©cĂ©dentes. Ăa devrait donc ĂȘtre trĂšs facile de passer Ă cette version pour votre projet. Il y a toutefois quelques limitations temporaires : - Certains paquets Python compilĂ©s ne fournissent par encore de `wheel` (paquets binaires) pour Python 3.10 et donc lâinstallation dâun tel paquet va demander une compilation. Le paquet `numpy`, ainsi que `pandas` qui en dĂ©pend, fait partie de cette catĂ©gorie. La compilation peut sâavĂ©rer compliquĂ©e si vous ĂȘtes sur une distribution qui fait du morcelage de paquets (`-dev`, `-doc`, ...). Il vous faudra alors installer une myriade de paquets systĂšmes (en plus du compilateur) pour pouvoir installer votre paquet python compilĂ©. Donc changer uniquement votre environnement en Python 3.10 nâest pas forcĂ©ment suffisant. Ceci est bien sĂ»r temporaire et les auteurs de ces bibliothĂšques fourniront pour sĂ»r des `wheels` prochainement. - Certains paquets Python nâont pas pensĂ© que la version mineure de Python pouvait tenir sur deux chiffres au lieu dâun. Je pense notamment Ă `uwsgi`. Mais câest corrigĂ© sur la branche principale et une version va voir le jour. Ce ne sont que de petites contraintes qui vont se corriger dâici que la finale sorte dĂ©but octobre, mais en attendant, si vous voulez tester/anticiper, vous savez Ă quoi vous attendre. AltStyle ă«ăăŁăŠć€æăăăăăŒăž (->ăȘăȘăžăă«) / ăąăăŹăč: ăąăŒă: ăăă©ă«ă éłćٰăă©ăŠă¶ ă«ăä»ă é èČćè»ą æćæĄć€§ ăąăă€ă«
AltStyle ă«ăăŁăŠć€æăăăăăŒăž (->ăȘăȘăžăă«) / ăąăăŹăč: ăąăŒă: ăăă©ă«ă éłćٰăă©ăŠă¶ ă«ăä»ă é èČćè»ą æćæĄć€§ ăąăă€ă«