2010-01-27 6 views
25

J'ai essayé de comprendre les métaclasses python, et j'ai donc passé en revue quelques exemples de code. Pour autant que je le comprenne, une métaclasse Python peut être n'importe quel appelable. Donc, je peux avoir mes métaclasse commeHabituellement, hériter des métaclasses De type?

def metacls(clsName, bases, atts): 
    .... 
    return type(clsName, bases, atts) 

Cependant, je l'ai vu beaucoup de gens écrivent leurs métaclasses de la façon suivante:

class Metacls(type): 
    def __new__(meta, clsName, bases, atts): 
     .... 
     return type.__new__(meta, clsName, bases, atts) 

Pour autant que je peux voir, ce serait aussi bien faire la même chose. Y a-t-il une raison d'utiliser la classe de base à la place? Est-ce coutumier?

Répondre

37

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.

+1

Merci d'avoir pris le temps d'expliquer cela. –

+1

Merci beaucoup, il est rare que je vois une réponse aussi claire. – Nikwin

Questions connexes