Quoi de neuf dans Python 3.10 ?

Du Pattern Matching

Comme le bon vieux switch case, ou pas, en tout cas pour ceux qui demandaient tout le temps « comment on fait un switch case en Python » à qui on répondait « une bonne vieille batterie de if, mon ami, ou un dict peut être. », et bien ça y est.

Avant :

try:
    print(
        {
            "+": operator.__add__,
            "*": operator.__mul__,
            "%": operator.__mod__,
            "-": operator.__sub__,
            "^": operator.__pow__,
            "/": operator.__truediv__,
        }[sys.argv[2]](int(sys.argv[1]), int(sys.argv[3]))
    )
except KeyError:
    print("Unknown operator.")

Maintenant :

match sys.argv[1:]:
    case a, "+", b:
        print(int(a) + int(b))
    case a, "*", b:
        print(int(a) * int(b))
    case a, "%", b:
        print(int(a) % int(b))
    case a, "-", b:
        print(int(a) - int(b))
    case a, "^", b:
        print(int(a) ** int(b))
    case a, "/", b:
        print(int(a) / int(b))
    case "-", a:
        print(-int(a))
    case "√", a:
        print(int(a) ** .5)
    case ("π", ):
        print(math.pi)
    case _:
        print("Unknown operator", sys.argv[1:])

J’en ai profité pour ajouter des cas qui nétait pas gérables par la version précédente : deux cas avec une seule opérande (- a et √ a), et une avec une constante π.

Pour celle de la constante j’ai mi ("π", ) et non "π" : sys.argv[1:] est dans tous les cas une séquence, et jamais une chaîne, donc elle peut correspondre à la séquence d’un seule élément ("π", ) mais elle ne peut pas correspondre à la chaîne "π".

Exactement comme on aurait pu écrire :

(pi, ) = sys.argv[1:]

en Python < 3.10.

Vous voulez présenter un autre aspect sympa de Python 3.10 ? Continuons le thread, moi je vais manger.

4 « J'aime »

Vérification optionnelle de la taille pour zip (PEP 618)

Vous connaissez la fonction zip ?
C’est une fonction qui permet d’assembler plusieurs itérables pour les parcourir simultanément.

>>> words = ['abc', 'def', 'ghi']
>>> numbers = [4, 5, 5]
>>> for word, number in zip(words, numbers):
...     print(number, '-', word)
... 
4 - abc
5 - def
5 - ghi

Que fait cette fonction si nos itérables n’ont pas tous la même taille (disons que words contienne 4 éléments alors que numbers n’en contient que 3) ? Elle s’arrête au premier terminé, silencieusement.

>>> words.append('jkl')
>>> for word, number in zip(words, numbers):
...     print(number, '-', word)
... 
4 - abc
5 - def
5 - ghi

Depuis Python 3.10, zip vient avec un paramètre optionnel booléen strict qui permet de lever une erreur dans un tel cas pour avertir que tous les itérables n’ont pas la même taille.

>>> for word, number in zip(words, numbers, strict=True):
...     print(number, '-', word)
... 
4 - abc
5 - def
5 - ghi
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: zip() argument 2 is shorter than argument 1

Cette erreur ne survient qu’en fin d’itération car les itérables peuvent être de taille indéterminée, voire infinie.

Notez que pour avoir le comportement inverse (itérer jusqu’au plus long), il existe aussi la fonction zip_longest du module itertools.

3 « J'aime »

dataclasses et arguments keyword-only

Les dataclasses (classes de données) sont une nouveauté de Python 3.7, elles permettent d’écrire facilement des classes dédiées à simplement contenir des données, sans avoir à gérer manuellement les attributs.

import dataclasses

@dataclasses.dataclass
class Point:
    x: int
    y: int

p = Point(1, 2)
print(p.x, p.y)

On le voit, Point est appelée ici avec des arguments positionnels, mais les arguments nommés sont aussi autorisés (Point(x=1, y=2)).

Avec Python 3.10 il devient possible d’obliger l’utilisation des arguments nommés pour certains ou tous les attributs.
On peut ainsi utiliser kw_only=True lors de la définition de la dataclass pour l’appliquer à tous les attributs.

@dataclasses.dataclass(kw_only=True)
class Point:
    x: int
    y: int

Si on veut faire du cas par cas, on peut utiliser le champ field du module dataclasses pour préciser la valeur de kw_only pour les champs affectés.

@dataclasses.dataclass
class Point:
    x: int
    y: int = dataclasses.field(kw_only=True)

Enfin, il est aussi possible d’utiliser l’annotation KW_ONLY du même module sur un attribut spécial _ pour signifier que tous les champs qui suivent sont à arguments nommés seulement.

@dataclasses.dataclass
class Point:
    x: int
    _: dataclasses.KW_ONLY
    y: int
1 « J'aime »

Y a un meilleur message d’erreur quand l’attribut est pas trouvé:

>>> import sys
>>> sys.getdefaultecoding()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'sys' has no attribute 'getdefaultecoding'.
Did you mean: 'getdefaultencoding'?  # <--- nouveau :)
1 « J'aime »

Ahhh si on part sur les messages d’erreur alors, si on a :

print(42
print(42)

En 3.9 ça donnait :

  File "err.py", line 3
    print(42)
    ^
SyntaxError: invalid syntax

maintenant ça donne :

  File "err.py", line 1
    print(42
         ^
SyntaxError: '(' was never closed

et ça, j’aime beaucoup, le nombre de centaines de fois où j’ai du expliquer qu’en Python quand on a une SyntaxError inexplicable il faut regarder à la ligne d’avant … fiou …

4 « J'aime »