6618 votes

Que sont les métaclasses en Python ?

En Python, que sont les métaclasses et à quoi servent-elles ?

7857voto

e-satis Points 146299

Les classes en tant qu'objets

Avant de comprendre les métaclasses, vous devez maîtriser les classes en Python. Et Python a une idée très particulière de ce que sont les classes, empruntée au langage Smalltalk.

Dans la plupart des langages, les classes ne sont que des morceaux de code qui décrivent comment produire un objet. C'est un peu vrai en Python aussi :

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

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Mais les classes sont plus que cela en Python. Les classes sont aussi des objets.

Oui, des objets.

Dès que vous utilisez le mot-clé class Python l'exécute et crée un OBJET. L'instruction

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

crée en mémoire un objet avec le nom "ObjectCreator".

Cet objet (la classe) est lui-même capable de créer des objets (les instances), et c'est pourquoi c'est une classe .

Mais quand même, c'est un objet, et donc :

  • vous pouvez l'assigner à une variable
  • vous pouvez le copier
  • vous pouvez y ajouter des attributs
  • vous pouvez le passer comme un paramètre de fonction

Par exemple :

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Créer des classes de façon dynamique

Les classes étant des objets, vous pouvez les créer à la volée, comme n'importe quel objet.

Tout d'abord, vous pouvez créer une classe dans une fonction en utilisant class :

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Mais ce n'est pas si dynamique, car vous devez toujours écrire vous-même la classe entière.

Puisque les classes sont des objets, elles doivent être générées par quelque chose.

Lorsque vous utilisez le class Python crée automatiquement cet objet. Mais comme avec la plupart des choses en Python, il vous donne un moyen de le faire manuellement.

Rappelez-vous la fonction type ? La bonne vieille fonction qui vous permet de savoir quel type d'un objet :

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

Bien, type a une capacité complètement différente, il peut aussi créer des classes à la volée. type peut prendre la description d'une classe comme paramètres, et retourner une classe.

(Je sais, c'est idiot que la même fonction puisse avoir deux utilisations complètement différentes en fonction des paramètres que vous lui passez. C'est un problème dû à la rétrocompatibilité compatibilité en Python)

type fonctionne de cette façon :

type(name, bases, attrs)

