Что касается вопроса 1, я думаю, что «метакласс» класса cls
следует понимать как type(cls)
. Такой способ понимания совместим с сообщением об ошибке Python в следующем примере:
>>> class Meta1(type): pass
...
>>> class Meta2(type): pass
...
>>> def metafunc(name, bases, methods):
... if methods.get('version') == 1:
... return Meta1(name, bases, methods)
... return Meta2(name, bases, methods)
...
>>> class C1:
... __metaclass__ = metafunc
... version = 1
...
>>> class C2:
... __metaclass__ = metafunc
... version = 2
...
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
То есть, согласно сообщению об ошибке, метакласс класса является классом, даже если вызываемый объект, используемый для создания класса, может быть чем угодно.
Что касается второго вопроса, действительно с подклассом типа, используемым в качестве метакласса, вы можете делать то же самое, что и с любым другим вызываемым объектом. В частности, возможно, что он выдает что-то, что не является его экземпляром:
>>> class Mockup(type):
... def __new__(cls, name, bases, methods):
... return Meta1(name, bases, methods)
...
>>> class Foo:
... __metaclass__ = Mockup
...
>>> type(Foo)
<class '__main__.Meta1'>
>>> isinstance(Foo, Mockup)
False
>>> Foo.__metaclass__
<class '__main__.Mockup'>
Что касается того, почему Python дает свободу использования любого вызываемого объекта: предыдущий пример показывает, что на самом деле не имеет значения, является ли вызываемый объект типом или нет.
Кстати, вот забавный пример: можно закодировать метаклассы, которые сами по себе имеют метакласс, отличный от type
--- назовем его метаметаклассом. Метаметакласс реализует то, что происходит при вызове метакласса. Таким образом можно создать класс с двумя базами, чьи метаклассы не являются подклассами друг друга (сравните с сообщением об ошибке Python в приведенном выше примере!). Действительно, только метакласс результирующего класса является подклассом метакласса баз, и этот метакласс создается на лету:
>>> class MetaMeta(type):
... def __call__(mcls, name, bases, methods):
... metabases = set(type(X) for X in bases)
... metabases.add(mcls)
... if len(metabases) > 1:
... mcls = type(''.join([X.__name__ for X in metabases]), tuple(metabases), {})
... return mcls.__new__(mcls, name, bases, methods)
...
>>> class Meta1(type):
... __metaclass__ = MetaMeta
...
>>> class Meta2(type):
... __metaclass__ = MetaMeta
...
>>> class C1:
... __metaclass__ = Meta1
...
>>> class C2:
... __metaclass__ = Meta2
...
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
...
>>> type(C3)
<class '__main__.Meta1Meta2'>
Что менее интересно: предыдущий пример не будет работать в Python 3. Если я правильно понимаю, Python 2 создает класс и проверяет, является ли его метакласс подклассом всех его баз, тогда как Python 3 сначала проверяет, существует ли одна база, метакласс которой является суперклассом метаклассов всех остальных баз, и только затем создает новый класс. Это регресс, с моей точки зрения. Но это будет темой нового вопроса, который я собираюсь опубликовать...
Изменить: новый вопрос: здесь
person
Simon King
schedule
13.10.2016