Le pattern Dispatcher en Python

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

a marqué ce sujet comme résolu.

Ben c'est une classe, qui spécialise la classe type (toutes les classes de Python dérivent de type, alors que tous les objets en Python dérivent de object, donc object est une instance de type), donc une métaclasse sert à decrire le comportement d'une classe, tout comme une classe sert à décrire le comportement d'un objet.

Si l'on veut qu'une classe B possède les mêmes méthodes et les attributs que la classe A mais en lui ajoutant des comportements spéciaux, il suffit que B hérite de A. En faisant ça on crée une nouvelle classe qui descend de object (la classe mère de toutes les classes).

De façon analogue, si on veut qu'une classe (et tous ses descendants) possède(nt) des méthodes de classe et des attributs de classe spéciaux, alors on décrit ces attributs et ces méthodes dans une classe fille de type (la classe de toutes les classes).

Dans le tuto, j'ai besoin qu'un attribut de classe (le dictionnaire), soit créé et ajouté automatiquement à toutes les classes qui héritent de Dispatcher, donc je décris l'ajout de cet attribut dans le constructeur de la métaclasse, c'est à dire le constructeur qui est appelé chaque fois qu'on crée une nouvelle classe avec class Foo: .

+1 -0

Trivial. ^(;,;)^

Édit : de manière plus complète : j'ai dû lire ton message 4 fois avant de le comprendre — j'espère — correctement.

Et ça m'a fait ça pour tout le tuto. Autant dire que je trouve le niveau un peu élevé pour moi. :D

+0 -0

C'est jamais évident de trouver le bon angle. :D

Peut-être en comparant avec un truc connu : on est bien d'accord que toutes les classes possèdent un dictionnaire (__dict__) qui référence ses méthodes ? (str.__dict__ référence toutes les méthodes de la classe str).

Bien, donc ça veut dire qu'à un moment donné, on passe de ceci :

1
2
3
4
5
class Foo:
    def une_methode(self):
        pass
    def une_autre_methode(self):
        pass

À cela :

