Comment expliqueriez-vous les métaclasses à un débutant ?

Autrement que par "t'occupe, on s'en sert jamais".

a marqué ce sujet comme résolu.

Salut,

En ce moment, je suis en train de rédiger un tuto dans lequel je suis obligé d'implémenter une métaclasse pour résoudre un problème. Le hic, c'est que ce passage du tuto semble perdre vachement les lecteurs, donc j'ai envie d'y inclure une explication qui puisse au moins les aider à suivre (parce que la métaclasse en question n'est pas le sujet du tuto). De même, entwanne semble bloqué dans sa recherche d'idées pour améliorer son cours sur les métaclasses.

Je vous soumets donc la question, afin de faire un petit brainstorm dessus :

Comment expliqueriez-vous les métaclasses à quelqu'un qui connaît tout juste la POO en Python ?

À mon avis, une bonne approche, c'est de commencer par un constat bien concret dans la console : on peut manipuler une classe (l'assigner à une variable, récupérer ou modifier ses attributs) comme n'importe quel objet en Python. Donc une classe est un objet.

Si une classe est un objet, ça veut dire qu'elle est une instance de quelque chose :

1
2
>>> type(str)
<class 'type'>

En somme, toutes les classes en Python sont des instances de type. C'est pourquoi type est une métaclasse : une classe qu'on instancie pour obtenir une nouvelle classe.

Par conséquent, pour créer une métaclasse, il suffit d'hériter de la classe type.

Ensuite, il reste beaucoup de choses à expliquer, et je pense que dans un seul tuto on ne pourrait pas toutes les faire, mais si on devait sélectionner les notions les plus importantes à connaître quand on veut programmer des métaclasses, selon vous ça serait lesquelles ?

C'est à vous !

+2 -0

Un exemple, un exemple, un exemple. Je pense qu'il faut absolument un exemple pour comprendre. Un truc bidon, par exemple renommer les méthodes.

Mais surtout, ne pas s'étaler, les métaclasses sont le genre de choses que tu ne comprends pas forcément du premier coup, donc il vaut mieux les présenter rapidement et mettre quelques liens pour ceux qui veulent appronfondir.

Sinon, deux liens qui font référence AMHA.

Ca me semble être la meilleure approche.

Le truc qui m'avait le plus aidé lors de mon apprentissage de Groovy (je ne sais pas dans quelle mesure ça se rapproche de la méta-programmation de Python) c'est le schéma de résolution d'une propriété ou d'une méthode au Runtime. Et notamment cette phrase : "Pour chaque classe, qu'il s'agisse d'une classe Java ou Groovy, dans l'environnement runtime de Groovy, il existe une méta-classe associée, cette méta-classe joue le rôle d'intercepteur lors d'un appel de méthode".

Ca passe par un MetaClassRegistry qui stocke les associations classe <-> méta-classe et au final, pour peu qu'on sache ce qu'est un registry (un dictionnaire en somme), ça se comprend assez aisément. Dans le cas de Python, j'imagine que c'est l'interpréteur qui joue ce rôle directement, mais rien de bien sorcier.

Donc oui, commencer par dire qu'une classe est un objet, clairement c'est le bon point de départ, ensuite essayer d'expliquer ce que fait l'interpréteur quand on lui demande d'appeler une méthode sur un objet. C'est satisfaisant pour le lecteur qui a l'impression de rentrer dans les arcanes du truc (et c'est le cas), et au final c'est pas si complexe que ça. Il faut savoir bien l'illustrer cela dit, dans le cas de Groovy c'est chouette, parce que tout le monde s'est déjà dit un jour en Java "ah ptain c'est con il manque cette méthode dans l'API standard, ça serait bien pratique s'ils la rajoutaient".

Ca permet notamment de résoudre les exercices classiques de programmation en agissant directement sur la classe standard (ici la classe String CharSequence)

PS : ah mince du coup j'ai un peu l'avis contraire de Nodraak. Je poste quand même du coup, au cas où y'ait du bon à piocher ici aussi.

+0 -0

Ce qui bloque, je pense, les débutants (dont moi), c'est l'imbrication des concepts : une classe pour instancier une classe. Quand on n'a pas de recul sur la question, on se perd facilement, notamment au niveau de la distinction entre __init__ et __new__.

Partant de là, il pourrait être intéressant de commencer par manipuler des métaclasses sous forme de fonctions.

