URL: https://linuxfr.org/news/python-3-10-rc1-est-disponible
Title: Python 3.10 (rc1) est disponible
Authors: Cyrille Pontvieux
Snark, yPhil, Ysabeau, ariasuni, Benoît Sibaud, Yves Bourguignon, bobble bubble, pamputt, palm123 et dourouc05
Date: 2021-04-30T13:19:22+02:00
License: CC By-SA
Tags: python et sortie_version
Score: 38
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…
![Logo Python](https://www.python.org/static/img/python-logo.png)
----
[Notes de versions](https://www.python.org/downloads/release/python-3100rc1/)
----
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.