Le guide ultime et définitif sur la programmation orientée objet en Python à l’usage des débutants qui sont rassurés par les textes détaillés qui prennent le temps de tout expliquer. Partie 8.


8ème et dernier chapitre sur la programmation orientée objet en Python. Nous allons voir l’ultime réalité, le secret cosmique de nirvana Pythonique, le truc dont personne ne se sert avant d’avoir au moins codé 3 moteurs de blogs, un bot twitter et une IA de real doll.

J’ai nommé…

Les métaclasses.

Comme tous les articles sur la question, je commence avec la citation standard :

Les métaclasses sont une magie trop obscure pour que 99% des utilisateurs s’en préoccupe. Si vous vous demandez si vous en avez besoin, ce n’est pas le cas (les gens qui en ont vraiment besoin le savent avec certitude et n’ont pas besoin d’explications sur la raison).

Tim Peters, les mec qui a écrit le PEP20.

En résumé, cet article ne vous servira probablement pas. Je me suis servi, en 10 ans, deux fois des métaclasses dans ma vie. C’est pour le sport, quoi.

Pré-requis:

  • Avoir lu la partie précédente et bien tout compris. Mais alors bien tout. Si il subsiste un doute dans votre esprit, relisez toute la série car vous ne vous en sortirez pas.
  • Notamment, avoir bien pigé le principe de object, car les metaclasses ne fonctionnent qu’avec les New Style classes.
  • Comprendre le principe de références, callable, lambdas et dictionnaires en Python. En fait à peu prêt tout sauf les métaclasses parce que ce truc est ce que vous voulez apprendre en dernier de toute façon.
  • Être bien reposé et avoir du temps devant soi. Pas la peine de lire en diagonal. Vraiment.

Musique.

Au début, il y avait les objets

Et Dieu Guido vit que cela était bon, et qu’il n’y avait pas de raison de ne pas se marrer un bon coup, alors il décida que les classes, qui servaient à fabriquer les objets, seraient aussi des objets.

Dans la plupart des langages, une classe est vraiment juste un plan pour produire un objet, un bout de code, une syntaxe élaborée pour dire “voici ma classe”. On peut l’imaginer comme cela en Python également, et vivre très heureux et avoir beaucoup de bons moments :

>>> class CreateurDObject(object):
...     pass
...
>>> mon_objet = CreateurDObject()
>>> print(mon_objet)
<__main__.CreateurDObject object at 0x1c22fd0>

Sauf qu’en Python, les classes sont aussi des objets.

Je vous laisse maturer ça 1 minute. Prenez une inspiration.

Quand l’interpréteur Python lit le mot class, il crée un objet. Dans le code ci-dessus, en mémoire, Python crée un objet CreateurDObject.

Cet objet est une classe, il permet de créer d’autres objets – ses instances – mais ça reste un objet. Et comme tous les objets…

On peut l’assigner à une variable :

>>> ReferenceACreateurDObjet = CreateurDObject
>>> ReferenceACreateurDObjet()
<__main__.CreateurDObject object at 0x1c320d0>

On peut le passer en paramètre :

>>> def afficher_une_class(cls):
...     print("Hey, on m'a passé la classe %s" % cls)
...
>>> afficher_une_class(CreateurDObject)
Hey, on m'a passé la classe <class '__main__.CreateurDObject'>

On peut lui ajouter des attributs à la volée :

>>> CreateurDObject.nouvel_attribut = 'nouvelle valeur'
>>> hasattr(CreateurDObject, 'nouvel_attribut')
True
>>> CreateurDObject.nouvel_attribut
u'nouvelle valeur'

Le 7eme jour, on se faisait grave chier alors on a créé des classes dynamiquement

Peut être vous souvenez-vous qu’on peut créer des fonctions à la volée ?

Parce qu’on peut faire pareil avec les classes :