Une fois que le lecteur est familier avec le fait qu'une classe est un objet, qu'on peut en créer dynamiquement, on lui présente les métaclasses sous forme de classes.

+0 -0

Je pense que le concept de métaclasse en lui-même est aisé à comprendre : les classes sont des objets, donc des instances d'autres classes, appelées métaclasses.

Le soucis est plutôt au niveau de la mise en application, et des différents concepts que ça recouvre.

  • Le constructeur __new__ qui doit souvent être introduit à ce moment là car peu usité en dehors des météclasses ;
  • L'ordre de résolution des méthodes et attributs (MRO).

Et aussi, il faut réussir à faire comprendre l'intérêt des métaclasses, ce qui est loin d'être évident. Je me souviens des premières fois où j'ai été confronté au concept, je comprenais le fonctionnement, mais la question qui revenait était « À quoi ça sert ? >.

Je dirais donc qu'il faut travailler sur une série d'exemples faciles et concrets de mise en application, j'avais par exemple trouvé la classe Enum qui me paraissait assez simple à expliquer.

En ce moment, je suis en train de rédiger un tuto dans lequel je suis obligé d'implémenter une métaclasse pour résoudre un problème.

nohar

C'est marrant, parce que je trouve que c'est déjà presque gagné, dans ces conditions. Si tu as à montrer les métaclasses parce-que tu y es obligé, et que tu expliques en quoi, ça devrait le faire. La difficulté peut généralement venir du fait qu'on se dise, après avoir vu les métaclasses : "oui, so what?". Parce que sinon, comme le dit entwanne, le concept en soi se comprend aisément. C'est plutôt une chance que tu aies à les montrer dans ton tutoriel.

+2 -0

En ce moment, je suis en train de rédiger un tuto dans lequel je suis obligé d'implémenter une métaclasse pour résoudre un problème.

nohar

C'est marrant, parce que je trouve que c'est déjà presque gagné, dans ces conditions. Si tu as à montrer les métaclasses parce-que tu y es obligé, et que tu expliques en quoi, ça devrait le faire.

Au

Dans le cas de mon tuto, c'est exactement ce que j'ai fait : je me suis bien appesanti sur la raison pour laquelle j'étais obligé de passer par une métaclasse.

Et je suis complètement d'accord avec vous deux : ce qui doit bloquer les gens qui découvrent les métaclasses, c'est que la plupart du temps on ne leur montre pas d'exemple concret (qui sert dans la vraie vie) pour légitimer leur existence.

Partant de là, il pourrait être intéressant de commencer par manipuler des métaclasses sous forme de fonctions.

Une fois que le lecteur est familier avec le fait qu'une classe est un objet, qu'on peut en créer dynamiquement, on lui présente les métaclasses sous forme de classes.

Vayel

Alors je suis d'accord avec au moins ça : montrer qu'on peut utiliser directement le constructeur type(name, bases, attrs) dans la console peut grave aider.

Par contre je ne suis pas très chaud à l'idée d'y aller en plusieurs étapes en montrant plusieurs façons différentes de construire une métaclasse. Si Python est aussi facile à apprendre, c'est en partie grâce à l'idiome "There should be one, and preferably only one, obvious way to do it.". Pour rendre un cours compréhensible, je pense qu'il faut éviter de faire apprendre au lecteur une syntaxe ou une construction temporaire qu'on remplace plus tard par la façon normale et habituelle de faire la même chose : appeler le constructeur de type dans la console, c'est cool pour expliquer et faire comprendre ce qui se passe, mais dans la réalité, personne ne fait jamais ça, parce que la façon idiomatique de créer une métaclasse, c'est d'hériter de type. Après, si on surcharge __new__ plutôt que __init__, c'est parce que dans __new__, on agit avant que la classe ne soit construite par Python. En général c'est plus élégant et plus safe de faire comme ça, plutôt que de patcher la classe après coup dans __init__.

Mais tu soulèves un point important : les gens méconnaissent peut-être la différence entre __new__ et __init__.

+1 -0

Parler de init qui sert à construire un objet puis exposer new qui fait appel au constructeur de la "super class" type serait peut être une bonne approche ^^ enfin j'me plante sûrement et même si c'est pas le cas, j'ai oublié des trucs :-°

Folaefolc

