Como as classes são instâncias de uma metaclasse, não é inesperado que um "método de instância" na metaclasse se comporte como um método de classe.
No entanto, sim, existem diferenças - e algumas são mais que semânticas:
- A diferença mais importante é que um método na metaclasse não é "visível" de uma instância de classe . Isso acontece porque a pesquisa de atributo no Python (de maneira simplificada - os descritores podem ter precedência) procura por um atributo na instância - se não estiver presente na instância, o Python procurará na classe dessa instância e a pesquisa continuará. as superclasses da classe, mas não nas classes da classe. O stdlib do Python utiliza esse recurso no
abc.ABCMeta.register
método Esse recurso pode ser usado para o bem, pois os métodos relacionados à própria classe são livres para serem reutilizados como atributos de instância sem nenhum conflito (mas um método ainda entraria em conflito).
- Outra diferença, embora óbvia, é que um método declarado na metaclasse pode estar disponível em várias classes, não relacionadas de outra forma - se você tiver hierarquias de classe diferentes, não relacionadas de maneira alguma com o que elas lidam, mas desejar alguma funcionalidade comum para todas as classes , você teria que criar uma classe mixin, que teria que ser incluída como base nas duas hierarquias (por exemplo, incluindo todas as classes no registro de um aplicativo). (Nota: o mixin às vezes pode ser uma chamada melhor do que uma metaclasse)
- Um método de classe é um objeto "método de classe" especializado, enquanto um método na metaclasse é uma função comum.
Então, acontece que o mecanismo usado pelos métodos de classe é o " protocolo descritor ". Enquanto as funções normais apresentam um __get__
método que insere o self
argumento quando são recuperados de uma instância e deixa esse argumento vazio quando recuperado de uma classe, um classmethod
objeto tem um diferente __get__
, que insere a própria classe (o "proprietário") como o primeiro parâmetro nas duas situações.
Isso não faz diferenças práticas na maioria das vezes, mas se você deseja acessar o método como uma função, para adicionar dinamicamente a adição de decorador, ou qualquer outro, para um método na metaclasse meta.method
recupera a função, pronta para ser usada , enquanto você precisa usá cls.my_classmethod.__func__
-lo para recuperá-lo de um método de classe (e, em seguida, você deve criar outro classmethod
objeto e atribuí-lo de volta, se fizer alguma quebra).
Basicamente, estes são os 2 exemplos:
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)
Em outras palavras: além da diferença importante de que um método definido na metaclasse é visível a partir da instância e um classmethod
objeto não, as outras diferenças em tempo de execução parecerão obscuras e sem sentido - mas isso acontece porque o idioma não precisa ir fora do caminho com regras especiais para métodos de classe: As duas formas de declarar um método de classe são possíveis, como conseqüência do design da linguagem - uma, pelo fato de que uma classe é ela própria um objeto e outra, como uma possibilidade entre muitas, o uso do protocolo descritor que permite especializar o acesso ao atributo em uma instância e em uma classe:
O classmethod
builtin é definido no código nativo, mas poderia ser codificado em python puro e funcionaria exatamente da mesma maneira. A classe de 5 linhas abaixo pode ser usada como classmethod
decorador, sem diferenças de tempo de execução, para a representação de @classmethod" at all (though distinguishable through introspection such as calls to
instância , and even
interna, é claro):
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
E, além dos métodos, é interessante ter em mente que atributos especializados como a @property
na metaclasse funcionarão como atributos de classe especializados, da mesma forma, sem nenhum comportamento surpreendente.