>>> def fabriquer_une_class(nom):
...     if nom == 'bulbizarre':
...         class Bulbizarre(object):
...             pass
...         return Bulbizarre # je retourne Bulbizarre, PAS Bulbizarre()
...     else:
...         class Salameche(object): # qui prenait carapuce, sérieux ?
...             pass
...         return Salameche
>>> Pokemon = fabriquer_une_class('autre')
>>> type(Pokemon) # Pokemon est une CLASSE, pas une instance
<type 'type'>
>>> nouveau_pokemon = Pokemon() # la je crée une instance
>>> type(nouveau_pokemon)
<class '__main__.Salameche'>

Une classe n’est pas quelque chose de figé dans le marbre, comme tout le reste en Python, on peut les fabriquer dynamiquement, les modifier en cours de route, les malmener, etc.

Aucun pokemon n’a cependant été blessé pendant la rédaction de cet article. Tiens je me demande si il y a des sites zoophiles spécialisés dans les Pokemons. Qu’est-ce que je raconte ? Évidement qu’il y en a. D’ailleurs Pikachu est comme une shocking flesh torch avec piles incluses quand on y pense. Ou un appareil à abdos. Il faut que je fasse des abdos, j’ai pris du bide. Heu… où j’en étais ?

Ah oui. Classes. Dynamiques.

Comme toutes les opérations courantes idées à la con, Python vous permet de faire ça complètement à la main. Maintenant vient la troisième Révélation Des Métaclasses : la fonction type() a deux usages.

Dans une première vie type() est une fonction ordinaire, elle sert à retourner le type d’un objet :

>>> print type(1)
<type 'int'>
>>> print type("1")
<type 'str'>
>>> print type(CreateurDObject)
<type 'type'>
>>> print type(CreateurDObject())
<class '__main__.CreateurDObject'>

Et propose à sa logeuse de descendre ses poubelles. Mais elle a une autre vie électronique, elle est aussi capable de créer une classe.

Oui c’est très con d’avoir choisit la même PUTAIN DE FONCTION pour faire deux trucs qui n’ont rien à voir. Mais les deux usages ont un avenir et il va falloir faire avec, se caler ça où je pense et oublier votre avocat.

Bref, ça marche comme ça :

type(nom de la classe, tuple des parents de la classe, dictionnaire contenant les attributs)

Le nom de la classe, je pense que vous avez pigé.

Le tuple des parents, il peut être vide. C’est dans le cas où vous souhaitez un héritage : vous pouvez passer des références aux classes parentes.

Le dictionnaire est assez simple : chaque clé est un nom d’attribut, chaque valeur est une valeur d’attribut.

Exemple, cette classe :

>>> class Pokeball(object):
...     pass

Peut se créer ainsi :

>>> Pokeball = type('Pokeball', (), {})

Et s’utiliser tout pareil :

>>> print(Pokeball)
<class '__main__.Pokeball'>
>>> print(Pokeball())
<__main__.Pokeball object at 0x1c32450>

Vous aurez noté que le nom de classe passé en paramètre est le même que celui de la variable qui va recevoir la classe ainsi créée. Oui, ils peuvent être différents. Non, ça ne sert à rien.

Et je sais que certains auraient préféré MyLittlePoney, mais je fais avec ma maigre culture G. Faudrait que je tente avec des noms de Pogs.

Un petit exemple avec des attributs :

>>> class Pokeball(object):
...     couleurs = ('rouge', 'blanc')

Ce qui donne :

>>> Pokeball = type('Pokeball', (), {'couleurs': ('rouge', 'blanc')})
>>> Pokeball.couleurs
(u'rouge', u'blanc')

Étant donné que les méthodes sont des attributs…

>>> class Pokeball(object):
  ...     def attraper(self):
  ...         print("ratééééééééééééé")
  ...

Peut se traduire par :

>>> def out(): print "ratéééééééééééé"
...
>>> Pokeball = type('Pokeball', (), {'attraper': out})
>>> Pokeball().attraper()
ratéééééééééééé

Au passage, toute classe créée par type hérite automatiquement de object, pas besoin de le préciser.

Petite démo avec de l’héritage :

>>> class Masterball(Pokeball):
...     def attraper(self):
...         print("yeahhhhhhhhhh")
...

Se transforme en :

>>> def out(): print "yeahhhhhhhhhh"
...
>>> Masterball = type('Masterball', (Pokeball,), {'attraper': out})
>>> Masterball().attraper()
yeahhhhhhhhhh

