15 Question: Que sont les métaclasses en Python?

question créée à Thu, Jan 11, 2018 12:00 AM

Que sont les métaclasses et comment les utilisons-nous?

    
5184
15 réponses                              15                         

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.

Pendant que vous êtes en Python, vous pouvez utiliser des callables arbitraires pour les métaclasses (comme Jerub montre), la meilleure approche consiste à en faire une classe elle-même. type est la métaclasse habituelle en Python. type est lui-même une classe et c'est son propre type. Vous ne pourrez pas recréer quelque chose comme type uniquement en Python, mais Python triche un peu. Pour créer votre propre métaclasse en Python, vous voulez simplement placer la sous-classe type.

Une métaclasse est le plus souvent utilisée comme classe-fabrique. 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ées aux méthodes normales __init__ et __new__, les métaclasses vous permettent donc de faire des choses supplémentaires lors de la création d'une classe, comme enregistrer la nouvelle classe dans un registre ou remplacer la classe par quelque chose d'autre.

Lorsque l'instruction class est exécutée, Python exécute d'abord le corps de l'instruction class en tant que bloc de code normal. L'espace de nommage résultant (un dict) contient les attributs de la classe à venir. La métaclasse est déterminée en examinant les classes de base de la classe à venir (les métaclasses sont héritées), à l'attribut __metaclass__ de la variable de classe à être (le cas échéant) ou __metaclass__. 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 réalité le type d'une classe, pas seulement une fabrique pour elle, vous pouvez donc en faire beaucoup plus. Vous pouvez, par exemple, définir des méthodes normales sur la métaclasse. Ces méthodes de métaclasse sont comme des méthodes de classe en ce sens qu'elles peuvent être appelées sur la classe sans instance, mais elles ne sont pas non plus comme des méthodes de classe en ce sens qu'elles ne peuvent pas être appelées sur une instance de la classe. type.__subclasses__() est un exemple de méthode sur la métaclasse type. Vous pouvez également définir les méthodes "magiques" normales, telles que __add__, __iter__ et __getattr__, pour implémenter ou modifier le comportement de la classe.

Voici un exemple agrégé des éléments suivants:

 
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__
    
2512
2019-03-04 21: 34: 19Z
  1. class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
    2017-08-03 14: 34: 29Z
  2. ppperry voulait évidemment dire que vous ne pouvez pas recréer le type sans utiliser le type lui-même comme métaclasse. Ce qui est juste à dire.
    2018-09-18 23: 24: 10Z
  3. Unregister () ne devrait-il pas être appelé par une instance de la classe Example?
    2018-11-29 00: 59: 15Z
  4. Notez que __metaclass__ n'est pas pris en charge dans Python 3. Dans Python 3, utilisez class MyObject(metaclass=MyType), voir python.org/dev/peps/pep-3115 et la réponse ci-dessous.
    2019-05-01 08: 36: 00Z

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ées au langage Smalltalk.

Dans la plupart des langages, les classes ne sont que des éléments de code décrivant comment produire un objet. C’est vrai aussi en Python:

 
>>> 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, les objets.

Dès que vous utilisez le mot clé class, Python l'exécute et créeun objet. L'instruction

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

crée en mémoire un objet nommé "ObjectCreator".

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

Mais ça reste un objet, et donc:

  • vous pouvez l'assigner à une variable
  • vous pouvez le copier
  • vous pouvez y ajouter des attributs
  • vous pouvez le transmettre en tant que paramètre de fonction

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éation dynamique de classes

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 à l'aide de 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.

Les classes étant des objets, elles doivent être générées par quelque chose.

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

Rappelez-vous la fonction type? La bonne vieille fonction qui vous permet de savoir quoi tapez un objet est:

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

Eh 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 en tant que paramètres, et retourner une classe.

(Je sais, c’est idiot que la même fonction puisse avoir deux utilisations complètement différentes selon les paramètres que vous lui transmettez. C’est un problème dû à l’arrière-plan. compatibilité en Python)

type fonctionne comme suit:

 
type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

exemple:

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

peut être créé manuellement de cette façon:

 
>>> 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 classe et comme variable pour contenir la référence de 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. Donc:

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

Peut être traduit en:

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

Et utilisé comme 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

Vous voudrez éventuellement ajouter des méthodes à votre classe. Juste définir une fonction avec la signature appropriée et 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 la création dynamique de la classe, comme si vous ajoutiez des méthodes à un objet de classe créé normalement.

 
>>> 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 allons: en Python, les classes sont des objets et vous pouvez créer une classe à la volée, de manière dynamique.

C’est ce que fait Python lorsque vous utilisez le mot clé class, à l’aide d’une métaclasse.

Que sont les métaclasses (enfin)

Les métaclasses sont la "substance" qui crée les classes.

Vous définissez des classes afin de créer des objets, non?

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

Eh bien, ce sont les métaclasses qui créent ces objets. Ce sont les cours 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 ceci:

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

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

Vous vous demandez maintenant pourquoi diable est-il écrit en minuscule et non pas Type?

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

Vous voyez cela en vérifiant l'attribut __class__.

Tout, et je veux dire tout, est un objet en Python. Cela comprend les ints, chaînes, fonctions et classes. Tous sont des objets. Et tous ont été créé à 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__ d'un __class__?

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

Ainsi, une métaclasse est ce 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 utilisée par Python, mais vous pouvez bien sûr créer votre propre métaclasse.

Attribut __metaclass__

