Quelles sont les métaclasses en Python?


Answers

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 langues, les classes sont juste des morceaux de code qui décrivent comment produire un objet. C'est aussi vrai 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ée un OBJECT. 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 encore, 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 en 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 dynamiquement

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

D'abord, vous pouvez créer une classe dans une fonction en utilisant la 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, puisque vous devez toujours écrire toute la classe vous-même.

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

Lorsque vous utilisez le mot class 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 le type fonction? La bonne vieille fonction qui vous permet de savoir de quel type est un objet:

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

Eh bien, le type a une capacité complètement différente, il peut également créer des classes à la volée. type peut prendre la description d'une classe en tant que paramètres et renvoyer une classe.

(Je sais, c'est stupide que la même fonction puisse avoir deux usages complètement différents selon les paramètres que vous lui passez.) C'est un problème dû à la rétrocompatibilité en Python)

type fonctionne de cette façon:

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

par 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 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. Alors:

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

Finalement, vous voudrez ajouter des méthodes à votre classe. Définissez simplement une fonction avec la signature appropriée et attribuez-la en tant qu'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 ajouter 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, dynamiquement.

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

Quelles sont les métaclasses (enfin)

Les métaclasses sont les «trucs» qui créent des classes.

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

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

Eh bien, les métaclasses sont ce qui crée ces objets. Ce sont les classes des classes, vous pouvez les représenter de cette façon:

MyClass = MetaClass()
my_object = MyClass()

Vous avez vu ce type vous permet de faire quelque chose comme ceci:

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

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

Maintenant vous vous demandez pourquoi le diable est-il écrit en minuscules, et pas de Type ?

Eh bien, je suppose que c'est une question de cohérence avec str , la classe qui crée des objets de chaînes, et int la classe qui crée des 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 inclut les ints, les chaînes, les fonctions et les classes. Tous sont des objets. Et tous 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 __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 juste la substance qui crée des objets de classe.

Vous pouvez l'appeler une «usine de classe» 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.

L'attribut __metaclass__

Vous pouvez ajouter un attribut __metaclass__ lorsque vous écrivez une classe:

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

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

Attention, c'est difficile.

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

Python recherchera __metaclass__ dans la définition de la classe. S'il le 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 cela plusieurs fois.

Quand vous faites:

class Foo(Bar):
    pass

Python fait ce qui suit:

Y a-t-il un attribut __metaclass__ 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 MODULE, et essayera de faire la même chose (mais seulement pour les classes qui n'héritent d'rien, en fait des classes de style ancien).

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

Attention ici que l'attribut __metaclass__ ne sera pas hérité, la métaclasse du parent ( Bar.__class__ ) sera. Si Bar utilisé un attribut __metaclass__ qui a créé Bar avec type() (et non pas 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 sous-classe ou utilise.

Métaclasses personnalisées

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

Vous le faites généralement pour les API, où vous voulez créer des classes correspondant au 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 y a plusieurs façons de le faire, mais une façon est de 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 il suffit de dire à la métaclasse de transformer tous les attributs en majuscules.

Heureusement, __metaclass__ peut être n'importe quel callable, 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 comprendre ... 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_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'

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_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)

Mais ce n'est pas vraiment OOP. Nous appelons le type directement et nous ne surchargeons pas ou __new__ pas le parent __new__ . Faisons le:

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)

Vous avez peut-être remarqué l'argument supplémentaire upperattr_metaclass . Il n'y a rien de spécial à ce sujet: __new__ reçoit toujours la classe dans laquelle il est défini, en tant que premier paramètre. Tout comme vous avez vous- self pour les méthodes ordinaires qui reçoivent l'instance en tant que 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 la clarté, mais comme pour self - self , tous les arguments ont des noms conventionnels. Donc, une vraie métaclasse de production ressemblerait à 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 type.__new__(cls, clsname, bases, uppercase_attr)

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ériter des métaclasses, hériter du type):

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)

C'est tout. Il n'y a vraiment rien de plus sur les 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 des métaclasses pour faire des choses tordues 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 eux-mêmes, ils sont simples:

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

Pourquoi utiliseriez-vous des classes de métaclasses au lieu de fonctions?

Puisque __metaclass__ peut accepter n'importe quel callable, pourquoi utiliseriez-vous une classe puisque c'est évidemment plus compliqué?

Il y a plusieurs raisons de le faire:

  • L'intention est claire. Lorsque vous lisez UpperAttrMetaclass(type) , vous savez ce qui va suivre
  • Vous pouvez utiliser OOP. La métaclasse peut hériter de la métaclasse, remplacer les méthodes parentes. 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, mais pas une métaclasse.
  • Vous pouvez mieux structurer votre code. Vous n'utilisez jamais de métaclasses pour quelque chose d'aussi trivial que l'exemple ci-dessus. C'est habituellement pour quelque chose de compliqué. Avoir la possibilité de créer plusieurs méthodes et de les regrouper dans une classe est très utile pour faciliter la lecture du code.
  • Vous pouvez accrocher __new__ , __init__ et __call__ . Ce qui vous permettra de faire des choses différentes. Même si généralement vous pouvez tout faire dans __new__ , certaines personnes sont plus à l'aise avec __init__ .
  • Ce sont des métaclasses, bon sang! Cela doit signifier quelque chose!

Pourquoi utiliseriez-vous des métaclasses?

Maintenant, la grande question. Pourquoi utiliseriez-vous une fonction sujettes aux erreurs obscures?