Où :

  • name : nom de la classe
  • bases : tuple de la classe parent (pour l'héritage, peut être vide)
  • attrs : dictionnaire contenant les noms et les valeurs des attributs

Par exemple :

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

peuvent être créés manuellement de cette manière :

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Vous remarquerez que nous utilisons "MyShinyClass" comme nom de la classe et comme variable pour contenir la référence de la classe. Ils peuvent être différents, mais il n'y a aucune raison de compliquer les choses.

type accepte un dictionnaire pour définir les attributs de la classe. Ainsi :

>>> class Foo(object):
...       bar = True

Peut être traduit en :

>>> Foo = type('Foo', (), {'bar':True})

Et utilisé comme une classe normale :

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

Et bien sûr, vous pouvez en hériter, donc.. :

>>>   class FooChild(Foo):
...         pass

serait :

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Éventuellement, vous voudrez ajouter des méthodes à votre classe. Il suffit de définir une fonction avec la signature appropriée et de l'assigner comme attribut.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

Et vous pouvez ajouter encore plus de méthodes après avoir créé dynamiquement la classe, tout comme vous ajoutez des méthodes à un objet de classe normalement créé.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Vous voyez où nous voulons en venir : en Python, les classes sont des objets, et vous pouvez créer une classe à la volée, de façon dynamique.

C'est ce que fait Python lorsque vous utilisez le mot-clé class et il le fait en utilisant une métaclasse.

Que sont les métaclasses (enfin)

Les métaclasses sont le "matériel" qui crée les classes.

Vous définissez des classes afin de créer des objets, n'est-ce pas ?

Mais nous avons appris que les classes Python sont des objets.

Ce sont les métaclasses qui créent ces objets. Elles sont les classes des classes, vous pouvez les imaginer de cette façon :

MyClass = MetaClass()
my_object = MyClass()

Vous avez vu que type vous permet de faire quelque chose comme ça :

MyClass = type('MyClass', (), {})

C'est parce que la fonction type est en fait une métaclasse. type est la que Python utilise pour créer toutes les classes en coulisse.

Maintenant, vous vous demandez pourquoi il est écrit en minuscules, et non pas Type ?

Eh bien, je suppose que c'est une question de cohérence avec str la classe qui crée les objets des objets chaînes de caractères, et int la classe qui crée les objets entiers. type est juste la classe qui crée les objets de classe.

Vous voyez qu'en vérifiant le __class__ attribut.

Tout, et je dis bien tout, est un objet en Python. Cela inclut les ints, les chaînes de caractères, les fonctions et les classes. Tous sont des objets. Et tous ces objets ont été créés à partir d'une classe :

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Maintenant, quel est le __class__ de tout __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Donc, une métaclasse est juste le truc qui crée des objets de classe.

Vous pouvez l'appeler une "fabrique de classes" si vous le souhaitez.

type est la métaclasse intégrée que Python utilise, mais bien sûr, vous pouvez créer votre propre métaclasse.

Le site __metaclass__ attribut

En Python 2, vous pouvez ajouter une fonction __metaclass__ lorsque vous écrivez une classe (voir la section suivante pour la syntaxe de Python 3) :

class Foo(object):
    __metaclass__ = something...
    [...]

Si vous le faites, Python utilisera la métaclasse pour créer la classe Foo .

Attention, c'est délicat.

Vous écrivez class Foo(object) d'abord, mais l'objet de classe Foo n'est pas encore créé dans la mémoire.

Python cherchera __metaclass__ dans la définition de la classe. S'il la trouve, il l'utilisera pour créer la classe d'objets Foo . Si ce n'est pas le cas, il utilisera type pour créer la classe.

Lisez ça plusieurs fois.

Quand vous le faites :

class Foo(Bar):
    pass

Python fait ce qui suit :

Y a-t-il un __metaclass__ l'attribut dans Foo ?

Si oui, créez en mémoire un objet de classe (j'ai dit un objet de classe, restez avec moi ici), avec le nom Foo en utilisant ce qui est dans __metaclass__ .

Si Python ne peut pas trouver __metaclass__ il cherchera un __metaclass__ au niveau du MODULE, et essayer de faire la même chose (mais seulement pour les classes qui n'héritent de rien, en gros les classes à l'ancienne).

Ensuite, s'il ne trouve pas de __metaclass__ du tout, il utilisera le Bar La métaclasse propre à l'utilisateur (le premier parent) (qui pourrait être la métaclasse par défaut type ) pour créer l'objet classe.

Faites attention ici que le __metaclass__ ne sera pas hérité, la métaclasse du parent ( Bar.__class__ ) le sera. Si Bar a utilisé un __metaclass__ qui a créé Bar avec type() (et non type.__new__() ), les sous-classes n'hériteront pas de ce comportement.

Maintenant la grande question est, que pouvez-vous mettre dans __metaclass__ ?

La réponse est quelque chose qui peut créer une classe.

Et qu'est-ce qui peut créer une classe ? type ou tout ce qui le sous-classe ou l'utilise.

Métaclasses en Python 3

La syntaxe pour définir la métaclasse a été modifiée dans Python 3 :

class Foo(object, metaclass=something):
    ...

c'est-à-dire le __metaclass__ n'est plus utilisé, au profit d'un argument mot-clé dans la liste des classes de base.

Le comportement des métaclasses reste cependant en grande partie les mêmes .

Une chose ajoutée aux métaclasses dans Python 3 est que vous pouvez également passer des attributs comme arguments de mot-clé dans une métaclasse, comme ceci :

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Lisez la section ci-dessous pour savoir comment python gère cela.

Métaclasses personnalisées

Le but principal d'une métaclasse est de changer automatiquement la classe lorsqu'elle est créée.

Vous faites généralement cela pour les API, où vous voulez créer des classes correspondant à la contexte actuel.

Imaginez un exemple stupide, où vous décidez que toutes les classes de votre module doivent avoir leurs attributs écrits en majuscules. Il existe plusieurs façons de de le faire, mais l'une d'entre elles consiste à définir __metaclass__ au niveau du module.

De cette façon, toutes les classes de ce module seront créées en utilisant cette métaclasse, et nous devons juste dire à la métaclasse de mettre tous les attributs en majuscules.

Heureusement, __metaclass__ peut en fait être n'importe quel appelable, il n'a pas besoin d'être une classe formelle (je sais, quelque chose avec 'class' dans son nom n'a pas besoin d'être une classe, allez savoir... mais c'est utile).

Nous allons donc commencer par un exemple simple, en utilisant une fonction.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """
    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

Vérifions :

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Maintenant, faisons exactement la même chose, mais en utilisant une vraie classe pour une métaclasse :

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Réécrivons ce qui précède, mais avec des noms de variables plus courts et plus réalistes maintenant que nous savons ce qu'ils signifient :

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Vous avez peut-être remarqué l'argument supplémentaire cls . Il n'y a rien de spécial à ce sujet : __new__ reçoit toujours la classe dans laquelle elle est définie, comme premier paramètre. Tout comme vous avez self pour les méthodes ordinaires qui reçoivent l'instance comme premier paramètre, ou la classe de définition pour les méthodes de classe.

Mais ce n'est pas une bonne méthode de fonctionnement opérationnel. Nous appelons type directement et nous ne surchargeons pas ou n'appelons pas la méthode d'évaluation du parent. __new__ . Faisons plutôt cela :

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

Nous pouvons le rendre encore plus propre en utilisant super ce qui facilitera l'héritage (car oui, vous pouvez avoir des métaclasses, héritant de métaclasses, héritant de type) :

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Oh, et dans python 3 si vous faites cet appel avec des arguments de mots-clés, comme ceci :

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

Il se traduit par ceci dans la métaclasse pour l'utiliser :

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

C'est ça. Il n'y a vraiment rien de plus à propos des métaclasses.

La raison derrière la complexité du code utilisant les métaclasses n'est pas à cause des métaclasses, c'est parce que vous utilisez généralement les métaclasses pour faire des trucs tordus en s'appuyant sur l'introspection, en manipulant l'héritage, les variables telles que __dict__ etc.

En effet, les métaclasses sont particulièrement utiles pour faire de la magie noire, et donc des choses compliquées. Mais en elles-mêmes, elles sont simples :

  • intercepter une création de classe
  • modifier la classe
  • retourne la classe modifiée

Pourquoi utiliser des classes de métaclasses au lieu de fonctions ?

Depuis __metaclass__ peut accepter n'importe quel callable, pourquoi utiliser une classe puisque c'est évidemment plus compliqué ?

Il y a plusieurs raisons de le faire :

  • L'intention est claire. Quand vous lisez UpperAttrMetaclass(type) vous savez ce qui va suivre
  • Vous pouvez utiliser OOP. Les métaclasses peuvent hériter de métaclasses, remplacer les méthodes des parents. Les métaclasses peuvent même utiliser des métaclasses.
  • Les sous-classes d'une classe seront des instances de sa métaclasse si vous avez spécifié une métaclasse-classe, mais pas avec une métaclasse-fonction.
  • Vous pouvez mieux structurer votre code. On n'utilise jamais les métaclasses pour quelque chose d'aussi trivial que l'exemple ci-dessus. C'est généralement pour quelque chose de compliqué. Avoir la possibilité de faire plusieurs méthodes et de les regrouper dans une classe est très utile pour rendre le code plus facile à lire.
  • Vous pouvez accrocher __new__ , __init__ et __call__ . Ce qui vous permettra de faire des choses différentes, même si d'habitude vous pouvez faire tout cela en __new__ , certaines personnes sont simplement plus à l'aise en utilisant __init__ .
  • On appelle ça des métaclasses, bon sang ! Ça doit vouloir dire quelque chose !

Pourquoi utiliser des métaclasses ?

Maintenant la grande question. Pourquoi utiliseriez-vous une obscure fonctionnalité sujette aux erreurs ?

Eh bien, en général, non :

Les métaclasses sont une magie plus profonde qui 99% des utilisateurs ne devraient jamais s'en préoccuper. Si vous vous demandez si vous en avez besoin, vous ne le faites pas (les personnes qui ont réellement ont besoin d'elles savent avec certitude qu'elles qu'ils en ont besoin et n'ont pas besoin d'une d'une explication sur le pourquoi).

Tim Peters, gourou de Python

Le principal cas d'utilisation d'une métaclasse est la création d'une API. L'ORM de Django en est un exemple typique. Il vous permet de définir quelque chose comme ceci :

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Mais si tu fais ça :

person = Person(name='bob', age='35')
print(person.age)

Il ne retournera pas un IntegerField objet. Il retournera un int et peut même les prendre directement dans la base de données.

Cela est possible parce que models.Model définit __metaclass__ et il utilise une certaine magie qui va transformer le Person que vous venez de définir avec de simples déclarations en un crochet complexe vers un champ de la base de données.

Django fait paraître simple quelque chose de complexe en exposant une API simple. et en utilisant des métaclasses, recréant le code de cette API pour faire le vrai travail dans les coulisses.

Le dernier mot

Tout d'abord, vous savez que les classes sont des objets qui peuvent créer des instances.

En fait, les classes sont elles-mêmes des instances. De métaclasses.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Tout est un objet en Python, et ils sont tous soit des instances de classes soit des instances de métaclasses.

Sauf pour type .

type est en fait sa propre métaclasse. Ce n'est pas quelque chose que vous pourriez reproduire en Python pur, et c'est fait en trichant un peu au niveau de l'implémentation. d'implémentation.

Deuxièmement, les métaclasses sont compliquées. Vous ne voudrez peut-être pas les utiliser pour pour des modifications de classes très simples. Vous pouvez modifier les classes en utilisant deux techniques différentes :

Dans 99% des cas où vous avez besoin d'une altération de classe, il est préférable d'utiliser celles-ci.

Mais dans 98 % des cas, il n'est pas nécessaire de modifier la classe du tout.

50 votes

Il semble que dans Django models.Model il n'utilise pas __metaclass__ mais plutôt class Model(metaclass=ModelBase): pour faire référence à un ModelBase qui effectue ensuite la magie de la métaclasse susmentionnée. Excellent article ! Voici les sources de Django : github.com/django/django/blob/master/django/db/models/

0 votes

Et si indiquer différent métaclasse dans la classe de base et dans la classe dérivée ?

25 votes

<<Faites attention ici que le __metaclass__ ne sera pas hérité, la métaclasse du parent ( Bar.__class__ ) sera. Si Bar a utilisé un __metaclass__ qui a créé Bar con type() (et non type.__new__() ), les sous-classes n'hériteront pas de ce comportement.>> -- Pourriez-vous expliquer un peu plus ce passage ?

3341voto

Thomas Wouters Points 38811

Une métaclasse est la classe d'une classe. Une classe définit le comportement d'une instance de la classe (c'est-à-dire un objet) tandis qu'une métaclasse définit le comportement d'une classe. Une classe est une instance d'une métaclasse.

Alors qu'en Python vous pouvez utiliser des callables arbitraires pour les métaclasses (comme Jerub ), la meilleure approche est d'en faire une classe à part entière. type est la métaclasse habituelle en Python. type est elle-même une classe, et elle est son propre type. Vous ne serez pas en mesure de recréer quelque chose comme type purement en Python, mais Python triche un peu. Pour créer votre propre métaclasse en Python, il vous suffit de sous-classer type .

Une métaclasse est le plus souvent utilisée comme une fabrique de classes. Lorsque vous créez un objet en appelant la classe, Python crée une nouvelle classe (lorsqu'il exécute l'instruction 'class') en appelant la métaclasse. Combiné avec l'instruction normale __init__ et __new__ les métaclasses permettent donc de faire des "choses supplémentaires" lors de la création d'une classe, comme l'enregistrement de la nouvelle classe dans un registre ou le remplacement de la classe par quelque chose d'entièrement différent.

Lorsque le class est exécutée, Python exécute d'abord le corps de l'instruction class comme un bloc de code normal. L'espace de nom résultant (un dict) contient les attributs de la classe à venir. La métaclasse est déterminée en regardant les classes de base de la classe à venir (les métaclasses sont héritées), à la balise __metaclass__ de la classe à venir (le cas échéant) ou l'attribut __metaclass__ variable globale. La métaclasse est ensuite appelée avec le nom, les bases et les attributs de la classe pour l'instancier.

Cependant, les métaclasses définissent en fait le type d'une classe, et pas seulement d'une fabrique pour celle-ci, de sorte que vous pouvez faire beaucoup plus avec eux. Vous pouvez, par exemple, définir des méthodes normales sur la métaclasse. Ces métaclasses-méthodes sont comme des classes-méthodes dans le sens où elles peuvent être appelées sur la classe sans instance, mais elles ne sont pas non plus comme des classes-méthodes dans le sens où elles ne peuvent pas être appelées sur une instance de la classe. type.__subclasses__() est un exemple de méthode sur le type métaclasse. Vous pouvez également définir les méthodes "magiques" normales, telles que __add__ , __iter__ et __getattr__ pour mettre en œuvre ou modifier le comportement de la classe.

Voici un exemple agrégé des morceaux :

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType

class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

17 votes

class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b

31 votes

Ppperry il a évidemment voulu dire que vous ne pouvez pas recréer le type sans utiliser le type lui-même comme une métaclasse. Ce qui est assez juste de dire.

4 votes

Unregister() ne devrait-il pas être appelé par une instance de la classe Exemple ?

454voto

Jerub Points 17488

Remarque : cette réponse concerne Python 2.x car elle a été écrite en 2008. Les métaclasses sont légèrement différentes en 3.x.

Les métaclasses sont la sauce secrète qui fait fonctionner les "classes". La métaclasse par défaut pour un nouvel objet de style est appelée "type".

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Les métaclasses prennent 3 arguments. ' nom ', ' bases et dict '

C'est ici que le secret commence. Cherchez d'où viennent le nom, les bases et le dict dans cet exemple de définition de classe.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Définissons une métaclasse qui démontrera comment ' classe : appelle ça.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

Et maintenant, un exemple qui signifie réellement quelque chose, ceci rendra automatiquement les variables de la liste "attributs" définies sur la classe, et définies à None.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Notez que le comportement magique que Initialised en ayant la métaclasse init_attributes n'est pas transmis à une sous-classe de Initialised .

Voici un exemple encore plus concret, montrant comment vous pouvez sous-classer 'type' pour créer une métaclasse qui exécute une action lorsque la classe est créée. C'est assez délicat :

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

class Foo(object):
    __metaclass__ = MetaSingleton

a = Foo()
b = Foo()
assert a is b

208voto

kindall Points 60645

D'autres ont expliqué comment les métaclasses fonctionnent et comment elles s'intègrent dans le système de types de Python. Voici un exemple de ce à quoi elles peuvent servir. Dans un cadre de test que j'ai écrit, je voulais garder la trace de l'ordre dans lequel les classes étaient définies, afin de pouvoir les instancier plus tard dans cet ordre. J'ai trouvé que le plus simple était de le faire en utilisant une métaclasse.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Tout ce qui est une sous-classe de MyType obtient alors un attribut de classe _order qui enregistre l'ordre dans lequel les classes ont été définies.

0 votes

Merci pour l'exemple. Pourquoi avez-vous trouvé cela plus facile que d'hériter de MyBase, dont le __init__(self) dit type(self)._order = MyBase.counter; MyBase.counter += 1 ?

4 votes

Je voulais que les classes elles-mêmes, et non leurs instances, soient numérotées.

0 votes

C'est vrai, duh. Merci. Mon code réinitialisait l'attribut de MyType à chaque instanciation, et ne définissait jamais l'attribut si une instance de MyType n'était jamais créée. Oups. (Et une propriété de classe pourrait également fonctionner, mais contrairement à la métaclasse, elle n'offre aucun endroit évident pour stocker le compteur).

187voto

Antti Rasinen Points 2837

Une des utilisations des métaclasses est l'ajout automatique de nouvelles propriétés et méthodes à une instance.

Par exemple, si vous regardez Modèles Django leur définition semble un peu confuse. On dirait que vous ne définissez que les propriétés des classes :

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Cependant, au moment de l'exécution, les objets Personne sont remplis de toutes sortes de méthodes utiles. Voir le source pour une métaclasse étonnante.

9 votes

L'utilisation de méta-classes ne consiste-t-elle pas à ajouter de nouvelles propriétés et méthodes à un système de gestion de l'information ? classe et non une instance ? D'après ce que j'ai compris, la méta-classe modifie la classe elle-même et, par conséquent, les instances peuvent être construites différemment par la classe modifiée. Cela pourrait être un peu trompeur pour les personnes qui essaient de comprendre la nature d'une méta classe. Avoir des méthodes utiles sur les instances peut être réalisé par une inhérence normale. La référence au code de Django comme exemple est bonne, cependant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X