Le pattern Dispatcher en Python

Ou comment soigner la flemme par l'élégance

a marqué ce sujet comme résolu.

En fait ça me dérange un peu de sortir une liste de pré-requis. Le titre présuppose que le lecteur est assez à l'aise en Python pour s'intéresser aux design patterns et en découvrir un nouveau, ce qui me fait dire que s'il clique, il trouvera surement quelque chose à en tirer (ne serait-ce qu'un pattern en deux classes à copier coller dont un mixin à hériter), sans forcément tout capter du premier coup à l'implémentation. Je n'ai pas envie de l'effrayer en lui jetant à la figure, dès l'intro, tout un tas de notions avancées abordées de façon annexe dans le tuto.

À partir de là la cible me semble naturellement être les gens à qui on ne la fait plus quand on leur dit que "Python est simple et facile", donc sûrement ceux qui ont lu le tuto d'Entwanne.

+1 -0

À partir de là la cible me semble naturellement être les gens à qui on ne la fait plus quand on leur dit que "Python est simple et facile", donc sûrement ceux qui ont lu le tuto d'Entwanne.

Soit. Dans ce cas, je vais faire un retour complet, suite à un lecture attentive, avec papier, crayon, et du temps. Je note tout les points qui m'ont posé une difficulté. Ça ne veut pas systématiquement dire qu'il a quelque chose à reprendre, simplement que, pour un individu non expert, ce point peut nécessité un temps de réflexion. À toi de voir ce que tu fais de ces commentaires ensuite.

1
2
class ChatBot:
    _callbacks = {}

Première subtilité (si, déjà). Tu fais une initialisation hors d'un __init__. Je connais, je crois que ça vise à associer la variable à l'objet plutôt qu'à ses instances. Spontanément, je ne vois pas l'intérêt de faire comme ça plutôt que de manière usuelle (avec un __init__). Je devine, sans connaitre, que @classmethod vise à mettre la méthode dans la classe, et joue donc un rôle similaire. Précision, les décorateurs sont pour moi quelque chose de théorique : je connais (que ce soit par le tuto d'Entwanne ou ceux de Sam&Max), j'ai des usages typiques en tête, mais je n'ai jamais eu concrètement l'occasion de les utiliser). Bref, je vois l'esprit, pas l'intérêt. Après test, ça permet de faire en sorte que ça marche (faire de register_cmd une méthode normale et mettre _callbacks dans un __init__ ne permet pas de reproduire le résultat voulu). Sans que je comprenne précisément pourquoi.

Au passage, je ne connaissais pas suppress.

D'ailleurs, le top, ce serait qu'il existe un mixin, qui puisse apporter la totalité de cette fonctionnalité à n'importe quelle classe.

Mettre un lien, c'est gentil. Mais ça reste un lien vers le milieu d'un 8e chapitre d'un bouquin. Dire ce qu'est un mixin en deux phrases ou une note de bas de page serait très gentil. Je n'ai compris la définition du cookbook qu'après avoir lu le tuto.

1
def __new__(mcs, name, bases, attrs)

Rien de particulier à dire, c'est bien expliqué, mais mon cerveau a mouliné un moment face à la fonction pour comprendre ce qu'elle faisait. Au visitor, j'ai lâché, et lu en diagonal (saturation).

Je dirai que ça résume bien le tuto : plein de notions inhabituelle (pour moi), mise ensemble de manière cohérente et bien expliquée, mais qui mises bout-à-bout font beaucoup. Le tuto est excellent, et je suis ravi de voir du contenu de ce niveau sur ZdS. Encore bravo pour ta pédagogie. :)

+0 -0

Ok. Je vois.

Globalement j'en déduis qu'il manque sur ZdS un tuto sur la POO, où l'on puisse trouver ce qui te bloque au départ (attributs et méthodes de classe), parce qu'à mes yeux ce sont des trucs de base en POO : il n'y a pas d'__init__ simplement parce que l'instance n'a pas besoin de maintenir un état. L'état modélisé par l'attribut _dispatch est propre à (partagé par) toute la classe.

Je rajouterai des explications sur le code initial dans les prochaines versions.

PS : à propos du "milieu de 8e chapitre d'un bouquin", le livre en question n'a jamais été écrit pour être lu dans l'ordre, ça pourrait tout aussi bien être le début du chapitre 1 que la fin du dernier chapitre, le contenu pointé par le lien est totalement indépendant du reste du livre.

+2 -0

Très chouette tuto !

Juste une petite question :

On en profitera d'ailleurs pour déplacer toutes nos @classmethod dans la définition de la métaclasse.

Est-ce bien idiomatique de faire ça ?

yoch

Je viens de tomber sur un cas qui le justifie, dans une autre métaclasse perso (celle de ma lib serialobj).

