2010-08-14 25 views
15

Aujourd'hui, j'ai trouvé une définition surprenante d'une métaclasse en Python here, avec la définition de la métaclasse effectivement en ligne. La partie pertinente estQuand intégrer les définitions de métaclasse en Python?

class Plugin(object): 
    class __metaclass__(type): 
     def __init__(cls, name, bases, dict): 
      type.__init__(name, bases, dict) 
      registry.append((name, cls)) 

Quand est-ce logique d'utiliser une telle définition en ligne?

D'autres arguments:

Un argument d'une façon serait que le métaclasse créé est pas réutilisable ailleurs en utilisant cette technique. Un contre-argument est qu'un modèle courant dans l'utilisation des métaclasses est de définir une métaclasse et de l'utiliser dans une classe, puis d'en hériter. Par exemple, dans la définition a conservative metaclass

class DeclarativeMeta(type): 
    def __new__(meta, class_name, bases, new_attrs): 
     cls = type.__new__(meta, class_name, bases, new_attrs) 
     cls.__classinit__.im_func(cls, new_attrs) 
     return cls 
class Declarative(object): 
    __metaclass__ = DeclarativeMeta 
    def __classinit__(cls, new_attrs): pass 

aurait pu être écrit comme

class Declarative(object): #code not tested! 
    class __metaclass__(type): 
     def __new__(meta, class_name, bases, new_attrs): 
      cls = type.__new__(meta, class_name, bases, new_attrs) 
      cls.__classinit__.im_func(cls, new_attrs) 
      return cls 
    def __classinit__(cls, new_attrs): pass 

toute autre considération?

Répondre

19

Comme toute autre forme de définition de classe imbriquée, une métaclasse imbriquée peut être plus "compacte et pratique" (tant que vous ne réutilisez pas cette métaclasse sauf par héritage) pour de nombreux types d'utilisation en production, mais peut être un peu gênant pour le débogage et l'introspection.

En fait, au lieu de donner la métaclasse un bon, le nom de haut niveau, vous allez finir avec tous métaclasses personnalisés définis dans un module étant undistiguishable les uns des autres en fonction de leurs attributs __module__ et __name__ (qui est ce que Python utilise pour former leur repr si nécessaire). Considérez:

>>> class Mcl(type): pass 
... 
>>> class A: __metaclass__ = Mcl 
... 
>>> class B: 
... class __metaclass__(type): pass 
... 
>>> type(A) 
<class '__main__.Mcl'> 
>>> type(B) 
<class '__main__.__metaclass__'> 

OIEau, si vous voulez examiner « quel type est la classe A » (un méta-classe est le type de la classe, rappelez-vous), vous obtenez une réponse claire et utile - il est Mcl dans le module principal. Toutefois, si vous voulez examiner « quel type est la classe B », la réponse est d'une grande utilité: il dit il est __metaclass__ dans le module main, mais c'est même pas vrai:

>>> import __main__ 
>>> __main__.__metaclass__ 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'module' object has no attribute '__metaclass__' 
>>> 

... est pas une telle chose, en fait; cette repr est trompeuse et pas très utile ;-). Une classe repr est essentiellement '%s.%s' % (c.__module__, c.__name__) - une règle simple, utile et cohérente - mais dans de nombreux cas comme, l'instruction class n'est pas unique à la portée du module, ou n'est pas du tout la portée du module (mais plutôt dans une fonction ou un corps de classe), ou même pas existant (les classes peuvent bien sûr être construites sans une instruction class, en appelant explicitement leur métaclasse), cela peut être quelque peu trompeur (et la meilleure solution est d'éviter, dans la mesure du possible , ces cas particuliers, sauf quand un avantage substantiel peut être obtenu en les utilisant).Par exemple, considérons:

>>> class A(object): 
... def foo(self): print('first') 
... 
>>> x = A() 
>>> class A(object): 
... def foo(self): print('second') 
... 
>>> y = A() 
>>> x.foo() 
first 
>>> y.foo() 
second 
>>> x.__class__ 
<class '__main__.A'> 
>>> y.__class__ 
<class '__main__.A'> 
>>> x.__class__ is y.__class__ 
False 

avec deux déclaration class à la même portée, le second reliaisons le nom (ici, A), mais les instances existantes se rapportent à la première liaison du nom par objet, et non par nom - donc les deux objets de classe restent, un accessible uniquement par l'attribut type (ou __class__) de ses instances (le cas échéant - si aucun, ce premier objet disparaît) - les deux classes ont le même nom et le même module (et donc le même représentation), mais ce sont des objets distincts. Les classes imbriquées dans les corps de classe ou de fonction ou créées en appelant directement la métaclasse (y compris type) peuvent provoquer une confusion similaire si un débogage ou une introspection est nécessaire. Donc, l'emboîtement de la métaclasse est OK si vous n'avez jamais besoin de déboguer ou d'introspecter ce code, et peut être vécu avec si quelqu'un qui fait ainsi comprendre ces bizarreries (même si cela ne sera jamais aussi pratique que d'utiliser une belle , vrai nom, bien sûr - tout comme le débogage d'une fonction codée avec lambda ne peut jamais être aussi pratique que le débogage codé avec def). Par analogie avec lambda par rapport à def, vous pouvez raisonnablement prétendre que la définition "imbriquée" anonyme est OK pour les métaclasses qui sont si simples, si faciles à comprendre, qu'aucun débogage ou introspection ne sera jamais envisageable.

En Python 3, la "définition imbriquée" ne fonctionne pas - là, une métaclasse doit être passée en argument à la classe, comme dans class A(metaclass=Mcl):, donc définir __metaclass__ dans le corps n'a aucun effet. Je crois que ceci suggère également qu'une définition de métaclasse imbriquée dans le code de Python 2 est probablement appropriée seulement si vous savez avec certitude que le code n'aura jamais besoin d'être porté sur Python 3 (puisque vous faites ce port beaucoup plus dur, et devrez dé-imbriquez la définition de la métaclasse à cet effet) - en d'autres termes, le code "throwaway" qui ne sera pas disponible dans quelques années, quand une version de Python 3 acquiert d'énormes avantages de vitesse, de fonctionnalité ou de support de partie, sur Python 2.7 (la dernière version de Python 2).

code que vous attendez pour être jetable, comme l'histoire de l'informatique nous montre, a une habitude attachante de vous surprendre tout à fait, et étant encore environ 20 ans plus tard (alors peut-être le code écrit dans le même temps " pour les âges "est complètement oublié ;-). Cela semble certainement suggérer d'éviter la définition imbriquée des métaclasses.

+1

Très belle explication, merci. – cji

+0

Réponse impressionnante. Il y a un peu manquant dans "vous allez compter sur ???, et la sérialisation". –

+0

Je ne suis pas sûr à quel point un problème est «pickle». Pickling semble fonctionner correctement [ici] (http://gist.github.com/524744). –