Je répète les Révélations Divines :

  1. Les classes sont des objets.
  2. On peut créer les classes à la volée.
  3. type() permet de créer une classe manuellement.
  4. Keanu Reeves est un mauvais acteur.

Vous voyez où je veux en venir, là, non ?

Et le tout puissant déclara : tu créeras des classes avec des classes

Et tu n’aimeras pas ça. Alors au début tu le feras avec des fonctions parce que c’est plus facile.

Une métaclasse, c’est seulement le nom du “truc” qui fabrique une classe. C’est tout.

C’est pour ça qu’on appelle cela des métaclasses, les classes des classes.

Puisque les classes sont des objets ?

Vous suivez ?

Non ?

Ah.

o

En deux lignes alors :

UneClasse = MetaClass()
un_objet = UneClasse()

Mieux ?

Vous avez vu la fonction type() créer des classes ? En fait la fonction type() est une métaclasse. C’est la métaclasse dont Python se sert pour créer tous les objets classes, quand vous tapez le keyword class.

Il est facile de voir ça en regardant l’attribut __class__, qui indique quelle classe a servi à créer un objet :

>>> pokemon_capture = 149
>>> pokemon_capture.__class__
<type 'int'>
>>> nom = 'magicarp'
>>> nom.__class__
<type 'str'>
>>> def attrapez_les_presque_tous(): pass
>>> attrapez_les_presque_tous.__class__
<type 'function'>
>>> class Pokedex(object): pass
>>> gadget = Pokedex()
>>> gadget.__class__
<class '__main__.Pokedex'>

Mais quelle est le __class__ de tout __class__ ?

>>> attrapez_les_presque_tous.__class__.__class__
<type 'type'>
>>> pokemon_capture.__class__.__class__
<type 'type'>
>>> nom.__class__.__class__
<type 'type'>
>>> gadget.__class__.__class__
<type 'type'>

Normalement c’est à ce moment là que vous avez la Grande Révélation.

Faites “Ahhhhhhhhhhhhhhhhh”.

Donc la métaclasse, c’est le machin qui fabrique les classes, c’est une factory de classe. type est la métaclasse utilisée par défaut, mais vous pensez bien, chers amis, qu’on peut fabriquer ses propres métaclasses. Sinon ça serait pas marrant.

Ça marche comme ça :

class UneClasse(object):
  __metaclass__ = votre métaclasse
  [ le reste du code de la classe ]

Quand Python va voir ça, il ne va pas créer la classe immédiatement, il va d’abord suivre la chaîne d’héritage pour trouver quelle métaclasse utiliser pour fabriquer la classe :

Il va d’abord vérifier si il y a __metaclass__ de déclarée. Si ce n’est pas le cas, il va chercher dans les parents si ils ont un attribut __metaclass__. Si ce n’est pas le cas, il va utiliser type. Si Python trouve __metaclass__, alors il utilisera son contenu à la place de type.

Dans les deux cas, il va collecter toutes les informations sur la classe (le nom, les parents, les attributs), et les passer en paramètres à type ou votre métaclasse, et récupérer le résultat. Puis, seulement à ce moment là, il enregistre votre classe en mémoire.

Prenez votre temps sur ces paragraphes, c’est un peu la clé de tout le tuto.

Bon, nouvelle révélation : une métaclasse n’a pas besoin d’être une classe.

Je sais, c’est idiot d’appeler un truc “métaclasse” si ça n’a pas besoin d’être une classe. Mais on parle de la fonctionnalité qui utilise type pour un truc qui n’a rien à voir. Ça devait être le jour du beaujolais nouveau quand ils ont introduit la feature.

En fait, une métaclasse peut être n’importe quel callable qui retourne une classe, donc une fonction toute conne fait très bien l’affaire.

Le but de la métaclasse, c’est d’intercepter la création de la classe afin de la modifier, et voici ce que ça donne :

# une métaclasse DOIT avoir la même signature que type() puisque
# Python va lui passer tout ça automatiquement en paramètre
 