Eh bien, d'habitude 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, ce n'est pas le cas (les personnes qui en ont réellement besoin savent avec certitude qu'elles en ont besoin et n'ont pas besoin d'explications sur les raisons).

Python Guru Tim Peters

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

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

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

Mais si vous faites ceci:

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

Il ne retournera pas un objet IntegerField . Il retournera un int , et peut même le prendre directement à partir de la base de données.

Ceci est possible car models.Model définit __metaclass__ et utilise un peu de magie qui transformera la Person vous venez de définir avec des instructions simples en un crochet complexe vers un champ de base de données.

Django simplifie l'apparence de quelque chose de complexe en exposant une API simple et en utilisant des métaclasses, recréant du code à partir de cette API pour faire le vrai travail en coulisses.

Le dernier mot

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 ce sont toutes des instances de classes ou des instances de métaclasses.

Sauf pour le type .

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

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

99% du temps, vous avez besoin d'altération de classe, vous feriez mieux d'utiliser ceux-ci.

Mais 98% du temps, vous n'avez pas besoin d'altération de classe du tout.

Question

Que sont les métaclasses et à quoi les utilisons-nous?




Python 3 update

There are (at this point) two key methods in a metaclass:

  • __prepare__ , and
  • __new__

__prepare__ lets you supply a custom mapping (such as an OrderedDict ) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don't implement __prepare__ a normal dict is used.

__new__ is responsible for the actual creation/modification of the final class.

A bare-bones, do-nothing-extra metaclass would like:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Un exemple simple:

Say you want some simple validation code to run on your attributes -- like it must always be an int or a str . Without a metaclass, your class would look something like:

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

As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.

A simple metaclass can address that problem:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

This is what the metaclass would look like (not using __prepare__ since it is not needed):

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)

A sample run of:

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

produit:

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

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.

The 'ValidateType' class for reference:

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



Une utilisation pour les métaclasses consiste à ajouter de nouvelles propriétés et méthodes à une instance automatiquement.

Par exemple, si vous regardez les modèles Django , leur définition semble un peu confuse. Il semble que vous ne définissiez que les propriétés de classe:

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

Cependant, à l'exécution, les objets Person sont remplis de toutes sortes de méthodes utiles. Voir la source pour une métaclasse étonnante.




The tl;dr version

The type(obj) function gets you the type of an object.

The type() of a class is its metaclass .

To use a metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass



Je pense que l'introduction de ONLamp à la programmation de la métaclasse est bien écrite et donne une très bonne introduction au sujet en dépit d'être déjà plusieurs années.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

En bref: Une classe est un plan pour la création d'une instance, une métaclasse est un plan pour la création d'une classe. On peut facilement voir que dans les classes Python, il faut aussi des objets de première classe pour activer ce comportement.

Je n'en ai jamais écrit un moi-même, mais je pense que l'une des plus belles utilisations des métaclasses peut être vue dans le framework Django . Les classes de modèle utilisent une approche de métaclasse pour activer 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.

La chose qui reste à dire est: Si vous ne savez pas ce que sont les métaclasses, la probabilité que vous n'en aurez pas besoin est de 99%.




Role of a metaclass's __call__() method when creating a class instance

If you've done Python programming for more than a few months you'll eventually stumble upon code that looks like this:

# 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')

The latter is possible when you implement the __call__() magic method on the class.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

The __call__() method is invoked when an instance of a class is used as a callable. But as we've seen from previous answers a class itself is an instance of a metaclass, so when we use the class as a callable (ie when we create an instance of it) we're actually calling its metaclass's __call__() method. At this point most Python programmers are a bit confused because they've been told that when creating an instance like this instance = SomeClass() you're calling it's __init__() method. Some who've dug a bit deeper know that before __init__() there's __new__() . Well, today another layer of truth is being revealed, before __new__() there's the metaclass's __call__() .

Let's study the method call chain from specifically the perspective of creating an instance of a class.

This is a metaclass that logs exactly the moment before an instance is created and the moment it's about to return it.

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

This is a class that uses that metaclass

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__()."

And now let's create an instance of Class_1

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.

The code above doesn't actually do anything other than logging the task and then delegating the actual work to the parent (ie keeping the default behavior). So with type being Meta_1 's parent class, we can imagine that this would be the pseudo implementation of type.__call__() :

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

We can see that the metaclass's __call__() method is the one that's called first. It then delegates creation of the instance to the class's __new__() method and initialization to the instance's __init__() . It's also the one that ultimately returns the instance.

From the above it stems that the metaclass's __call__() is also given the opportunity to decide whether or not a call to Class_1.__new__() or Class_1.__init__() will eventually be made. Over the course of its execution it could actually return an object that hasn't been touched by either of these methods. Take for example this approach to the singleton pattern:

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__()."

Let's observe what happens when repeatedly trying to create an object of type Class_2

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



The type() function can return the type of an object or create a new type,

for example, we can create a Hi class with the type() function and do not need to use this way with class Hi(object):

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

In addition to using type() to create classes dynamically, you can control creation behavior of class and use metaclass.

According to the Python object model, the class is the object, so the class must be an instance of another certain class. By default, a Python class is instance of the type class. That is, type is metaclass of most of the built-in classes and metaclass of user-defined classes.

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

Magic will take effect when we passed keyword arguments in metaclass, it indicates the Python interpreter to create the CustomList through ListMetaclass. new (), at this point, we can modify the class definition, for example, and add a new method and then return the revised definition.




Related