L'instruction match, aka PEP622

Ça devient compliqué à suivre, pour ceux qui prendraient le train en route, ça à démarré en juin avec la PEP622, et qui en gros propose ça :

# point is an (x, y) tuple
match point:
    case (0, 0):
        print("Origin")
    case (0, y):
        print(f"Y={y}")
    case (x, 0):
        print(f"X={x}")
    case (x, y):
        print(f"X={x}, Y={y}")
    case _:
        raise ValueError("Not a point")

(je vous laisse lire les PEPs pour en savoir plus, ou les articles de lwn sur le sujet : partie 1 et partie 2).

La PEP est depuis remplacée par d’autres (PEP634 : la spec, PEP635 : les motivations, et PEP636 : un tuto), et le débat continue toujours…

La PEP642 propose une syntaxe alternative, et d’autres PEPs sont crées pour tenter de résoudre des problèmes spécifiques, comme la PEP640 qui propose une syntaxe pour les variables innutilisées (? au lieu de _).

1 J'aime

Est-ce qu’il y a moyen de tester avec une bibliothèque ou bien un build spécifique de CPython?

Je suis intéressé dans le pattern matching de l’ast.

Vérifie que c’est “à jour”, si " à jour" a du sens vu que les propositions divergent…

1 J'aime

Génial ! Ça serait en Python 3.10 ou 3.11 ?

Il faut d’abord resoudre tous les débats, pleins de points sont encore trop flous pour donner une release, c.f. les différents threads qui parlent de “pattern matching” ici https://mail.python.org/archives/list/python-dev@python.org/

1 J'aime

Il y a ce débat depuis longtemps ?
Pourquoi c’est ressorti et quels sont les plus et les moins de cet instruction ?

Depuis longtemps plus ou moins oui car ça a toujours été une fonctionnalité demandée.
L’intérêt c’est que c’est plus avancé qu’une batterie de if/elif, ça permet de façon très élégante de déconstruire les objets et de récupérer ce que l’on y veut.

Ça revient sur le devant de la scène car Python a changé son analyseur syntaxique, et qu’il est maintenant possible (facilement) d’avoir des soft-keywords, c’est-à-dire des mots-clés qui ne le sont que dans un certain contexte.
Ce qui permet d’introduire cette nouvelle fonctionnalité sans casser des codes précédents qui utiliseraient des variables match ou case.

1 J'aime

Ouais, c’est beaucoup plus élégant.

Ah ! Je ne savais pas pour son analyseur syntaxique.
Oui, ça peut être un plus pour mettre en place cet instruction.

Le problème c’est que dans la forme je trouve ça vraiment très laid et ça casse la simplicité de lecture de Python en l’état.

Par exemple l’introduction du _ pour le any je trouve ça vraiment pas parlant d’un point de vue de langage, je préférerai un mot clé identifiable.

Y a beaucoup à redire sur cette PEP, elle apporte de bonnes idées d’évolution dans le langage selon moi, mais pas avec la bonne forme. Pour l’instant, j’y suis opposé.

De ce que j’avais compris (je n’ai pas relu récemment) ce n’est pas spécifiquement le _ pour any, c’est tout nom de variable (puisqu’une variable peut-être assignée à toute expression), c’est standard dans les mécanismes de pattern-matching.
La spécificité du _ est de ne pas extraire le résultat, ce nom étant déjà couramment utilisé pour des résultats inutiles.

J’avais lu aussi que c’était assez typique du pattern matching dans ses aspects purement sémantiques, mais pour moi ça rompt avec le zen Python dans sa forme. Je n’ai pas de solution miracle à proposer mais je trouve ça perturbant face à l’écosystème existant.

Je ne suis cependant pas hostile à l’idée mais je pense que ça mérite plus de réflexion pour concilier la grammaire de cette fonctionnalité et l’historique du langage.

Qu’est-ce que tu en penses toi ?

Je suis pas très objectif parce que ça fait longtemps que j’attends une telle fonctionnalité en Python, c’est toujours quelque chose que j’ai beaucoup aimé dans les langages fonctionnels.
J’aimais bien la solution proposée à base d’un protocole spécifique de match (méthode __match__), très pythonique, mais ça a l’air d’avoir été reporté, donc j’attends ça avec impatience.

Sinon j’attends aussi de voir comment ils dépatouilleront le problème de tester l’égalité à une variable (matcher que x vaut foo, et non matcher toute valeur de x en la capturant dans une variable foo).

J’ai aucune idée de la meilleur mise en œuvre en Python (en Scheme, c’est une macro), cela dit je plussoie que c’est très utile.

Les exemples utilise la branche https://github.com/brandtbucher/cpython/tree/patma

Voici des usages que j’ai identifie:

Remplacer if / elif / else

Avant

value = input("Value: ")

if value == "one":
    print("Value is: 1")
elif value == "two":
    print("Value is: 2")
else:
    print("Value is unknown")

Après

value = input()

match value:
    case "one":
        print("Value is: 1")
    case "two":
        print("Value is: 2")
    case _:
        print("Unknown value")

Remplacer un dictionnaire de dispatch

Le dictionnaire de “dispatch” est une pattern pour éviter les if / elif / else en pagaille.

Avant


def print_one():
    print("Value is: 1")

def print_two():
    print("Value is: 2")

dispatch = dict(one=print_one, two=print_two)

value = input("value: ")

try:
    func = dispatch[value]
except KeyError:
    print("Unknown value...")
else:
    func()

Après

C’est la même chose pour le cas du if/elif/else

URL Route avec paramétrage

L’idée ici est de décider quelle route prendre en fonction de ce qu’on reçoit en HTTP en tant que chemin.

Avant

Django utilise / utilisait des regex (!) associés a des callables, flask un peu pareil en plus magicien, il utilise un langage spécifique embarque dans une chaîne de caractère qui est traduite en regex et qui permet de match (!) et capturer les paramètres.

En gros le code ressemble a:

def model_delete(request, model, pk):
    model_class = string_to_model(model)
    pk = int(pk)
    model.delete(pk)
    return redirect("/{}/list".format(model))

routes = [
    ["/<model>/create/", model_create],
    ["/<model>/delete/<pk>/", model_delete],
]

for pattern, callable in routes:
    if args := match(pattern, request.path):
        return callable(request, *args)
else:
    return 404

Après

def model_delete(request, model, pk):
    model_class = string_to_model_class(model)
    pk = int(pk)
    model_class.delete(pk)
    return redirect("/{}/list".format(model))

path = request.path.split("/")
match path:
    case [model, "create"]:
        model_create(request)
    case [model, "delete", pk]:
        model_delete(request, model, pk)

Une derniere possibilite c’est faire de la transformation d’ast. C’est une chose que j’ai pas eut le courage de re-faire avec la classe ast transformer parce que c’est pas tres lisible.

Voila un code qui marche que juste un peu, mais qui donne une piste: https://github.com/amirouche/fancyfly/blob/main/fancyfly.py

@amirouche Pourquoi utiliser des listes dans ton premier exemple ?

C’était pas utile, merci!