Je ne sais pas ce que tu ranges derrière « construire », mais on ne peut pas vraiment dire que la méthode __init__ construise un objet.

__new__ est une méthode de classe, qui construit justement un objet du type en question. __init__ est une méthode d'instance, qui sert à initialiser un objet nouvellement créé.

Dans le doute, réflexe : une petite expérience.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Foo:
    def __new__(*args, **kwargs):
        print("new")
        print("=>", args, kwargs)
        obj = object.__new__(*args, **kwargs)
        print("<=", obj)
        return obj

    def __init__(self):
        print("init")

foo = Foo()
1
2
3
4
new
=> (<class '__main__.Foo'>,) {}
<= <__main__.Foo object at 0xf6dc8710>
init

On voit que __new__ est appelée en premier avec la classe Foo en premier argument (donc c'est une méthode de classe comme l'a dit Entwanne). Et qu'elle retourne une nouvelle instance de cette classe (un nouvel objet).

Puis c'est au tour de __init__ d'être appelée.

On dit souvent en Python que __init__ est le constructeur… Eh bien c'est un abus de langage. Le véritable constructeur de la classe est __new__ : avant de l'appeler, l'objet n'existe pas, et c'est elle qui construit le nouvel objet et le retourne. __init__, comme son nom l'indique, est l'initialiseur. C'est elle qui met l'objet nouvellement créé dans son état initial.

Cet abus de langage est assez courant parce que dans tous les autres langages orientés objet (ou presque), le constructeur et l'initialiseur sont une seule et même méthode.

+1 -0

D'après la doc, __new__ est une méthode statique. Ça explique d'ailleurs pourquoi il faut lui passer explicitement la classe à construire.

(Que __new__ reçoive automatiquement la classe voulue comme premier argument lors de la construction est dû au fait que c'est le __call__ de la métaclasse qui est chargé d'invoquer __new__, or __call__ est une méthode normale dont le premier argument correspond à la classe en question. En d'autres termes, Foo(1) correspond en réalité à Foo.__call__(1), qui correspond lui même à type(Foo).__call__(Foo, 1), et qui va se charger d'appeler Foo.__new__(Foo, 1) afin de construire l'objet voulu.)

+1 -0

Bonjour à tous,

@nohar,

Comment expliqueriez-vous les métaclasses à un débutant ?

Je ne le ferais pas, en tout cas pas avant d'avoir de sérieux prérequis dans la partie avancé du langage python, une bonne base en POO et une grosse expérience, ce qui ne rentre plus dans le cadre du débutant.

je suis en train de rédiger un tuto dans lequel je suis obligé d'implémenter une métaclasse pour résoudre un problème

Alors, n'y a-t-il pas moyen de contourner la solution pour arriver à un niveau syntaxique correspondant au débutant, voir intermédiaire ?

Et au pire, montrer l'autre solution avec les métaclasses afin qu'il puisse en voir un avantage plus tard lors de sa progression dans le langage ?

Désolé si je fais mon rabat-joie :(

Bonne journée, à bientôt

En fait le terme "débutant" porte a confusion dans le titre de ce thread. Le tuto en question est destiné à des gens qui développent en Python et sont déjà assez à l'aise avec.

Si je dis que je suis "obligé de passer par une métaclasse" c'est surtout parce que c'est la seule solution propre. L'unique alternative dans mon cas, aurait été de faire un décorateur de classe, mais cette solution est beaucoup moins puissante (et intuitivement plus "sale" puisqu'elle consiste à planter des attributs et méthodes sur une classe déjà créée, et je ne suis pas fan du monkey patching).

+1 -0

En fait le terme "débutant" porte a confusion dans le titre de ce thread. Le tuto en question est destiné à des gens qui développent en Python et sont déjà assez à l'aise avec.

Annoncer les pré-requis utiles pour bien comprendre ton tutoriel serait intéressant dans ce cas de figure, non ?

Si je dis que je suis "obligé de passer par une métaclasse" c'est surtout parce que c'est la seule solution propre. L'unique alternative dans mon cas, aurait été de faire un décorateur de classe, mais cette solution est beaucoup moins puissante (et intuitivement plus "sale" puisqu'elle consiste à planter des attributs et méthodes sur une classe déjà créée, et je ne suis pas fan du monkey patching).

La question que je me pose, est, si j'utilise un autre langage dont les métaclasses, les décorateurs sont inexistants, puis-je résoudre le problème et créer un bot IRC ?

