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.
Très belle explication, merci. – cji
Réponse impressionnante. Il y a un peu manquant dans "vous allez compter sur ???, et la sérialisation". –
Je ne suis pas sûr à quel point un problème est «pickle». Pickling semble fonctionner correctement [ici] (http://gist.github.com/524744). –