В обычном коде Python единственный вызов, который фактически создает объект класса в памяти с двоичной структурой и всеми необходимыми полями, это type.__new__
. При использовании функции, вызываемой как метакласс, нужно просто создать экземпляр type
или создать что-то, что вообще не является классом (см. Ниже).
Можно было бы создать другой «базовый метакласс», используя собственный код C или даже в чистом Python, вызывая функции выделения памяти для собственной ОС и заполняя ту же структуру , определенную для «объекта типа» , - однако, это будет выполнять только ту же задачу, которую уже выполняет type.__new__
, так что это будет сложное и подверженное ошибкам повторное изобретение колеса, которое не может быть улучшено в дальнейшем, поскольку полученная двоичная компоновка должна быть такой же как определено в этой структуре (т. е. указатели на методы, реализующие «магические функции», такие как __init__
, __geitem__
и т. д., должны иметь те же смещения, что и в этой структуре)
(Дополнительные поля и даже изысканные значения для данных в этой структуре могут быть выполнены типом наследования кода - следовательно, обычным метаклассом)
Как вы говорите, любой вызываемый объект, даже простая функция, может быть указан как «метакласс» в теле класса (как в Python 2, так и в Python 3), однако, независимо от того, что делает этот вызываемый объект, в определенный момент он придется вызывать type.__new__
(функции делают это косвенно, вызывая type
). Однако, как только класс построен, он сам является объектом Python, который является экземпляром класса - тип класса (что входит в его атрибут __class__
) является его эффективным метаклассом. Если метакласс, указанный в коде как атрибут __metaclass__
или как метакласс kwarg в Python 3, является подклассом type
, это также будет собственно метакласс. Иначе, если это обычная функция, которая просто действует как фабрика классов, вызывающая type
в своем теле, эффективный метакласс это просто тип.
Другими словами:
In [1]: def function_meta(name, bases, ns):
...: return type(name, bases, ns)
...:
In [2]: class test(metaclass=function_meta):
...: pass
...:
In [3]: type(test)
Out[3]: type
In [4]: class ClassMeta(type):
...: pass
...:
...:
In [5]: class test2(metaclass=ClassMeta):
...: pass
...:
In [6]: type(test2)
Out[6]: __main__.ClassMeta
Итак, почему нельзя использовать класс, который не наследуется от type
?
Проблема не в том, что указанный метакласс не наследуется от типа - можно использовать любой вызываемый объект, как показано выше.
Ошибка, возникающая при попытке сделать это, вызвана вызовом type.__new__
с не подклассом типа в качестве первого параметра:
In [11]: class InheritFromOther(object):
...: def __new__(mcls, name, bases, ns):
...: return type.__new__(mcls, name, bases, ns)
...: # the line above is the one that errors - as
...: # mcls here is not a subclass of type.
...: # type(name, bases, ns) would work.
In [12]: class test4(metaclass=InheritFromOther):
...: pass
...:
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-bf5b1fa4bb7f> in <module>()
----> 1 class test4(metaclass=InheritFromOther):
2 pass
<ipython-input-11-62d1fe46490b> in __new__(mcls, name, bases, ns)
1 class InheritFromOther(object):
2 def __new__(mcls, name, bases, ns):
----> 3 return type.__new__(mcls, name, bases, ns)
4
TypeError: type.__new__(InheritFromOther): InheritFromOther is not a subtype of type
Теперь, если мы вызовем type.__new__
с допустимым подклассом типа в качестве первого параметра:
In [13]: class InheritFromOtherTake2(object):
...: def __new__(mcls, name, bases, ns):
...: return type.__new__(type, name, bases, ns)
...:
In [14]: class test5(metaclass=InheritFromOtherTake2):
...: pass
...:
In [15]: type(test5)
Out[15]: type
Просто для полноты, как упомянуто выше, действительно возможно, чтобы вызываемый объект, используемый в качестве метакласса, возвращал что-то отличное от экземпляра типа (или его подкласса). В этом случае объект, полученный из тела оператора class
, просто не будет классом, но что бы это ни вызывало:
In [7]: def dict_maker_meta(name, bases, ns):
...: return ns
...:
In [8]: class test3(metaclass=dict_maker_meta):
...: a = 1
...: b = 2
...: c = 'test'
...:
In [9]: type(test3)
Out[9]: dict
In [10]: test3
Out[10]:
{'__module__': '__main__',
'__qualname__': 'test3',
'a': 1,
'b': 2,
'c': 'test'}