def prefixer(nom, parents, attributs):
    """
        On va créer une métaclasse qui prend tous les noms de méthodes,
        et les préfixes du mot "attaque". Oui ça ne sert à rien. Mais
        les usages des métaclasses qui servent à quelque chose sont
        généralement très compliqués.
    """
 
    # On crée un nouveau dictionnaire d'attributs avec les noms préfixés
    # On fait gaffe à pas modifier les __méthodes_magique__.
    nouveaux_attributs = {}
    for nom, val in attributs.items():
        if not nom.startswith('__'):
            nouveaux_attributs['attaque_' + nom] = val
        else:
            nouveaux_attributs[nom] = val
 
    # On délègue la création de la classe à type() :
    return type(nom, parents, nouveaux_attributs)

L’utilisation est toute simple, on écrit une classe normale, et on lui rajoute __metaclass__:

>>> class Ronflex(object):
 
    __metaclass__ = prefixer
 
    def armure(self):
        print("defense +15")
 
    def dodo(self):
        print("zzzzzz")
>>> r = Ronflex()
>>> r.attaque_armure()
defense +15
>>> r.attaque_dodo()
zzzzzz
>>> r.dodo()
Traceback (most recent call last):
  File "<ipython-input-58-90dd54234d5d>", line 1, in <module>
    r.dodo()
AttributeError: 'armure' object has no attribute 'dodo'

Voilà, c’est à peu près tout ce qu’il y a à comprendre des métaclasses :

  1. On intercepte la création classe.
  2. On modifie les paramètres.
  3. On retourne la classe customisée.

Maintenant, la grande question : à quoi ça sert ?

Généralement, ça sert à faire de jolies APIs. Par exemple, les ORM (peewee, SQLAlchemy, l’ORM de Django) utilisent les métaclasses pour avoir un style déclaratif :

class Article(Model):
    titre = model.CharField()

Ici, Model va contenir une métaclasse, comme c’est un parent, la métaclasse sera aussi appelée pour Article, et elle peut donc agir pour modifier titre à la volée.

En effet, quand vous faites :

>>> art = Article.get(id=13)
>>> art.titre
u'Python pour les méca-scriptophiles'

Avec un ORM, vous récupérez la valeur de titre dans la base de données et non un CharField(). C’est le but : cacher des requêtes SQL et donner l’impression de manipuler des objets.

Ici le rôle de la métaclasse, c’est de prendre tous les attributs de types xxxField(), et modifier la classe pour qu’accéder à l’attribut fasse la requête voulue à la base de données.

On peut aussi utiliser les métaclasses pour faire des vérifications : s’assurer que la classe n’utilise pas un nom que vous voulez réserver, ou qu’elle contient bien un attribut.

Il y a même des implémentations d’interfaces (ou plutôt de l’équivalent des classes abstraites en Java) pour Python utilisant les métaclasses. Je ne suis pas convaincu par leur utilité, mais c’est possible.

Soyez prophètes

C’est pas le tout, mais maintenant que vous avez compris, il est temps de limer les bords pour que vous alliez porter la bonne parole.

Donc déjà, bon à savoir : metaclass est un paramètre de classe en Python 3. Ca donne ça:

class DePython3(metaclass=votre_metaclasse)

Ensuite, il y des conventions de nommage. Tout comme on nomme self le premier paramètre des méthodes, et *args / **kwargs les paramètres dynamiques, les paramètres des métaclasses ont des noms conventionels. La métaclasse précédente s’écrirait donc plus proprement :

def prefixer(name, bases, dct):
    nouveaux_attributs = {}
    for nom, val in dct.items():
        if not nom.startswith('__'):
            nouveaux_attributs['attaque_' + nom] = val
        else:
            nouveaux_attributs[nom] = val
 
    # On délègue la création de la classe à type() :
    return type(name, bases, nouveaux_attributs)

Enfin, souvenez-vous que les métaclasses ne fonctionnent qu’avec les New Style classes, donc celles qui héritent de object.

Par ailleurs, vous croiserez peut être des fois __metaclass__ en plein milieu d’un module (pas dans une classe donc). C’est une vieille façon de faire, qui affecte toutes les classes du module. Ce n’est plus recommandé.

