Поскольку классы являются экземплярами метакласса, неудивительно, что «метод экземпляра» в метаклассе будет вести себя как метод класса.
Однако, да, есть различия - и некоторые из них более чем семантические:
- Самое важное отличие состоит в том, что метод в метаклассе не «виден» из экземпляра класса . Это происходит потому, что поиск атрибута в Python (в упрощенном виде - дескрипторы могут иметь приоритет) ищет атрибут в экземпляре - если он отсутствует в экземпляре, Python затем ищет в классе этого экземпляра, а затем поиск продолжается суперклассы класса, но не на классы класса. Stdlib Python использует эту функцию в
abc.ABCMeta.register
методе. Эта функция может быть использована навсегда, поскольку методы, связанные с самим классом, могут свободно использоваться в качестве атрибутов экземпляра без какого-либо конфликта (но метод все равно будет конфликтовать).
- Другое отличие, хотя и очевидное, состоит в том, что метод, объявленный в метаклассе, может быть доступен в нескольких классах, не связанных между собой - если у вас разные иерархии классов, они вообще не связаны в том, с чем они имеют дело, но вам нужна некоторая общая функциональность для всех классов. вам нужно будет создать класс mixin, который должен быть включен как базовый в обе иерархии (скажем, для включения всех классов в реестр приложений). (NB. Иногда миксин может быть лучше, чем метакласс)
- Classmethod - это специализированный объект "classmethod", а метод в метаклассе - обычная функция.
Таким образом, случается, что механизм, который используют методы класса, является « протоколом дескриптора ». В то время как обычные функции имеют __get__
метод, который вставляет self
аргумент, когда они извлекаются из экземпляра, и оставляет этот аргумент пустым, когда извлекается из класса, classmethod
объект имеет другой объект __get__
, который вставит сам класс («владельца») в качестве объекта. Первый параметр в обеих ситуациях.
В большинстве случаев это не имеет практических различий, но если вам нужен доступ к методу как функции, для целей добавления к нему динамического добавления декоратора или любого другого, для метода в метаклассе meta.method
извлекается функция, готовая для использования , в то время как вы должны использовать его cls.my_classmethod.__func__
для извлечения из метода класса (а затем вам нужно создать другой classmethod
объект и назначить его обратно, если вы сделаете некоторую обертку).
По сути, это 2 примера:
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)
Другими словами: помимо важного отличия того, что метод, определенный в метаклассе, виден из экземпляра, а classmethod
объект нет, другие различия во время выполнения будут казаться неясными и бессмысленными - но это происходит потому, что языку не нужно идти по-своему с особыми правилами для методов классов: как следствие разработки языка возможны оба способа объявления метода классов - один, потому что класс сам является объектом, а другой, как возможность среди многих, использование протокола дескриптора, который позволяет специализировать доступ к атрибутам в экземпляре и в классе:
classmethod
Встроенный определяются в машинном коде, но он может просто быть закодирован в чистом питоне и будет работать в точно так же. 5-строчный класс ниже может быть использован в качестве classmethod
декоратора (без различий во времени выполнения по @classmethod" at all (though distinguishable through introspection such as calls to
сравнению со встроенным представлением , and even
isinstance):
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
Помимо методов, интересно иметь в виду, что специализированные атрибуты, такие как @property
метакласс, будут работать как атрибуты специализированных классов, точно так же, без какого-либо удивительного поведения.