Je pense que oui, ce qui ne rend pas les métaclasses, les décorateurs indispensables dans ce type de problème, ou je me trompe ? C'est le mot "obligé" qui me gêne, car quand un débutant veut créer un bot IRC, si on lui dit qu'il est "obligé" d'utiliser les métaclasses en python, il va vite abandonner le langage. Après, te donner des exemples afin d'utiliser ce concept je n'en ai pas !

Cependant le tutoriel, je l'ai compris en le lisant 3 fois, mais avec une bonne expérience et avec une connaissance déjà acquise concernant ce concept que malgré tout je n'utilise jamais. Alors je te dis bravo pour l'écriture de ce tuto…

le tuto est déjà en ligne

Mais le sujet n'étant pas résolu ni fermé ;)

+0 -0

Concernant la question de départ, j'ajouterais une méthode simple et efficace AMHA, qui s'applique d'ailleurs à beaucoup d'autres cas (c'est une méthode que perso j'emploie couramment en dehors de python aussi). Donner une trace des différents appels "cachés" lors de la création d'une classe ou d'un objet à grands coups de print aide grandement à comprendre les mécanismes en jeu et visualiser l'ordre des choses. Quelque chose comme ceci.

Bien sûr, ce n'est pas suffisant (ça ne remplace pas les explications, mais ça aide déjà pas mal à les faire passer), et le lecteur doit être familier avec certaines notions pas forcément triviales (d'où l'utilité des prérequis), mais ça aide à construire une représentation mentale de ce qui se passe réellement, et il pourra toujours jouer avec le code pour vérifier divers points de compréhension.

EDIT: Pour un exemple réaliste d'utilisation et simple à la fois, on peut par exemple s'inspirer de ce sujet. Mais il est difficile de trouver quelque choses qui fasse appel à toutes les variations possibles (par exemple l'usage de __prepare__).

Sinon, il me semble important d'insister sur le fait qu'il faut toujours se poser la question de savoir si l'emploi d'une métaclasse est vraiment adaptée au problème, et si l'utilisateur final de la classe ne va pas être plus perdu qu'autre chose par l'aspect "magique" résultant. (autrement dit, le "t'occupe, on s'en sert jamais" a son utilité pédagogique ;) )

+0 -0

En fait le terme "débutant" porte a confusion dans le titre de ce thread. Le tuto en question est destiné à des gens qui développent en Python et sont déjà assez à l'aise avec.

Annoncer les pré-requis utiles pour bien comprendre ton tutoriel serait intéressant dans ce cas de figure, non ?

J'avais expliqué pourquoi je ne voulais pas complexer le lecteur avec une liste impossible de prérequis sur des trucs annexes : le sujet du tuto est le pattern. Pas son implémentation.

Si je dis que je suis "obligé de passer par une métaclasse" c'est surtout parce que c'est la seule solution propre. L'unique alternative dans mon cas, aurait été de faire un décorateur de classe, mais cette solution est beaucoup moins puissante (et intuitivement plus "sale" puisqu'elle consiste à planter des attributs et méthodes sur une classe déjà créée, et je ne suis pas fan du monkey patching).

La question que je me pose, est, si j'utilise un autre langage dont les métaclasses, les décorateurs sont inexistants, puis-je résoudre le problème et créer un bot IRC ?

Je pense que oui, ce qui ne rend pas les métaclasses, les décorateurs indispensables dans ce type de problème, ou je me trompe ? C'est le mot "obligé" qui me gêne, car quand un débutant veut créer un bot IRC, si on lui dit qu'il est "obligé" d'utiliser les métaclasses en python, il va vite abandonner le langage. Après, te donner des exemples afin d'utiliser ce concept je n'en ai pas !

fred1599

Je dois m'être mal expliqué dans le tuto si tu as compris qu'on était obligé de passer par une métaclasse pour implémenter un bot IRC. Là où la metaclasse est indispensable, c'est justement pour qu'on puisse utiliser le pattern Dispatcher (lui-même pas obligatoire pour faire quoi que ce soit), en héritant simplement d'une classe, sans avoir à le réimplémenter à chaque fois qu'on en a besoin. Parce qu'au final, ce tuto, on peut déjà en tirer un gros bénéfice en collant ma metaclasse dans un fichier auquel on ne touche plus jamais sans comprendre son implémentation…

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