Terminons sur une version de métaclasse qui fait la même chose que prefixer(), mais sous forme de classe. Parce que sinon à quoi ça sert que ça s’appelle une métaclasse, hein ?

# Oui, on peut hériter de type(), car c'est une classe, enfin une métaclasse.
# Mais pas sous forme de fonction. Sous forme de classe. Mais son nom
# est en minuscule, comme celui des classes int() et str(). Qui ne sont
# pas des métaclasses. Souvenez-vous : Beaujolais.
class Prefixer(type):
 
    # __new__ est le vrai constructeur en Python, et il retourne l'instance
    # en cours d'une classe. Or, qu'elle est l'instance d'une métaclasse ?
    # Une classe !
    def __new__(cls, name, bases, dct):
 
        nouveaux_attributs = {}
        for nom, val in dct.items():
            if not nom.startswith('__'):
                nouveaux_attributs['attaque_' + nom] = val
            else:
                nouveaux_attributs[nom] = val
 
        # On délègue la création de la classe à son parent.
        # Notez que cls doit être passé de bout en bout, ce qui n'est pas
        # le cas d'habitude avec 'self'
        return super(Prefixer, cls).__new__(cls, name, bases, nouveaux_attributs)

Et ça s’utilise pareil

>>> class Ronflex(object):
    __metaclass__ = Prefixer
    def armure(self):
        print("defense +15")
    def dodo(self):
        print("zzzzzz")
...
>>> r = Ronflex()
>>> r.attaque_dodo()
zzzzzz
>>> r.attaque_armure()
defense +15
>>> r.dodo()
Traceback (most recent call last):
  File "<ipython-input-66-90dd54234d5d>", line 1, in <module>
    r.dodo()
AttributeError: 'Ronflex' object has no attribute 'dodo'

Pourquoi utiliser une classe plutôt qu’une fonction alors que c’est vraiment plus compliqué ?

Et bien l’avantage d’utiliser une classe est qu’elle peut avoir des attributs et hériter. Car bien entendu, on peut avoir des metaclasses, qui héritent de metaclasses, et ainsi de suite, jusqu’à ce que du Lisp paraisse avoir du sens en comparaison.

On peut même faire plus vicieux et utiliser __call__ au lieu de __new__:

# on hérite plus de type, rien à foutre
class Prefixer(object):
 
    def __init__(self, prefix='attaque'):
        self.prefix = prefix
 
    # __call__ est la méthode appelée automatiquement quand on ajoute () après
    # un nom d'objet. Ca permet de rendre un objet callable. En gros, ça nous
    # permet d'utiliser une instance de Prefixer() comme une fonction
    def __call__(self, name, bases, dct):
 
        nouveaux_attributs = {}
        for nom, val in dct.items():
            if not nom.startswith('__'):
                nouveaux_attributs[self.prefix + nom] = val
            else:
                nouveaux_attributs[nom] = val
 
        return type(name, bases, nouveaux_attributs)

Et c’est sympa car du coup on peut passer des paramètres à notre métaclasse :

>>> class Ronflex(object):
    __metaclass__ = Prefixer(prefix='tatayoyo_')
    def armure(self):
        print("defense +15")
    def dodo(self):
        print("zzzzzz")
...         
>>> r = Ronflex()
>>> r.tatayoyo_dodo()
zzzzzz

Voilà, vous avez vu le visage de Dieu en face. C’est comme ça qu’il fabrique le monde.

Ah mais attendez ? Si type() est la métaclasse de tous les objets ? Qu’est-ce qui et la métaclasse de type() ?

>>> type.__class__
<type 'type'>

Je vous laisse méditer sur cette découverte métaphysique.

