Como las clases son instancias de una metaclase, no es inesperado que un "método de instancia" en la metaclase se comporte como un método de clase.
Sin embargo, sí, hay diferencias, y algunas de ellas son más que semánticas:
- La diferencia más importante es que un método en la metaclase no es "visible" desde una instancia de clase . Esto sucede porque la búsqueda de atributos en Python (de manera simplificada, los descriptores pueden tener prioridad) busca un atributo en la instancia; si no está presente en la instancia, Python busca en la clase de esa instancia y luego la búsqueda continúa las superclases de la clase, pero no en las clases de la clase. Python stdlib hace uso de esta característica en el
abc.ABCMeta.register
método. Esa característica se puede usar para bien, ya que los métodos relacionados con la clase en sí mismos se pueden reutilizar como atributos de instancia sin ningún conflicto (pero un método seguiría en conflicto).
- Otra diferencia, aunque obvia, es que un método declarado en la metaclase puede estar disponible en varias clases, no relacionadas de otra manera: si tiene diferentes jerarquías de clases, no están relacionadas en absoluto con lo que tratan, pero desea alguna funcionalidad común para todas las clases , tendría que crear una clase mixin, que debería incluirse como base en ambas jerarquías (por ejemplo, para incluir todas las clases en un registro de aplicación). (Nota: el mixin a veces puede ser una mejor opción que una metaclase)
- Un método de clase es un objeto especializado de "método de clase", mientras que un método en la metaclase es una función ordinaria.
Entonces, sucede que el mecanismo que utilizan los métodos de clase es el " protocolo descriptor ". Mientras que las funciones normales presentan un __get__
método que insertará el self
argumento cuando se recuperan de una instancia, y dejará ese argumento vacío cuando se recupere de una clase, un classmethod
objeto tiene un diferente __get__
, que insertará la clase en sí (el "propietario") como primer parámetro en ambas situaciones.
Esto no hace diferencias prácticas la mayor parte del tiempo, pero si desea acceder al método como una función, con el fin de agregarle decorador dinámicamente, o cualquier otro, para un método en la metaclase meta.method
recupera la función, lista para ser utilizada , mientras que tiene que usar cls.my_classmethod.__func__
para recuperarlo de un método de clase (y luego tiene que crear otro classmethod
objeto y asignarlo de nuevo, si realiza un ajuste ).
Básicamente, estos son los 2 ejemplos:
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 otras palabras: aparte de la importante diferencia de que un método definido en la metaclase es visible desde la instancia y un classmethod
objeto no, las otras diferencias en tiempo de ejecución parecerán oscuras y sin sentido, pero eso sucede porque el lenguaje no necesita ir fuera de su camino con reglas especiales para métodos de clase: Ambas formas de declarar un método de clase son posibles, como consecuencia del diseño del lenguaje, una, por el hecho de que una clase es en sí misma un objeto, y otra, como una posibilidad entre muchos, de El uso del protocolo descriptor que permite especializar el acceso a atributos en una instancia y en una clase:
La classmethod
construcción se define en código nativo, pero podría codificarse en Python puro y funcionaría exactamente de la misma manera. La clase de 5 líneas de abajo se puede usar como classmethod
decorador sin diferencias de tiempo de ejecución con la representación de @classmethod" at all (though distinguishable through introspection such as calls to
instancia , and even
integrada, por supuesto):
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
Y, más allá de los métodos, es interesante tener en cuenta que los atributos especializados, como un @property
en la metaclase, funcionarán como atributos de clase especializados, igual, sin ningún comportamiento sorprendente.