En Python 2, vous pouvez ajouter un attribut __metaclass__ lorsque vous écrivez une classe (voir la section suivante pour la syntaxe 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 d'abord class Foo(object), mais l'objet de classe Foo n'est pas créé en mémoire pour le moment.

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

Lisez-le plusieurs fois.

Quand vous le faites:

 
class Foo(Bar):
    pass

Python effectue les opérations suivantes:

Y at-il un attribut __metaclass__ dans Foo?

Si oui, créez enmemory 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 trouve pas __metaclass__, il recherchera un __metaclass__ au niveau MODULE et tentera de faire de même (mais uniquement pour les classes qui n'héritent de rien, essentiellement des classes de style ancien).

S'il ne trouve pas du tout de __metaclass__, il utilisera sa propre métaclasse (le premier parent) (qui peut être la valeur par défaut de Bar) pour créer l'objet de classe.

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

Maintenant, la grande question est: que pouvez-vous mettre dans type.__new__()?

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

Et qu'est-ce qui peut créer une classe? __metaclass__, 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:

 type

i.e. l'attribut

class Foo(object, metaclass=something):
    ...
n'est plus utilisé, en faveur d'un argument de mot clé dans la liste des classes de base.

Le comportement des métaclasses reste toutefois sensiblement le même .

Une chose ajoutée aux métaclasses dans Python 3 est que vous pouvez également transmettre des attributs en tant qu'arguments mot-clé dans une métaclasse, comme suit:

 __metaclass__

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 la classe automatiquement, quand il est créé.

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

Imaginez un exemple stupide, dans lequel vous décidez que toutes les classes de votre module devraient avoir leurs attributs écrits en majuscules. Il y a plusieurs façons de Pour ce faire, une solution consiste à définir

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...
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 transformer tous les attributs en majuscules.

Heureusement, __metaclass__ peut en réalité être appelé, il n’est pas nécessaire que ce soit un classe formelle (je sais, quelque chose avec «classe» dans son nom n'a pas besoin d'être une classe, allez comprendre ... mais c'est utile).

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

 __metaclass__

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

 
# 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_attr):
    """
      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_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

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

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

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Mais ce n’est pas vraiment la POO. Nous appelons le

# 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_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)
directement et nous ne substituons pas ou appelez le parent type. Faisons-le:  __new__

Vous avez peut-être remarqué l'argument supplémentaire

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)
. Il y a rien de spécial à ce sujet: upperattr_metaclass reçoit toujours la classe dans laquelle il est défini, en tant que premier paramètre. Tout comme vous avez __new__ 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.

Bien sûr, les noms que j'ai utilisés ici sont longs pour des raisons de clarté, mais comme pour self, tous les arguments ont des noms conventionnels. Donc une vraie production la métaclasse ressemblerait à ceci:

 self

Nous pouvons le rendre encore plus propre en utilisant

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)
, ce qui facilitera l'héritage (car oui, vous pouvez avoir des métaclasses, héritant de métaclasses, héritant de type):  super

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

 
class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

Cela se traduit dans la métaclasse de l'utiliser:

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

C'est ça. Il n’ya vraiment rien de plus sur les métaclasses.

La complexité du code utilisant des métaclasses n’est pas due au fait que des métaclasses, c'est parce que vous utilisez habituellement des métaclasses pour faire des choses tordues en s’appuyant sur l’introspection, la manipulation de l’héritage, des vars comme

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

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

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

Pourquoi voudriez-vous utiliser les classes métaclasses au lieu de fonctions?

Puisque __dict__ peut accepter n'importe quel appelable, pourquoi voudriez-vous utiliser une classe? puisque c'est évidemment plus compliqué?

Il y a plusieurs raisons de le faire:

  • L'intention est claire. Quand vous lisez __metaclass__, vous savez qu'est-ce qui va suivre
  • Vous pouvez utiliser la POO. La métaclasse peut hériter de la métaclasse et remplacer les méthodes parent. 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 classe de métaclasse, mais pas avec une fonction de métaclasse.
  • Vous pouvez mieux structurer votre code. Vous n’utilisez jamais de métaclasse pour quelque chose comme trivial comme l'exemple ci-dessus. C'est généralement pour quelque chose de compliqué. Avoir le la capacité de créer plusieurs méthodes et de les regrouper dans une même classe est très utile pour faciliter la lecture du code.
  • YVous pouvez accrocher les UpperAttrMetaclass(type), __new__ et __init__. Ce qui permettra vous faire des choses différentes. Même si d’habitude vous pouvez tout faire en __call__, Certaines personnes sont simplement plus à l'aise avec __new__.
  • Ce sont des métaclasses, bon Dieu! Cela doit vouloir dire quelque chose!

Pourquoi utiliseriez-vous des métaclasses?

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

Eh bien, généralement vous ne le faites pas:

  

Les métaclasses sont une magie plus profonde que   99% des utilisateurs ne devraient jamais s'inquiéter.   Si vous vous demandez si vous en avez besoin,   vous ne faites pas (les gens qui réellement   besoin d'eux savent avec certitude que   ils ont besoin d'eux et n'ont pas besoin d'un   explication sur pourquoi).

Tim Peters, gourou du python

Le principal cas d'utilisation d'une métaclasse est la création d'une API. L’exemple typique est l’ORM Django.

Cela vous permet de définir quelque chose comme ceci:

 __init__

Mais si vous faites ceci:

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

Il ne retournera pas d'objet

guy = Person(name='bob', age='35')
print(guy.age)
. Il renverra un IntegerField et pourra même le prendre directement à partir de la base de données.

Ceci est possible car int définit models.Model et il utilise un peu de magie qui transformera le __metaclass__ que vous venez de définir avec des instructions simples dans un crochet complexe à un champ de base de données.

Django simplifie l'aspect de quelque chose de complexe en exposant une API simple et en utilisant des métaclasses, recréer le code à partir 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 pouvant créer des instances.

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

 Person

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

Sauf pour

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

type est en fait sa propre métaclasse. Ce n'est pas quelque chose que vous pourriez reproduire en pur Python, et se fait en trompant un peu à la mise en œuvre niveau.

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

99% du temps vous avez besoin d'une modification de classe, vous feriez mieux de les utiliser.

Mais 98% du temps, vous n’avez absolument pas besoin de changer de classe.

    
6304
2019-04-16 17: 55: 24Z
  1. Il semble que dans Django type, il n'utilise pas models.Model mais plutôt __metaclass__ pour faire référence à une classe class Model(metaclass=ModelBase): qui effectue ensuite la magie de métaclasse susmentionnée. Super article! Voici la source Django: github.com/django /django /blob /master /django /db /models /…
    2017-04-12 13: 18: 59Z
  2. Que se passe-t-il si indiquer une métaclasse différente dans les classes de base et dérivées?
    2017-04-20 03: 24: 08Z
  3. < < Veillez à ce que l'attribut ModelBase ne soit pas hérité, la métaclasse du parent (__metaclass__) le sera. Si Bar.__class__ a utilisé un attribut Bar qui a créé __metaclass__ avec Bar (et non type()), les sous-classes n'hériteront pas de ce comportement. &Gt; > - Pourriez-vous /quelqu'un expliquer un peu plus ce passage?
    2017-04-25 21: 32: 44Z
  4. @ MaxGoodridge C'est la syntaxe Python 3 pour les métaclasses. Voir Modèle de données Python 3.6 . Modèle de données Python 2.7
    2017-06-13 13: 22: 27Z
  5. C'est une réponse du wiki de la communauté (donc, ceux qui ont commenté avec des corrections /améliorations pourraient envisager de modifier leurs commentaires dans la réponse, s'ils sont sûrs qu'ils sont corrects).
    2017-11-08 08: 59: 31Z

    Notez que cette réponse concerne Python 2.x telle qu’elle avait é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 le travail de la classe. La métaclasse par défaut pour un nouvel objet de style s'appelle 'type'.

     type.__new__()

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

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

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

    Définissons une métaclasse qui montrera comment " classe: " l'appelle.

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

    Et maintenant, un exemple qui signifie quelque chose, les variables de la liste "attributs" seront automatiquement définies dans la classe et définies sur Aucune.

     
    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'
    

    Notez que le comportement magique qu'Initalised gagne en ayant la métaclasse

    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
    
    n'est pas transmis à une sous-classe de Initalised.

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

     init_attributes     
    359
    2019-05-13 07: 55: 44Z

    L'une des utilisations des métaclasses consiste à ajouter automatiquement de nouvelles propriétés et méthodes à une instance.

    Par exemple, si vous consultez les modèles Django , leurs modèles définition semble un peu déroutant. Il semble que vous ne définissiez que les propriétés de la classe:

     
    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
    

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

    146
    2008-09-19 06: 45: 40Z
    1. L'utilisation de méta-classes ne permet-elle pas d'ajouter de nouvelles propriétés et méthodes à une classe et non à une instance? Autant que je sache, 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. Pourrait être un peu trompeur pour les personnes qui essaient d'obtenir la nature d'une méta-classe. Avoir des méthodes utiles sur les instances peut être obtenu par une inhérence normale. La référence au code Django en tant qu'exemple est bonne, cependant.
      2017-01-27 23: 24: 42Z

    D'autres ont expliqué le fonctionnement des métaclasses et leur intégration dans le système de types Python. Voici un exemple de ce qu'ils peuvent être utilisés. Dans un framework de test que j'ai écrit, je voulais garder une trace de l'ordre dans lequel les classes étaient définies, afin de pouvoir les instancier ultérieurement dans cet ordre. J'ai trouvé le plus simple de le faire en utilisant une métaclasse.

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

    Tout ce qui est une sous-classe de

    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
    
    obtient alors un attribut de classe MyType qui enregistre l'ordre dans lequel les classes ont été définies.     
    146
    2016-11-28 18: 04: 41Z
    1. Merci pour l'exemple. Pourquoi avez-vous trouvé cela plus facile que d'hériter de MyBase, dont _order dit __init__(self)?
      2019-04-16 17: 59: 34Z
    2. Je voulais que les classes elles-mêmes, et non leurs instances, soient numérotées.
      2019-04-16 18: 09: 39Z
    3. Droite, duh. Merci. Mon code réinitialiserait l'attribut de MyType à chaque instanciation et ne le définirait jamais si une instance de MyType n'était jamais créée. Oops. (Et une propriété de classe pourrait également fonctionner, mais contrairement à la métaclasse, elle n’offre aucun endroit évident pour stocker le compteur.)
      2019-04-17 21: 58: 00Z

    Je pense que l’introduction d’ONLamp à la programmation de métaclasse est bien écrite et donne une très bonne introduction au sujet bien qu’elle ait déjà plusieurs années.

    http://www.onlamp.com/pub /a /python /2003/04/17 /metaclasses.html (archivé à https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a /python/2003/04/17/metaclasses.html )

    En bref: une classe est un schéma directeur pour la création d’une instance, une métaclasse est un schéma directeur pour la création d’une classe. On peut facilement voir que, dans Python, les classes doivent également être des objets de première classe pour activer ce comportement.

    Je n'en ai jamais écrit moi-même, mais je pense qu'une des plus belles utilisations des métaclasses est visible dans le framework Django. . Les classes de modèle utilisent une approche de métaclasse pour permettre un style déclaratif d'écriture de nouveaux modèles ou de classes de formulaire. Pendant que la métaclasse crée la classe, tous les membres ont la possibilité de personnaliser la classe elle-même.

    Ce qu'il reste à dire, c'est: si vous ne savez pas ce que sont les métaclasses, la probabilité que vous n'en ayez pas besoin soit de 99%.

        
    106
    2018-08-13 04: 53: 15Z
      

    Que sont les métaclasses? Pourquoi les utilisez-vous?

    TLDR: une métaclasse instancie et définit le comportement d'une classe de la même façon qu'une classe instancie et définit le comportement d'une instance.

    Pseudocode:

     type(self)._order = MyBase.counter; MyBase.counter += 1

    Ce qui précède devrait vous sembler familier. D'où vient

    >>> Class(...)
    instance
    
    ? C'est une instance d'une métaclasse (également pseudocode):  Class

    En code réel, nous pouvons passer la métaclasse par défaut,

    >>> Metaclass(...)
    Class
    
    , tout ce dont nous avons besoin pour instancier une classe et nous obtenons une classe:  type

    En d'autres termes

    • Une classe est à une instance comme une métaclasse est à une classe.

      Lorsque nous instancions un objet, nous obtenons une instance:

       
      >>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
      <class '__main__.Foo'>
      

      De même, lorsque nous définissons explicitement une classe avec la métaclasse par défaut,

      >>> object()                          # instantiation of class
      <object object at 0x7f9069b4e0b0>     # instance
      
      , nous l'instancions:  type
    • En d'autres termes, une classe est une instance d'une métaclasse:

       
      >>> type('Object', (object,), {})     # instantiation of metaclass
      <class '__main__.Object'>             # instance
      
    • En d'autres termes, une métaclasse est une classe.

       
      >>> isinstance(object, type)
      True
      

    Lorsque vous écrivez une définition de classe et que Python l'exécute, il utilise une métaclasse pour instancier l'objet de classe (qui, à son tour, sera utilisé pour instancier des instances de cette classe).

    Tout comme nous pouvons utiliser les définitions de classe pour modifier le comportement des instances d'objet personnalisé, nous pouvons utiliser une définition de classe de métaclasse pour modifier le comportement d'un objet de classe.

    À quoi peuvent-ils servir? docs :

      

    Les utilisations potentielles des métaclasses sont illimitées. Parmi les idées explorées figurent la journalisation, la vérification des interfaces, la délégation automatique, la création automatique de propriétés, les mandataires, les frameworks et le verrouillage /synchronisation automatique des ressources.

    Néanmoins, il est généralement recommandé aux utilisateurs d'éviter d'utiliser des métaclasses sauf en cas d'absolue nécessité.

    Vous utilisez une métaclasse chaque fois que vous créez une classe:

    Lorsque vous écrivez une définition de classe, par exemple, comme ceci,

     
    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

    Vous instanciez un objet de classe.

     
    class Foo(object): 
        'demo'
    

    Cela revient à appeler

    >>> Foo
    <class '__main__.Foo'>
    >>> isinstance(Foo, type), isinstance(Foo, object)
    (True, True)
    
    de manière fonctionnelle avec les arguments appropriés et à affecter le résultat à une variable de ce nom:  type

    Remarque, certaines choses sont automatiquement ajoutées au

    name = 'Foo'
    bases = (object,)
    namespace = {'__doc__': 'demo'}
    Foo = type(name, bases, namespace)
    
    , c'est-à-dire à l'espace de noms:  __dict__

    La métaclasse de l'objet créé est, dans les deux cas,

    >>> Foo.__dict__
    dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
    '__module__': '__main__', '__weakref__': <attribute '__weakref__' 
    of 'Foo' objects>, '__doc__': 'demo'})
    
    .

    (Une note sur le contenu de la classe type: __dict__ existe parce que les classes doivent savoir où elles sont définies et que __module__ et __dict__ le sont car nous ne définissons pas __weakref__ - si nous définir __slots__ nous allons économiser un peu d’espace dans les instances, car nous pouvons interdire __slots__ et __dict__ en les excluant. Par exemple:

     __weakref__

    ... mais je m'éloigne du sujet.)

    Nous pouvons étendre
    >>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
    >>> Baz.__dict__
    mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})
    
    comme n'importe quelle autre définition de classe:

    Voici le type classes par défaut:

     __repr__

    Une des choses les plus utiles que nous puissions faire par défaut en écrivant un objet Python est de lui fournir un bon

    >>> Foo
    <class '__main__.Foo'>
    
    . Lorsque nous appelons __repr__, nous apprenons qu’il existe un bon test pour un help(repr) qui requiert également un test d’égalité - __repr__. Les implémentations simples suivantes de obj == eval(repr(obj)) et __repr__ pour les instances de classe de notre classe type fournissent une démonstration susceptible d’améliorer les classes par défaut __eq__:  __repr__

    Alors maintenant, lorsque nous créons un objet avec cette métaclasse, le

    class Type(type):
        def __repr__(cls):
            """
            >>> Baz
            Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
            >>> eval(repr(Baz))
            Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
            """
            metaname = type(cls).__name__
            name = cls.__name__
            parents = ', '.join(b.__name__ for b in cls.__bases__)
            if parents:
                parents += ','
            namespace = ', '.join(': '.join(
              (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
                   for k, v in cls.__dict__.items())
            return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
        def __eq__(cls, other):
            """
            >>> Baz == eval(repr(Baz))
            True            
            """
            return (cls.__name__, cls.__bases__, cls.__dict__) == (
                    other.__name__, other.__bases__, other.__dict__)
    
    en écho sur la ligne de commande fournit une vision beaucoup moins laide que celle par défaut:  __repr__

    Avec un

    >>> class Bar(object): pass
    >>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
    >>> Baz
    Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
    
    bien défini pour l'instance de classe, nous avons une capacité plus grande de déboguer notre code. Cependant, une vérification beaucoup plus poussée avec __repr__ est peu probable (car les fonctions seraient plutôt impossibles à évaluer de leurs eval(repr(Class)) par défaut).

    Utilisation prévue: __repr__ un espace de noms

    Si, par exemple, nous voulons savoir dans quel ordre les méthodes d'une classe sont créées, nous pourrions fournir un dict ordonné en tant qu'espace de nom de la classe. Nous ferions cela avec __prepare__ qui retourne le dict de l'espace de noms pour classe si elle est implémentée dans Python 3 :

     __prepare__

    Et utilisation:

     
    from collections import OrderedDict
    
    class OrderedType(Type):
        @classmethod
        def __prepare__(metacls, name, bases, **kwargs):
            return OrderedDict()
        def __new__(cls, name, bases, namespace, **kwargs):
            result = Type.__new__(cls, name, bases, dict(namespace))
            result.members = tuple(namespace)
            return result
    

    Et nous avons maintenant un enregistrement de l'ordre dans lequel ces méthodes (et autres attributs de classe) ont été créés:

     
    class OrderedMethodsObject(object, metaclass=OrderedType):
        def method1(self): pass
        def method2(self): pass
        def method3(self): pass
        def method4(self): pass
    

    Remarque, cet exemple a été adapté de la documentation - le nouvel énumération de la bibliothèque standard le fait.

    Nous avons donc instancié une métaclasse en créant une classe. Nous pouvons également traiter la métaclasse comme n'importe quelle autre classe. Il a un ordre de résolution de méthode:

     
    >>> OrderedMethodsObject.members
    ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')
    

    Et il a approximativement le

    >>> inspect.getmro(OrderedType)
    (<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)
    
    correct (que nous ne pouvons plus évaluer si nous ne pouvons pas trouver un moyen de représenter nos fonctions.):  repr     
    93
    2017-08-30 03: 19: 14Z

    Mise à jour Python 3

    Il existe (à ce stade) deux méthodes clés dans une métaclasse:

    •  
      >>> OrderedMethodsObject
      OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
      hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
      ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
      
      et
    • __prepare__

    __new__ vous permet de fournir un mappage personnalisé (tel qu'un __prepare__) à utiliser comme espace de noms pendant la création de la classe. Vous devez renvoyer une instance de l’espace de nom de votre choix. Si vous n’implémentez pas OrderedDict, un __prepare__ normal est utilisé.

    dict est responsable de la création /modification effective de la classe finale.

    Une métaclasse dépourvue de sens et de style "rien à faire en plus" aimerait:

     __new__

    Un exemple simple:

    Supposons que vous souhaitiez que du code de validation simple soit exécuté sur vos attributs - comme s'il devait toujours s'agir d'un

    class Meta(type):
    
        def __prepare__(metaclass, cls, bases):
            return dict()
    
        def __new__(metacls, cls, bases, clsdict):
            return super().__new__(metacls, cls, bases, clsdict)
    
    ou d'un int. Sans métaclasse, votre classe ressemblerait à quelque chose comme:  str

    Comme vous pouvez le constater, vous devez répéter le nom de l'attribut deux fois. Cela rend possible les fautes de frappe avec des insectes irritants.

    Une simple métaclasse peut résoudre ce problème:

     
    class Person:
        weight = ValidateType('weight', int)
        age = ValidateType('age', int)
        name = ValidateType('name', str)
    

    Voici à quoi ressemblerait la métaclasse (sans utiliser

    class Person(metaclass=Validator):
        weight = ValidateType(int)
        age = ValidateType(int)
        name = ValidateType(str)
    
    car elle n'est pas nécessaire):  __prepare__

    Un exemple d’exécution de:

     
    class Validator(type):
        def __new__(metacls, cls, bases, clsdict):
            # search clsdict looking for ValidateType descriptors
            for name, attr in clsdict.items():
                if isinstance(attr, ValidateType):
                    attr.name = name
                    attr.attr = '_' + name
            # create final class and return it
            return super().__new__(metacls, cls, bases, clsdict)
    

    produit:

     
    p = Person()
    p.weight = 9
    print(p.weight)
    p.weight = '9'
    

    Remarque : cet exemple est assez simple, il aurait également pu être accompli avec un décorateur de classe, mais une métaclasse en ferait probablement beaucoup plus.

    La classe 'ValidateType' pour référence:

     
    9
    Traceback (most recent call last):
      File "simple_meta.py", line 36, in <module>
        p.weight = '9'
      File "simple_meta.py", line 24, in __set__
        (self.name, self.type, value))
    TypeError: weight must be of type(s) <class 'int'> (got '9')
    
        
    67
    2016-03-01 19: 48: 34Z

    Rôle d'une méthode
    class ValidateType:
        def __init__(self, type):
            self.name = None  # will be set by metaclass
            self.attr = None  # will be set by metaclass
            self.type = type
        def __get__(self, inst, cls):
            if inst is None:
                return self
            else:
                return inst.__dict__[self.attr]
        def __set__(self, inst, value):
            if not isinstance(value, self.type):
                raise TypeError('%s must be of type(s) %s (got %r)' %
                        (self.name, self.type, value))
            else:
                inst.__dict__[self.attr] = value
    
    d'une métaclasse lors de la création d'une instance de classe

    Si vous avez programmé Python pendant plus de quelques mois, vous finirez par tomber sur un code qui ressemble à ceci:

     __call__()

    Ce dernier est possible lorsque vous implémentez la méthode magique

    # define a class
    class SomeClass(object):
        # ...
        # some definition here ...
        # ...
    
    # create an instance of it
    instance = SomeClass()
    
    # then call the object as if it's a function
    result = instance('foo', 'bar')
    
    sur la classe.  __call__()

    La méthode

    class SomeClass(object):
        # ...
        # some definition here ...
        # ...
    
        def __call__(self, foo, bar):
            return bar + foo
    
    est invoquée lorsqu'une instance d'une classe est utilisée en tant qu'appelable. Mais comme nous l’avons vu à partir des réponses précédentes, une classe elle-même est une instance d’une métaclasse. Ainsi, lorsque nous utilisons cette classe comme appelable (c’est-à-dire lorsque nous en créons une instance), nous appelons sa méthode __call__(), la métaclasse. À ce stade, la plupart des programmeurs Python sont un peu confus, car on leur a dit que lors de la création d'une instance de ce type __call__() vous appelez sa méthode instance = SomeClass(). Certains qui ont creusé un peu plus profondément savent qu'avant __init__() il y en avait __init__(). Eh bien, aujourd’hui, une autre couche de vérité est révélée. Avant __new__(), il y avait la métaclasse '__new__().

    Étudions la chaîne d’appel à la méthode sous l’angle de la création d’une instance de classe.

    Il s'agit d'une métaclasse qui enregistre exactement le moment précédant la création d'une instance et le moment où elle est sur le point de la renvoyer.

     __call__()

    Ceci est une classe qui utilise cette métaclasse

     
    class Meta_1(type):
        def __call__(cls):
            print "Meta_1.__call__() before creating an instance of ", cls
            instance = super(Meta_1, cls).__call__()
            print "Meta_1.__call__() about to return instance."
            return instance
    

    Et maintenant, créons une instance de

    class Class_1(object):
    
        __metaclass__ = Meta_1
    
        def __new__(cls):
            print "Class_1.__new__() before creating an instance."
            instance = super(Class_1, cls).__new__(cls)
            print "Class_1.__new__() about to return instance."
            return instance
    
        def __init__(self):
            print "entering Class_1.__init__() for instance initialization."
            super(Class_1,self).__init__()
            print "exiting Class_1.__init__()."
    
     Class_1

    Notez que le code ci-dessus ne fait en réalité que consigner les tâches. Chaque méthode délègue le travail réel à l'implémentation de son parent, conservant ainsi le comportement par défaut. Puisque

    instance = Class_1()
    # Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
    # Class_1.__new__() before creating an instance.
    # Class_1.__new__() about to return instance.
    # entering Class_1.__init__() for instance initialization.
    # exiting Class_1.__init__().
    # Meta_1.__call__() about to return instance.
    
    est la classe parente de type (Meta_1 étant la métaclasse par défaut) et compte tenu de la séquence de classement du résultat ci-dessus, nous avons maintenant un indice sur ce que serait la pseudo implémentation de type:  type.__call__()

    Nous pouvons voir que la méthode

    class type:
        def __call__(cls, *args, **kwarg):
    
            # ... maybe a few things done to cls here
    
            # then we call __new__() on the class to create an instance
            instance = cls.__new__(cls, *args, **kwargs)
    
            # ... maybe a few things done to the instance here
    
            # then we initialize the instance with its __init__() method
            instance.__init__(*args, **kwargs)
    
            # ... maybe a few more things done to instance here
    
            # then we return it
            return instance
    
    de la métaclasse est celle qui est appelée en premier. Il délègue ensuite la création de l'instance à la méthode __call__() de la classe et l'initialisation à __new__() de l'instance. C'est également celui qui renvoie l'instance.

    De ce qui précède, il découle que la métaclasse '__init__() a également la possibilité de décider si un appel vers __call__() ou Class_1.__new__() sera éventuellement effectué. Au cours de son exécution, il pourrait en fait renvoyer un objet qui n'a été touché par aucune de ces méthodes. Prenez par exemple cette approche du motif singleton:

     Class_1.__init__()

    Observons ce qui se passe lors de tentatives répétées de création d'un objet de type

    class Meta_2(type):
        singletons = {}
    
        def __call__(cls, *args, **kwargs):
            if cls in Meta_2.singletons:
                # we return the only instance and skip a call to __new__()
                # and __init__()
                print ("{} singleton returning from Meta_2.__call__(), "
                       "skipping creation of new instance.".format(cls))
                return Meta_2.singletons[cls]
    
            # else if the singleton isn't present we proceed as usual
            print "Meta_2.__call__() before creating an instance."
            instance = super(Meta_2, cls).__call__(*args, **kwargs)
            Meta_2.singletons[cls] = instance
            print "Meta_2.__call__() returning new instance."
            return instance
    
    class Class_2(object):
    
        __metaclass__ = Meta_2
    
        def __new__(cls, *args, **kwargs):
            print "Class_2.__new__() before creating instance."
            instance = super(Class_2, cls).__new__(cls)
            print "Class_2.__new__() returning instance."
            return instance
    
        def __init__(self, *args, **kwargs):
            print "entering Class_2.__init__() for initialization."
            super(Class_2, self).__init__()
            print "exiting Class_2.__init__()."
    
     Class_2     
    58
    2018-08-27 17: 21: 01Z

    Une métaclasse est une classe qui indique comment (une autre) classe doit être créée.

    C’est un cas où j’ai considéré la métaclasse comme une solution à mon problème: J'ai eu un problème vraiment compliqué, qui aurait probablement pu être résolu différemment, mais j'ai choisi de le résoudre en utilisant une métaclasse. En raison de sa complexité, c’est l’un des rares modules que j’ai écrit où les commentaires dans ce module dépassent la quantité de code écrite. Ici c'est ...

     
    a = Class_2()
    # Meta_2.__call__() before creating an instance.
    # Class_2.__new__() before creating instance.
    # Class_2.__new__() returning instance.
    # entering Class_2.__init__() for initialization.
    # exiting Class_2.__init__().
    # Meta_2.__call__() returning new instance.
    
    b = Class_2()
    # <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
    
    c = Class_2()
    # <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
    
    a is b is c # True
    
        
    50
    2016-01-25 20: 08: 37Z

    #!/usr/bin/env python
    
    # Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.
    
    # This requires some explaining.  The point of this metaclass excercise is to
    # create a static abstract class that is in one way or another, dormant until
    # queried.  I experimented with creating a singlton on import, but that did
    # not quite behave how I wanted it to.  See now here, we are creating a class
    # called GsyncOptions, that on import, will do nothing except state that its
    # class creator is GsyncOptionsType.  This means, docopt doesn't parse any
    # of the help document, nor does it start processing command line options.
    # So importing this module becomes really efficient.  The complicated bit
    # comes from requiring the GsyncOptions class to be static.  By that, I mean
    # any property on it, may or may not exist, since they are not statically
    # defined; so I can't simply just define the class with a whole bunch of
    # properties that are @property @staticmethods.
    #
    # So here's how it works:
    #
    # Executing 'from libgsync.options import GsyncOptions' does nothing more
    # than load up this module, define the Type and the Class and import them
    # into the callers namespace.  Simple.
    #
    # Invoking 'GsyncOptions.debug' for the first time, or any other property
    # causes the __metaclass__ __getattr__ method to be called, since the class
    # is not instantiated as a class instance yet.  The __getattr__ method on
    # the type then initialises the class (GsyncOptions) via the __initialiseClass
    # method.  This is the first and only time the class will actually have its
    # dictionary statically populated.  The docopt module is invoked to parse the
    # usage document and generate command line options from it.  These are then
    # paired with their defaults and what's in sys.argv.  After all that, we
    # setup some dynamic properties that could not be defined by their name in
    # the usage, before everything is then transplanted onto the actual class
    # object (or static class GsyncOptions).
    #
    # Another piece of magic, is to allow command line options to be set in
    # in their native form and be translated into argparse style properties.
    #
    # Finally, the GsyncListOptions class is actually where the options are
    # stored.  This only acts as a mechanism for storing options as lists, to
    # allow aggregation of duplicate options or options that can be specified
    # multiple times.  The __getattr__ call hides this by default, returning the
    # last item in a property's list.  However, if the entire list is required,
    # calling the 'list()' method on the GsyncOptions class, returns a reference
    # to the GsyncListOptions class, which contains all of the same properties
    # but as lists and without the duplication of having them as both lists and
    # static singlton values.
    #
    # So this actually means that GsyncOptions is actually a static proxy class...
    #
    # ...And all this is neatly hidden within a closure for safe keeping.
    def GetGsyncOptionsType():
        class GsyncListOptions(object):
            __initialised = False
    
        class GsyncOptionsType(type):
            def __initialiseClass(cls):
                if GsyncListOptions._GsyncListOptions__initialised: return
    
                from docopt import docopt
                from libgsync.options import doc
                from libgsync import __version__
    
                options = docopt(
                    doc.__doc__ % __version__,
                    version = __version__,
                    options_first = True
                )
    
                paths = options.pop('<path>', None)
                setattr(cls, "destination_path", paths.pop() if paths else None)
                setattr(cls, "source_paths", paths)
                setattr(cls, "options", options)
    
                for k, v in options.iteritems():
                    setattr(cls, k, v)
    
                GsyncListOptions._GsyncListOptions__initialised = True
    
            def list(cls):
                return GsyncListOptions
    
            def __getattr__(cls, name):
                cls.__initialiseClass()
                return getattr(GsyncListOptions, name)[-1]
    
            def __setattr__(cls, name, value):
                # Substitut option names: --an-option-name for an_option_name
                import re
                name = re.sub(r'^__', "", re.sub(r'-', "_", name))
                listvalue = []
    
                # Ensure value is converted to a list type for GsyncListOptions
                if isinstance(value, list):
                    if value:
                        listvalue = [] + value
                    else:
                        listvalue = [ None ]
                else:
                    listvalue = [ value ]
    
                type.__setattr__(GsyncListOptions, name, listvalue)
    
        # Cleanup this module to prevent tinkering.
        import sys
        module = sys.modules[__name__]
        del module.__dict__['GetGsyncOptionsType']
    
        return GsyncOptionsType
    
    # Our singlton abstract proxy class.
    class GsyncOptions(object):
        __metaclass__ = GetGsyncOptionsType()
    
    est en réalité un type - une classe qui en crée une autre. La plupart des metaclass sont les sous-classes de metaclass. Le type reçoit la classe metaclass comme premier argument et fournit un accès à un objet de classe avec les détails mentionnés ci-dessous:  new

    >>> class MetaClass(type):
    ...     def __init__(cls, name, bases, attrs):
    ...         print ('class name: %s' %name )
    ...         print ('Defining class %s' %cls)
    ...         print('Bases %s: ' %bases)
    ...         print('Attributes')
    ...         for (name, value) in attrs.items():
    ...             print ('%s :%r' %(name, value))
    ... 
    
    >>> class NewClass(object, metaclass=MetaClass):
    ...    get_choch='dairy'
    ... 
    class name: NewClass
    Bases <class 'object'>: 
    Defining class <class 'NewClass'>
    get_choch :'dairy'
    __module__ :'builtins'
    __qualname__ :'NewClass'
    

    Notez que la classe n'a été instanciée à aucun moment. le simple fait de créer la classe a déclenché l’exécution du Note:.

        
    36
    2017-08-29 05: 23: 15Z

    La version tl; dr

    La fonction metaclass vous donne le type d'un objet.

    Le type(obj) d'une classe est sa métaclasse .

    Pour utiliser une métaclasse:

     type()     
    32
    2016-12-30 17: 28: 40Z

    Les classes Python sont elles-mêmes des objets - comme par exemple - de leur méta-classe.

    La métaclasse par défaut, qui est appliquée lorsque vous déterminez des classes en tant que:

     
    class Foo(object):
        __metaclass__ = MyMetaClass
    

    Les méta-classes sont utilisées pour appliquer une règle à un ensemble de classes. Par exemple, supposons que vous construisiez un ORM pour accéder à une base de données et que vous souhaitiez que les enregistrements de chaque table fassent partie d'une classe mappée sur cette table (en fonction de champs, règles métier, etc.,), d'une utilisation possible de métaclasse. est, par exemple, la logique du pool de connexions, qui est partagée par toutes les classes d’enregistrements de toutes les tables. Une autre utilisation est la logique de prise en charge des clés étrangères, qui implique plusieurs classes d'enregistrements.

    lorsque vous définissez une métaclasse, vous sous-classez le type et pouvez remplacer les méthodes magiques suivantes pour insérer votre logique.

     
    class foo:
        ...
    

    de toute façon, ces deux sont les crochets les plus couramment utilisés. la méta-classification est puissante, et la liste ci-dessus est loin d’être exhaustive et exhaustive des utilisations de la méta-classification.

        
    22
    2017-07-13 08: 18: 22Z

    La fonction type () peut renvoyer le type d'un objet ou créer un nouveau type,

    Par exemple, nous pouvons créer une classe Hi avec la fonction type () et nous n’avons pas besoin de l’utiliser de cette manière avec la classe Hi (objet):

     
    class somemeta(type):
        __new__(mcs, name, bases, clsdict):
          """
      mcs: is the base metaclass, in this case type.
      name: name of the new class, as provided by the user.
      bases: tuple of base classes 
      clsdict: a dictionary containing all methods and attributes defined on class
    
      you must return a class object by invoking the __new__ constructor on the base metaclass. 
     ie: 
        return type.__call__(mcs, name, bases, clsdict).
    
      in the following case:
    
      class foo(baseclass):
            __metaclass__ = somemeta
    
      an_attr = 12
    
      def bar(self):
          ...
    
      @classmethod
      def foo(cls):
          ...
    
          arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}
    
          you can modify any of these values before passing on to type
          """
          return type.__call__(mcs, name, bases, clsdict)
    
    
        def __init__(self, name, bases, clsdict):
          """ 
          called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
          """
          pass
    
    
        def __prepare__():
            """
            returns a dict or something that can be used as a namespace.
            the type will then attach methods and attributes from class definition to it.
    
            call order :
    
            somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
            """
            return dict()
    
        def mymethod(cls):
            """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
            """
            pass
    

    Outre l'utilisation de type () pour créer des classes de manière dynamique, vous pouvez également contrôler le comportement de création de cette classe et utiliser une métaclasse.

    Selon le modèle d’objet Python, la classe est l’objet, elle doit donc être une instance d’une autre classe. Par défaut, une classe Python est une instance de la classe type. Autrement dit, type est une métaclasse de la plupart des classes intégrées et une métaclasse de classes définies par l'utilisateur.

     
    def func(self, name='mike'):
        print('Hi, %s.' % name)
    
    Hi = type('Hi', (object,), dict(hi=func))
    h = Hi()
    h.hi()
    Hi, mike.
    
    type(Hi)
    type
    
    type(h)
    __main__.Hi
    

    Magic prend effet lorsque nous passons des arguments de mots clés dans une métaclasse. Il indique à l'interpréteur Python de créer la liste personnalisée par le biais de ListMetaclass. new (), à ce stade, nous pouvons modifier la définition de la classe, par exemple, ajouter une nouvelle méthode, puis renvoyer la définition révisée.

        
    17
    2018-01-12 09: 30: 20Z

    En plus des réponses publiées, je peux dire qu'un

    class ListMetaclass(type):
        def __new__(cls, name, bases, attrs):
            attrs['add'] = lambda self, value: self.append(value)
            return type.__new__(cls, name, bases, attrs)
    
    class CustomList(list, metaclass=ListMetaclass):
        pass
    
    lst = CustomList()
    lst.add('custom_list_1')
    lst.add('custom_list_2')
    
    lst
    ['custom_list_1', 'custom_list_2']
    
    définit le comportement d'une classe. Donc, vous pouvez définir explicitement votre métaclasse. Chaque fois que Python obtient un mot clé metaclass, il commence alors à rechercher le class. S'il n'est pas trouvé, le type de métaclasse par défaut est utilisé pour créer l'objet de la classe. À l'aide de l'attribut metaclass, vous pouvez définir __metaclass__ de votre classe:  metaclass

    Cela produira la sortie comme ceci:

     
    class MyClass:
       __metaclass__ = type
       # write here other method
       # write here one more method
    
    print(MyClass.__metaclass__)
    

    Et bien sûr, vous pouvez créer votre propre

    class 'type'
    
    pour définir le comportement de toute classe créée à l'aide de votre classe.

    Pour cela, votre classe de type metaclass par défaut doit être héritée car il s'agit du metaclass principal:

     metaclass

    Le résultat sera:

     
    class MyMetaClass(type):
       __metaclass__ = type
       # you can write here any behaviour you want
    
    class MyTestClass:
       __metaclass__ = MyMetaClass
    
    Obj = MyTestClass()
    print(Obj.__metaclass__)
    print(MyMetaClass.__metaclass__)
    
        
    7
    2018-09-15 13: 17: 29Z
    class '__main__.MyMetaClass'
    class 'type'
    
source placée ici