1
2
>>> Foo.__dict__
mappingproxy({'une_autre_methode': <function Foo.une_autre_methode at 0x7f929bde97b8> ...

Pour que Foo ait un attribut, il faut qu'à un moment on ait appelé un constructeur qui l'a initialisé et rempli ce dictionnaire.

OK ?

He bien ce constructeur, c'est le constructeur de la classe type :

1
2
>>> isinstance(Foo, type)
True

C'est le constructeur qui "construit les classes" quand Python a fini de les parser.

Par chance, en Python, (et c'est là le truc qui donne le vertige) type est… une classe. Vu qu'on aime pas trop raisonner avec des miroirs qui se réfléchissent à l'infini, on dira que type est une méta-classe, parce que quand on instancie type, on obtient une classe.

Donc si on remet tout bout à bout : dans le tuto j'ai besoin qu'un attribut soit initialisé un peu comme l'attribut __dict__ des classes, c'est-à-dire automatiquement chaque fois que je déclare un Dispatcher. Comme c'est dans le constructeur de type que ça se passe, eh bien je crée une classe qui hérite de type et je surcharge son constructeur pour y ajouter ma tambouille.

C'est peut-être plus clair comme ça ?

+1 -0

Dans ce cas je vais ajouter cette explication au tuto, et également m'atterder un peu plus sur la raison pour laquelle on a besoin d'une métaclasse.

nohar

Je suis en train de réfléchir à différentes reformulations dans mon cours, et j'aimerais pouvoir expliquer le plus clairement possible les métaclasses. Je dois dire que je bloque pas mal, donc je suis preneur de toutes les idées à ce sujet.

Dans ce cas je vais ajouter cette explication au tuto, et également m'atterder un peu plus sur la raison pour laquelle on a besoin d'une métaclasse.

nohar

Je suis en train de réfléchir à différentes reformulations dans mon cours, et j'aimerais pouvoir expliquer le plus clairement possible les métaclasses. Je dois dire que je bloque pas mal, donc je suis preneur de toutes les idées à ce sujet.

entwanne

Je pense que c'est super difficile d'expliquer les métaclasses dans un tuto. Plus difficile que de les utiliser. Je crois que sije voulais les expliquer dans un cours, je passerais beaucoup de temps à familiariser le lecteur avec le jonglage mental une classe est un objet, parce que c'est une instance de type, donc type est une métaclasse.

Edit : en fait cette question, "comment expliqueriez-vous les métaclasses à un débutant ?", est super intéressante. Elle mérite sûrement un thread à elle toute-seule.

Edit 2 : https://zestedesavoir.com/forums/sujet/5736/comment-expliqueriez-vous-les-metaclasses-a-un-debutant/

+1 -0

Merci ! :-)

"Idiomatique" je ne saurais pas dire, mais ça me semble logique et "plus propre" que toutes les méthodes de classe/propriétés soient défenies au même endroit que l'attribut qu'elles servent à manipuler, dans la métaclasse, et de réserver la méthode d'instance (dispatch) à la classe Dispatcher.

+0 -0

D'accord, je comprend. En lisant ton message, je me demande pourquoi ne pas définir également dispatch comme méthode de classe ?

Sinon, une petite remarque à la relecture : tantôt tu écris __callbacks, tantôt __callbacks__. Egalement, il y a un __slots__ = () qui apparaît dans la dernière version sans raison véritable d'être là en particulier et non dans SingleDispatcher.

+0 -0

D'accord, je comprend. En lisant ton message, je me demande pourquoi ne pas définir également dispatch comme méthode de classe ?

Parce qu'on veut que dispatch soit accessible aux instances des classes filles : elle sert de pont entre l'attribut de classe et les méthodes d'instance qui l'utilisent. Cette méthode n'est d'ailleurs pas nécessaire à un dispatcher. C'est juste un raccourcis.

Sinon, une petite remarque à la relecture : tantôt tu écris __callbacks, tantôt __callbacks__. Egalement, il y a un __slots__ = () qui apparaît dans la dernière version sans raison véritable d'être là en particulier et non dans SingleDispatcher.

yoch

Oui j'ai beaucoup modifié le tuto depuis la première version.

Je vais tout renommer en __callbacks__. Le __slots__ = () sert à indiquer que la classe ne définit pas d'état (contrairement à la métaclasse), et donc qu'elle est utilisable en tant que mixin. Je pense que je peux carrément le virer dans le tuto, sinon je vais me retrouver à devoir expliquer comment marche __slots__ et j'ai pas envie. :D

+0 -0

En lisant ton message, je me demande pourquoi ne pas définir également dispatch comme méthode de classe ?

Parce qu'on veut que dispatch soit accessible aux instances des classes filles : elle sert de pont entre l'attribut de classe et les méthodes d'instance qui l'utilisent. Cette méthode n'est d'ailleurs pas nécessaire à un dispatcher. C'est juste un raccourcis.

nohar

En faire une méthode de classe ne l'empêche pas d'être accessible depuis une instance. Pour moi, puisque dispatch n'est pas lié à une instance en particulier, il devrait a priori être une méthode de classe. (du coup, visit aussi pourrait devenir une classmethod)

Alors il faut voir que dans le cas d'un vrai visitor ou un vrai dispatcher, tu vas avoir besoin que l'instance maintienne un état pour faire des trucs intelligents pendant qu'il visite un noeud ou traite un callback, là ce n'est pas le cas parce que je voulais que Dispatcher et Visitor soient des mixins (et que mes exemples se cantonnent au plus simple), mais du coup, ça n'aurait plus trop de sens que ces méthodes deviennent des méthodes de classe. C'est l'instance qui visite ou qui dispache, en fonction de ses données de classe (et d'instance le cas échéant), et je voudrais que ça apparaisse dès le design.

PS : sinon techniquement tu as raison, ça fonctionnerait tout aussi bien. C'est juste que j'aime bien quand le design traduit les responsabilités des classes. Si en lisant la doc du module, l'utilisateur voit qu'il hérite d'une méthode dispatch(self, ...), ça lui semblera évident que c'est pendant l'exécution d'une méthode d'instance qu'il devra appeler cette méthode, dispatch, et donc intuitivement ce sera son instance qu'il rendra responsable de toutes les opérations qui viennent autour du dispatch. A contrario si on en fait une méthode de classe, les gens vont être tentés de créer des dispatchers sans jamais les instancier, ce qui n'est pas du tout l'intention du pattern, et risque de traduire une erreur de design (un dictionnaire ferait tout aussi bien le job).

+0 -0

Tu n'as pas précisé du tout les prérequis.

D'ailleurs, quel est ton public cible ? Je demandes ça pour essayer de faire un retour un peu plus construit que le dernier.

+0 -0

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
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