Comme les classes sont des instances d'une métaclasse, il n'est pas surprenant qu'une "méthode d'instance" sur la métaclasse se comporte comme une méthode de classe.
Cependant, oui, il existe des différences - et certaines d'entre elles sont plus que sémantiques:
- La différence la plus importante est qu'une méthode de la métaclasse n'est pas "visible" à partir d'une instance de classe . Cela se produit parce que la recherche d'attribut en Python (de manière simplifiée - les descripteurs peuvent avoir la priorité) recherche un attribut dans l'instance - s'il n'est pas présent dans l'instance, Python regarde alors dans la classe de cette instance, puis la recherche se poursuit. les superclasses de la classe, mais pas sur les classes de la classe. Le stdlib Python utilise cette fonctionnalité dans la
abc.ABCMeta.register
méthode. Cette fonctionnalité peut être utilisée pour de bon, car les méthodes liées à la classe elles-mêmes peuvent être réutilisées en tant qu'attributs d'instance sans aucun conflit (mais une méthode serait toujours en conflit).
- Une autre différence, bien qu'évidente, est qu'une méthode déclarée dans la métaclasse peut être disponible dans plusieurs classes, non liées par ailleurs - si vous avez des hiérarchies de classes différentes, pas du tout liées à ce qu'elles traitent, mais que vous voulez une fonctionnalité commune pour toutes les classes , vous devriez trouver une classe mixin, qui devrait être incluse comme base dans les deux hiérarchies (disons pour inclure toutes les classes dans un registre d'application). (NB: le mixin peut parfois être un meilleur appel qu'une métaclasse)
- Un classmethod est un objet "classmethod" spécialisé, tandis qu'une méthode dans la métaclasse est une fonction ordinaire.
Ainsi, il arrive que le mécanisme utilisé par les méthodes de classe soit le " protocole descripteur ". Alors que les fonctions normales comportent une __get__
méthode qui insérera l' self
argument lors de leur récupération à partir d'une instance, et laissera cet argument vide lors de la récupération à partir d'une classe, un classmethod
objet a un autre __get__
, qui insérera la classe elle-même (le "propriétaire") en tant que premier paramètre dans les deux situations.
Cela ne fait aucune différence pratique la plupart du temps, mais si vous souhaitez accéder à la méthode en tant que fonction, dans le but d'y ajouter dynamiquement un décorateur, ou tout autre, pour une méthode dans la métaclasse meta.method
récupère la fonction, prête à être utilisée , alors que vous devez l'utiliser cls.my_classmethod.__func__
pour le récupérer à partir d'une méthode de classe (et ensuite vous devez créer un autre classmethod
objet et le réattribuer , si vous faites un habillage).
En gros, ce sont les 2 exemples:
class M1(type):
def clsmethod1(cls):
pass
class CLS1(metaclass=M1):
pass
def runtime_wrap(cls, method_name, wrapper):
mcls = type(cls)
setattr(mcls, method_name, wrapper(getatttr(mcls, method_name)))
def wrapper(classmethod):
def new_method(cls):
print("wrapper called")
return classmethod(cls)
return new_method
runtime_wrap(cls1, "clsmethod1", wrapper)
class CLS2:
@classmethod
def classmethod2(cls):
pass
def runtime_wrap2(cls, method_name, wrapper):
setattr(cls, method_name, classmethod(
wrapper(getatttr(cls, method_name).__func__)
)
)
runtime_wrap2(cls1, "clsmethod1", wrapper)
En d'autres termes: à part la différence importante qu'une méthode définie dans la métaclasse est visible à partir de l'instance et qu'un classmethod
objet ne le fait pas, les autres différences, au moment de l'exécution, sembleront obscures et dénuées de sens - mais cela se produit parce que le langage n'a pas besoin d'aller hors de son chemin avec des règles spéciales pour les méthodes de classe: Les deux façons de déclarer une méthode de classe sont possibles, en conséquence de la conception du langage - une, pour le fait qu'une classe est elle-même un objet, et une autre, comme une possibilité parmi beaucoup d'autres, de l'utilisation du protocole descripteur qui permet de spécialiser l'accès aux attributs dans une instance et dans une classe:
Le classmethod
builtin est défini en code natif, mais il pourrait simplement être codé en python pur et fonctionnerait exactement de la même manière. Le soufflet de classe 5 lignes peut être utilisé comme classmethod
décorateur sans différences d'exécution par rapport à la @classmethod" at all (though distinguishable through introspection such as calls to
représentation , and even
intégrée bien sûr):
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
Et, au-delà des méthodes, il est intéressant de garder à l'esprit que les attributs spécialisés tels que a @property
sur la métaclasse fonctionneront comme des attributs de classe spécialisés, tout de même, sans aucun comportement surprenant.