31 thoughts on “Le guide ultime et définitif sur la programmation orientée objet en Python à l’usage des débutants qui sont rassurés par les textes détaillés qui prennent le temps de tout expliquer. Partie 8.

  • kontre

    Oh punaise. OK, je crois que j’ai compris. Mais je n’ai pas la moindre idée de où je pourrais éventuellement envisager d’utiliser ça en vrai. Je confirme que ça sert à rien !

    Mais j’ai pas perdu mon temps, les petites remarques m’ont bien fait marrer. Je vais me mettre au Lisp.

    J’ai bloqué sur ce paragraphe :

    Il va regarder d’abord vérifier si il y a __metaclass__ de déclaré. Si ce n’est pas le cas, il va chercher dans les parents si ils ont un attribut __metaclass__. Si ce n’est pas le cas, il va utilise type. Si Python trouve __metaclass__, alors il utilisera son contenu à la place de type.

    En fait python cherche l’attribut __metaclass__ en respectant l’héritage, tout simplement, mais je trouve que c’est tourné de manière bien complexe.

  • Mentat

    Typo :
    Tim Peters, LE mec qui a écrit le PEP20.

    def fabriquer_une_class(nom):
    … if name == ‘bulbizarre’:
    Alors, c’est nom ou name ? Faut se décider là…

    Keanu Reeves est un mauvais acteur.
    -> Tom hanks est un mauvais acteur.

  • martin

    Comment s’appelle alors le fait d’ajouter dynamiquement une propriété à une classe existante:

    Soit la classe shapefile qui ne possède pas la propriété schema
    import shapefile
    reader = shapefile.Reader('strati.shp')
    reader.schema
    Traceback (most recent call last):
    File "", line 1, in
    AttributeError: Reader instance has no attribute 'schema'

    mais si maintenant je veux rajouter la propriété/fonction schema à la classe shapefile:

    def schema(reader):
    .... traitement ....
    return valeur

    shapefile.Reader.schema = property(lambda self: schema(self))

    reader.schema fonctionne

  • GM

    Ah merde, moi je l’ai utilisé qu’une seule fois en 10 ans.
    J’ai raté ma viiiiiie…

  • GM

    ps (désolé) : Je pense qu’aujourd’hui, les décorateurs et les properties permettent de s’affranchir des métaclasses sur la plupart des anciens usages.

  • Recher

    Mon cerveau dégouline un peu, mais j’y ai survécu.

    Juste une remarque. Dans la première définition de la classe Ronflex, tu écris dans la console “r.att”. Et ensuite je suppose que tu fais Tab, pour avoir la liste de tous les attributs de r commençant par “att”.

    C’est un peu confusionnant. Quand je vois un log de console python, je m’attends à ce que l’utilisateur a tapé sur Entrée à chaque ligne, et pas autre chose.
    C’est d’autant plus confusionnant que “att” ressemble à “attributs”, et j’étais en train de me demander si c’était pas une fonction magique pour sortir tous les attributs d’une classe.

    Faudrait, soit mettre un petit commentaire pour dire : “là, j’appuie sur Tab pour avoir l’auto-complétion”, soit carrément utiliser dir(r).

    En tout cas, merci pour cet article, je suis content d’avoir appris un truc dont j’aurais pas besoin. C’est pas drôle de tout le temps apprendre des trucs utiles.

  • Violette

    Bon, j’ai rien compris ce qui en soit n’est pas très grave, mais je tenais à poster pour dire que la musique est vraiment cool :D.

    Merci.

  • Sam Post author

    @Recher: c’est une faute de copie, je le retire.

    @GM: +1. Effectivement, à part pour des features “héritées”, un peu d’instrospection, un usage de __new__ ou d’un décorateur sera plus facile, plus lisible et plus souple.

  • Quasar

    J’ai rien compris, mais… j’adore le petit trombone qui pop lorsqu’on… :D

  • Etienne

    Merci, j’aime bien ce genre de trucs. C’est tellement bien expliqué qu’on a l’impression de regarder par dessus l’épaule de dieu.

    On en oublie même qu’on est sur tes épaules.

  • frodon

    Yop

    C’est un mécanisme qu’on a utilisé dans mon ancien labo de recherche pour développer un langage basé sur Python, mais avec du contrôle de type en plus.
    Le principe est qu’une personne écrit un modèle de données (pour un solveur numérique) basé sur ce langage. Le langage fournit toute une quincaillerie qui permet à l’auteur du modèle de préciser si des champs ont un type particulier (float), sont bornés (>0 pour une température en Kelvin), et d’autres joyeusetés.
    Ensuite ce modèle peut être utilisé par un autre utilisateur comme base pour décrire un jeu de données pour le solveur. Le langage est accompagné d’un moteur qui utilise ce principe des métaclasses pour générer à la volée les classes qui vont bien.

    Bref c’est pas connu, mais ça peut avoir son utilité.
    My 2 cents…

    Frodon

  • JEEK

    Class(e) comme d’habitude…et +1 avec Violette (sur le point que la musique est cool) !

    Merci Sam & Max
    ;-)

  • cyp

    Excellent comme d’habitude.
    J’ai un peux décroché sur la fin mais la création du monde n’est pas encore dans mon emploi du temps donc j’aurai le temps d’y revenir.

    Sinon:
    – fix
    s/que le classes/que les classes

    – là chez moi ça veut pas (ça bloque sur “(” après print), idée?:
    >> >> Pokeball = type(‘Pokeball’, (), {‘attraper’: lambda self: print(“ratéééééééééééé”)})
    File “”, line 1
    Pokeball = type(‘Pokeball’, (), {‘attraper’: lambda self: print(“ratéééééééééééé”)})
    ^
    SyntaxError: invalid syntax

    >> >>Masterball = type(‘Masterball’, (Pokeball,), {‘attraper’: lambda self: print(“yeahhhhhhhhhh”)})
    File “”, line 1
    Masterball = type(‘Masterball’, (Pokeball,), {‘attraper’: lambda self: print(“yeahhhhhhhhhh”)})
    ^
    SyntaxError: invalid syntax

    – alors la poule ou l’oeuf?
    >>> type.__class__

  • Sam Post author

    Arf, c’est juste que j’avais from __future__ import print_function… Print ne marche pas dans une lambda en keyword. Je vais fixer ça.

  • Réchèr

    Ding ! les corrections.

    # la je créer une instance
    # je crée une instance

    shocking flech torch
    shocking flesh torch
    (Pas sûr)

    A oui.
    Ah oui.

    oubliez votre avocat.
    oublier votre avocat.

    C’est pour ça qu’on appelle les métaclasses,
    C’est pour ça qu’on appelle ça des métaclasses,

    il d’abord suivre
    il va d’abord suivre

    le chaîne d’héritage
    la chaîne d’héritage

    qu’elle métaclasse
    quelle métaclasse

    Il va regarder d’abord vérifier
    Il va d’abord vérifier

    métaclass
    métaclasse

    qui n’a rien à avoir
    qui n’a rien à voir

    et les prefixe
    et les préfixe

    à quelques choses
    à quelque chose

    à peut prêt
    à peu près

    pas convaincus
    pas convaincu

    vous croisez
    vous croiserez

    Voili voilà, j’ai corrigé les articles qui me semblaient cool et importants. Pour le reste, je vous laisse vous débrouiller.

    En tout cas, c’était fun, cette série sur la POO. Je connaissais, mais ça m’a permis de voir ou revoir certains détails.

  • Sam Post author

    Merci énormément pour tout se travaille. En relisant certaines fautes c’est carrément la honte :)

  • sensini42

    Le dernier de la série \o/

    8ème : 8e

    Si il subsiste : s’il

    si il y a : s’il y a

    Les classes sont des objets. : une espace avant Les

    «Ahhhhhhhhhhhhhhhhh»

    si il y a/si ils ont : s’il y a/s’ils ont

    Que du pinaillage pour celui-ci. Et je fais partie des 99%.

  • Goldy

    Je ne sais pas trop si on peut encore se permettre de poser des questions, mais j’en aurais une à laquelle je peine à trouver réponse.

    Dans le cas de l’utilisation d’une classe metaclass, il ne semble pas possible de passer des arguments à celle-ci lors de l’instanciation de la classe qu’elle fabrique autrement que de passer par un scope globale.

    Par exemple, si l’on souhaite utiliser une méta classe afin de générer dynamiquement des attribues en fonction de paramètres, comment est-il possible de passer ces paramètres ?

    Une recherche sur les interwebs montre une possibilité en python3 en utilisant Maclass(metaclass=Mametaclass, arg1=1, arg2=2) au moment de l’instanciation de la classe et en ajoutant un **kwargs à la metaclass, mais la même chose ne semble pas possible en python2.

  • Sam Post author

    Les metaclass peuvent être n’importe quel callable, pas forcément une classe. Donc utiliser une lambda fonctionne :

    Maclass(metaclass=lambda: Mametaclass(arg1=1, arg2=2))
    

    ou

    Maclass(object):
        __metaclass__ = lambda: Mametaclass(arg1=1, arg2=2)
    
  • Goldy

    La question que je me pose se situe surtout sur la façon de passer des paramètres à

    __metaclass__

    lors de l’instanciation, le fait que cela soit une classe ou autre chose ne semble pas être un soucis ici.

    Par exemple:

     
    class Truc(object):
     
       __metaclass__ = Meta(param)
     
        [...]
     
    Truc(param=['mes', 'parametres',])

    Dans le code que tu donnes, le paramètre est définit en dur dans la définition de la classe (

    __metaclass__ = Prefixer(prefix='tatayoyo_')

    ), mais si l’on souhaite passer un paramètre dynamiquement lors de l’instanciation de la classe, il ne semble pas y avoir de possibilité de récupérer ce paramètre, à moins de passer par un scope plus global (utiliser une variable global qu’on assignera avant d’instancier la classe…).

    Pour donner un autre exemple, imaginons que nous souhaiterions définir dynamiquement la metaclass à utiliser lors de l’instanciation de la classe, cela serait le même problème.

    Une méthode qui me semblerait peut-être envisageable, serait de définir la classe dans une fonction prenant elle-même les paramètre afin d’avoir un scope accessible, de cette façon:

     
    def set_class(param):
     
        class Maclass(object):
     
            __metaclass__ = MetaClass(param=param)
     
            [...]
     
        return Maclass
     
    Maclass = set_class('mesparams')
     
    inst = Maclass()

    Mais je ne sais pas à quelle point c’est abominable (ni si cela marcherait en fait)…

    (désolé, j’espère que ma question est pertinente et que je ne pollue pas le fil des commentaires ^^’)

  • Goldy

    Ah, mille excuses, les balises code ont fait du caca dans mon commentaires… :c

  • Sam Post author

    Ah tu veux passer des paramètres à la métaclasse au moment de l’instanciation de la classe qu’elle génère ?

    La métaclasse doit être invisible à l’usage de la classe générée.

    On ne passe donc rien aux métaclasse, on passe tout en paramètre à la classe, comme si c’était elle qui avait le comportement, même si ce comportement vient de la métaclasse.

    Donc si tu veux passer quelque chose à la métaclasse, passe le à la classe, et la métaclasse le récupérera au moment de l’interception.

  • Jc

    Juste excellent tant dans la pédagogie ou de la démarche explicative que de l’humour bien trouvé. Merci beaucoup ça a mis beaucoup d’ordre dans mon esprit par rapport aux differentes “recettes” que l’on peut découvrir : soit par metaclass fonction ou classe ou bien utilisation tantôt de call tantôt de new dont j’avais de la peine à voir la cohérence. Merci encore

  • orwel

    Navré pour le niveau de ma remarque :

    Et je sais que certains auraient préféré MyLittlePoney, mais je fais avec ma maigre culture G.

    Pony pas poney

    Sinon très bon article, j’ai l’impression d’avoir compris.

  • guigui

    Je suis en python3.6, j’ai reussi à faire une metaclasse qui marche quand je l’appelle directement, en revanche je n’y arrive depuis une autre classe avec l’attribut __metaclass__.

    En situation d’echec cuisant, j’ai décidé de copier coller le code, en utilisant la metaclass/fonction prefixer et la classe Ronflex.

    Ben ca marche pas ! r.armure() il trouve mais pas r.attaque_armure()

    Tout ca pour m’appercevoir que en Python non préhistorique on charge la metaclasse comme ca:

    class Ronflex(metaclass=prefixer):

    pass

    Sinon ce serait possible un support pour le markdown ? Je galère un peu avec l’html pur..

  • Sam Post author

    De nombreux articles meritent une mise a jour sur le site. Pour le markdown dans les comments, je vais voir.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.