web-dev-qa-db-fra.com

Python Métaclasse: Comprendre le 'with_metaclass ()'

Je veux demander ce que l'appel with_metaclass() signifie dans la définition d'une classe.

Par exemple.:

class Foo(with_metaclass(Cls1, Cls2)):
  • Est-ce un cas spécial où une classe hérite d'une métaclasse?
  • La nouvelle classe est-elle également une métaclasse?
45
Zakos

with_metaclass() est une fonction d'usine de classe utilitaire fournie par la bibliothèque six pour faciliter le développement de code pour les deux Python 2 et 3.

Il utilise un peu de main (voir ci-dessous) avec une métaclasse temporaire, pour attacher une métaclasse à une classe régulière d'une manière qui est compatible avec les deux Python 2 et Python 3.

Citant de la documentation:

Créez une nouvelle classe avec la classe de base et la métaclasse de métaclasse. Ceci est conçu pour être utilisé dans des déclarations de classe comme ceci:

from six import with_metaclass

class Meta(type):
    pass

class Base(object):
    pass

class MyClass(with_metaclass(Meta, Base)):
    pass

Cela est nécessaire car la syntaxe pour attacher une métaclasse a changé entre Python 2 et 3:

Python 2:

class MyClass(object):
    __metaclass__ = Meta

Python 3:

class MyClass(metaclass=Meta):
    pass

La fonction with_metaclass() utilise le fait que les métaclasses sont a) héritées par des sous-classes, et b) une métaclasse peut être utilisée pour générer de nouvelles classes et c) lorsque vous sous-classe à partir d'une classe de base avec une métaclasse, créant l'objet de sous-classe réel est délégué à la métaclasse. Il crée efficacement une nouvelle classe de base temporaire avec une métaclasse metaclass temporaire qui, lorsqu'elle est utilisée pour créer la sous-classe , remplace la base temporaire combo classe et métaclasse avec la métaclasse de votre choix:

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(type):

        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)

        @classmethod
        def __prepare__(cls, name, this_bases):
            return meta.__prepare__(name, bases)
    return type.__new__(metaclass, 'temporary_class', (), {})

Décomposer ce qui précède:

  • type.__new__(metaclass, 'temporary_class', (), {}) utilise la métaclasse metaclass pour créer un nouvel objet de classe nommé temporary_class qui est entièrement vide sinon. type.__new__(metaclass, ...) est utilisé à la place de metaclass(...) pour éviter d'utiliser l'implémentation spéciale metaclass.__new__() qui est nécessaire au fonctionnement de la main légère dans une prochaine étape.
  • Dans Python 3 uniquement, lorsque temporary_class Est utilisé comme classe de base, Python appelle d'abord metaclass.__prepare__() (en passant le nom de classe dérivé, (temporary_class,) comme argument this_bases. La métaclasse prévue meta est alors utilisé pour appeler meta.__prepare__(), ignorant this_bases et passant l'argument bases.
  • ensuite, après avoir utilisé la valeur de retour de metaclass.__prepare__() comme espace de noms de base pour les attributs de classe (ou simplement en utilisant un dictionnaire simple lorsque on Python 2), Python appelle metaclass.__new__() pour créer la classe réelle. Ceci est à nouveau passé (temporary_class,) Comme tuple this_bases, Mais le code ci-dessus l'ignore et utilise bases à la place, en appelant meta(name, bases, d) pour créer la nouvelle classe dérivée.

Par conséquent, l'utilisation de with_metaclass() vous donne un nouvel objet de classe sans classes de base supplémentaires :

>>> class FooMeta(type): pass
...
>>> with_metaclass(FooMeta)  # returns a temporary_class object
<class '__main__.temporary_class'>
>>> type(with_metaclass(FooMeta))  # which has a custom metaclass
<class '__main__.metaclass'>
>>> class Foo(with_metaclass(FooMeta)): pass
...
>>> Foo.__mro__  # no extra base classes
(<class '__main__.Foo'>, <type 'object'>)
>>> type(Foo) # correct metaclass
<class '__main__.FooMeta'>
69
Martijn Pieters

[~ # ~] mise à jour [~ # ~] : la fonction six.with_metaclass() a depuis été corrigée avec une variante décoratrice, c'est-à-dire @six.add_metaclass(). Cette mise à jour résout certains problèmes de macro liés aux objets de base. Le nouveau décorateur serait appliqué comme suit:

import six

@six.add_metaclass(Meta)
class MyClass(Base):
    pass

Voici les notes de mise à jour et voici un exemple et une explication similaires et détaillés pour l'utilisation d'une alternative de décoration.

24
pylang