Il existe des différences subtiles, principalement liées à l'héritage. Lorsque vous utilisez une fonction en tant que métaclasse, la classe résultante est en réalité une instance de type
, et peut être héritée de sans restriction; cependant, la fonction de métaclasse ne sera jamais appelée pour de telles sous-classes. Lors de l'utilisation d'une sous-classe de type
en tant que métaclasse , la classe résultante sera une instance de cette métaclasse, tout comme l'une de ses sous-classes; cependant, l'héritage multiple sera restreint.
Illustrer les différences:
>>> def m1(name, bases, atts):
>>> print "m1 called for " + name
>>> return type(name, bases, atts)
>>>
>>> def m2(name, bases, atts):
>>> print "m2 called for " + name
>>> return type(name, bases, atts)
>>>
>>> class c1(object):
>>> __metaclass__ = m1
m1 called for c1
>>> type(c1)
<type 'type'>
>>> class sub1(c1):
>>> pass
>>> type(sub1)
<type 'type'>
>>> class c2(object):
>>> __metaclass__ = m2
m2 called for c2
>>> class sub2(c1, c2):
>>> pass
>>> type(sub2)
<type 'type'>
Notez que lors de la définition SUB1 et SUB2, aucune fonction de métaclasse ont été appelés. Ils seront créés exactement comme si c1 et c2 n'avaient pas de métaclasses, mais à la place, avait été manipulé après la création.
>>> class M1(type):
>>> def __new__(meta, name, bases, atts):
>>> print "M1 called for " + name
>>> return super(M1, meta).__new__(meta, name, bases, atts)
>>> class C1(object):
>>> __metaclass__ = M1
M1 called for C1
>>> type(C1)
<class '__main__.M1'>
>>> class Sub1(C1):
>>> pass
M1 called for Sub1
>>> type(Sub1)
<class '__main__.M1'>
Notez les différences déjà: M1 a été appelé lors de la création Sub1, et les deux classes sont des instances de M1. J'utilise super()
pour la création actuelle ici, pour des raisons qui deviendront claires plus tard.
>>> class M2(type):
>>> def __new__(meta, name, bases, atts):
>>> print "M2 called for " + name
>>> return super(M2, meta).__new__(meta, name, bases, atts)
>>> class C2(object):
>>> __metaclass__ = M2
M2 called for C2
>>> type(C2)
<class '__main__.M2'>
>>> class Sub2(C1, C2):
>>> pass
M1 called for Sub2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 23, in __new__
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Ceci est la restriction majeure sur l'héritage multiple avec les métaclasses. Python ne sait pas si M1 et M2 sont des métaclasses compatibles, donc il vous oblige à en créer un nouveau pour vous garantir qu'il fait ce dont vous avez besoin.
>>> class M3(M1, M2):
>>> def __new__(meta, name, bases, atts):
>>> print "M3 called for " + name
>>> return super(M3, meta).__new__(meta, name, bases, atts)
>>> class C3(C1, C2):
>>> __metaclass__ = M3
M3 called for C3
M1 called for C3
M2 called for C3
>>> type(C3)
<class '__main__.M3'>
C'est la raison pour laquelle je super()
dans la métaclasse __new__
fonctions: si chacun peut appeler le suivant dans le MRO.
Certains cas d'utilisation pourraient avoir besoin de vos classes pour être de type type
, ou pourraient vouloir pour éviter les problèmes d'héritage, auquel cas une fonction de métaclasse est probablement le chemin à parcourir. Dans d'autres cas, le type de la classe peut être vraiment important, ou vous pouvez opérer sur toutes les sous-classes, auquel cas le sous-classement type
serait une meilleure idée. N'hésitez pas à utiliser le style qui correspond le mieux à toute situation donnée.
Merci d'avoir pris le temps d'expliquer cela. –
Merci beaucoup, il est rare que je vois une réponse aussi claire. – Nikwin