Quand on définit une métaclasse de ce style (i.e. qui ajoute un comportement hyper précis comme celui-là), l'utilisateur avancé peut vouloir créer une métaclasse dérivée, c'est-à-dire hériter du comportement de la métaclasse en question, + le spécialiser pour rajouter des features avancées (c'est mon cas dans un projet dans lequel j'utilise serialobj aujourd'hui).

En somme dans mon cas je veux que mes classes héritent à la fois du comportement des serialobjs (ou de mon dispatcher pour revenir à l'exemple de ce tuto), mais avec en plus des fonctionnalités rajoutées dans la métaclasse. Dans ce cas, si toute l'implémentation réside dans la métaclasse, c'est vachement plus pratique : on crée une métaclasse un peu plus précise qui hérite de celle de la lib, et des objets qui sont des instances de cette nouvelle métaclasse. Les instances de cette métaclasse seront du coup, automatiquement, des serialobjs (ou dispatchers) on steroids sans avoir à se hasarder avec de l'héritage multiple dont les deux classes mères ont des métaclasses custom.

+0 -0

J’arrive un peu après la bataille mais je tenais à laisser un petit commentaire. J’ai trouvé ce tuto vraiment très intéressant ! J’avais déjà recodé le décorateur singledispatch pour le plaisir, et ce que je viens de lire offre de nouvelles possibilités. Je ne sais pas ce que vous en pensez, mais ce dispatch me fait un peu penser a ceux utilisés dans les classes base view de Django ?

+0 -0

Salut !

Ce tuto m’a l’air fort intéressant. :)

Je n’ai clairement pas ton niveau en Python donc je vais peut être dire une connerie, mais il me semble que définir un attribut dans la méthode __init__() rend l’attribut propre à l’instance créée. Je me trompe ? Dans ce cas, pourquoi ne pas créer l’attribut __callbacks__ dans une méthode __init__() ?

J’ai peut-être dit une grosse connerie sans m’en rendre compte hein, mais du coup je suis peut-être pas le seul parmi les semi-débutants à me poser la question.

+1 -0

Je n’ai clairement pas ton niveau en Python donc je vais peut être dire une connerie, mais il me semble que définir un attribut dans la méthode __init__() rend l’attribut propre à l’instance créée. Je me trompe ? Dans ce cas, pourquoi ne pas créer l’attribut __callbacks__ dans une méthode __init__() ?

rezemika

Je dirais que c’est parce que les callbacks doivent être enregistrés au niveau de la classe. Si on reprend le premier exemple donné, avec ChatBot, on veut que toutes les instances aient accès aux commandes enregistrées, pas juste l’instance bot.

Y’a pas de mauvaise question. :)

L’idée du dispatcher c’est de pouvoir faire quelque chose comme :

1
2
3
@MonDispatcher.register('foo')
def callbak_foo(*args): 
    return 42

Au moment où le décorateur est appelé, le dispatcher n’est pas instancié, c’est donc auprès de la classe MonDispatcher que tu enregistres le callback et non d’une instance de cette classe. Du coup tu as besoin que __callbacks__ soit un attribut de classe et non d’instance. C’est pour ça que tu ne peux pas l’initialiser dans __init__.

On pourrait imaginer faire à la place un dispatcher qu’il faut instancier pour enregistrer des callbacks auprès de l’instance, mais dans ce cas on perdrait tous les trucs cools de ce tuto, comme le fait de spécialiser un dispatcher en créant une classe fille, et du coup ça n’apporterait rien de plus que maintenir un simple dictionnaire de callbacks dans une variable globale.

+0 -0

Ah d’accord, j’avais mal compris le problème avec l’héritage. Merci pour vos explications ! :)

+0 -0

Je ne sais pas ce que vous en pensez, mais ce dispatch me fait un peu penser a ceux utilisés dans les classes base view de Django ?

akuket

Je n’ai jamais utilisé Django, mais ce genre de dispatch est vraiment hyper courant : ça revient juste au runtime à décider de ce qui est fait en fonction du contenu d’un dictionnaire. Ce qui est particulier, c’est que les valeurs du dictionnaire sont des fonctions et donc que l’on peut utiliser un décorateur pour les insérer dedans.

Du coup, ça ne m’étonne pas que Django l’utilise pour ses views. Les microframeworks web utilisent le même genre de pattern pour associer une route à une action…

+0 -0

Salut,

J’ai lu ton tuto avec intérêt et j’ai enfin vraiment compris ce qu’était une metaclass. L’approche par l’exemple est très bon je trouve.

Juste une petite question : pourquoi définis-tu la property "dispatcher" dans le new et non pas dans la classe Dispatcher, comme une propery "normale" ?

Salut,

J’ai lu ton tuto avec intérêt et j’ai enfin vraiment compris ce qu’était une metaclass. L’approche par l’exemple est très bon je trouve.

Juste une petite question : pourquoi définis-tu la property "dispatcher" dans le new et non pas dans la classe Dispatcher, comme une propery "normale" ?

sebsheep

On aurait aussi pu faire comme ça. Ça ne change pas grand chose fonctionnellement.

Cela dit je voulais que même la classe Dispatcher (qui est une instance de DispatcherMeta) ne vienne pas trop toucher à la tambouille interne (l’attribut de classe __callbacks__), et du coup, condenser tout ce code dans celui de la métaclasse.

C’est surtout une affaire de préférence personnelle. Au doigt mouillé ça me semble préférable de mettre ce code ici, mais seule l’expérience (qui ne s’est jamais produite) du jour où j’aurai besoin de modifier ce code pourra nous dire si c’était finalement une bonne idée ou pas.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte