Хитрость, выявленная
Обновление 2: Исходя из поведения, тот факт, что M0.__call__
вызывается ниже , должен быть побочным эффектом этой строки в builtin__build_class
в исходник CPython (Python/bltinmodule.c
).
Чтобы определить класс, имеющий метакласс, мы называем метаклассы __prepare__
, __new__
и __init__
как обычно. Это создает класс - в примере ниже, Meta
- который можно вызывать, но его внутренний слот PyFunction_GET_CODE
указывает не на его собственный __call__
, а скорее на его метакласс __call__
. Следовательно, если мы вызываем Meta()
(объект метакласса), мы вызываем M0.__call__
:
print("call Meta")
print("Meta returns:", Meta('name', (), {}))
print("finished calling Meta")
производит:
call Meta
M0 __call__: mmcls=<class '__main__.Meta'>, args=('name', (), {}), kwargs={}
Meta __new__: mcs=<class '__main__.Meta'>, name='name', bases=(), attrs={}, kwargs={}
Meta __init__: mcs=<class '__main__.name'>, name='name', bases=(), attrs={}, kwargs={}
Meta returns: <class '__main__.name'>
finished calling Meta
Другими словами, мы видим, что Meta
действует как type
, но это (довольно волшебно и не очень хорошо задокументировано) вызывает M0.__call__
. Это, без сомнения, связано с поиском __call__
в типе класса, а не в экземпляре класса (и действительно, нет экземпляра, кроме того, который мы создаем). На самом деле это общий случай: он выпадает из факта, что мы вызываем __call__
для типа из Meta
, а тип Meta
равен M0
:
print("type(Meta) =", type(Meta))
печать:
type(Meta) = <class '__main__.M0'>
, который объясняет, откуда это происходит. (Я все еще думаю, что это должно быть подчеркнуто в документации, которая также должна описывать ограничения на типизацию метаклассов - они применяются в _calculate_winner
в Lib/types.py
и, как код C, в _PyType_CalculateMetaclass
в объектах / typeobject.c .)
Обновлен оригинальный ответ
Я не знаю, откуда взялась ваша диаграмма, но это неверно. ОБНОВЛЕНИЕ: На самом деле вы можете иметь метакласс для своего метакласса; смотрите ответ jsbueno , и я обновил пример ниже. Новые предложения / текст выделены жирным шрифтом , , за исключением заключительного раздела, описывающего мое недоумение по поводу очевидного отсутствия документации.
В вашем существующем коде метакласса есть хотя бы одна ошибка. Наиболее важно, что __prepare__
должен быть методом класса. См. Также Использование метода __call__ метакласса вместо __new __? и PEP 3115 . И, чтобы использовать мета-мета-класс, ваш метакласс должен иметь собственный метакласс, не базовый класс.
Ответ Криса содержит правильные определения. Но есть некоторые неудачные асимметрии между аргументами метода метакласса и аргументами метода класса, которые я проиллюстрирую ниже.
Еще одна вещь, которая может помочь: обратите внимание, что метод метакласса __prepare__
вызывается перед созданием любых экземпляров класса B
: , он вызывается при определении самой class B
. Чтобы показать это, вот исправленный метакласс-класс. Я также добавил еще несколько иллюстраторов. Я также добавил мета-метакласс, основываясь на ответе jsbueno. Я не могу найти официальную документацию Python по этому вопросу, но я обновил вывод ниже.
class M0(type):
def __call__(mmcls, *args, **kwargs):
print("M0 __call__: mmcls={!r}, "
"args={!r}, kwargs={!r}".format(mmcls, args, kwargs))
return super().__call__(*args, **kwargs)
class Meta(type, metaclass=M0):
def __call__(cls, *args, **kwargs):
print("Meta __call__: cls={!r}, "
"args={!r}, kwargs={!r}".format(cls, args, kwargs))
return super().__call__(*args, **kwargs)
def __new__(mcs, name, bases, attrs, **kwargs):
print("Meta __new__: mcs={!r}, name={!r}, bases={!r}, "
"attrs={!r}, kwargs={!r}".format(mcs, name, bases, attrs, kwargs))
return super().__new__(mcs, name, bases, attrs)
def __init__(mcs, name, bases, attrs, **kwargs):
print("Meta __init__: mcs={!r}, name={!r}, bases={!r}, "
"attrs={!r}, kwargs={!r}".format(mcs, name, bases, attrs, kwargs))
super().__init__(name, bases, attrs, **kwargs)
@classmethod
def __prepare__(cls, name, bases, **kwargs):
print("Meta __prepare__: name={!r}, "
"bases={!r}, kwargs={!r}".format(name, bases, kwargs))
return {}
print("about to create class A")
class A(metaclass=Meta): pass
print("finished creating class A")
print("about to create class B")
class B(A, metaclass=Meta, foo=3):
@staticmethod
def __new__(cls, *args, **kwargs):
print("B __new__: cls={!r}, "
"args={!r}, kwargs={!r}".format(cls, args, kwargs))
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print("B __init__: args={!r}, kwargs={!r}, ".format(args, kwargs))
print("finished creating class B")
print("about to create instance b = B()")
b = B('hello', bar=7)
print("finished creating instance b")
Теперь давайте посмотрим, что произойдет, когда я запущу это, и разберем каждый фрагмент:
$ python3.6 meta.py
about to create class A
Meta __prepare__: name='A', bases=(), kwargs={}
M0 __call__: mmcls=<class '__main__.Meta'>, args=('A', (), {'__module__': '__main__', '__qualname__': 'A'}), kwargs={}
Meta __new__: mcs=<class '__main__.Meta'>, name='A', bases=(), attrs={'__module__': '__main__', '__qualname__': 'A'}, kwargs={}
Meta __init__: mcs=<class '__main__.A'>, name='A', bases=(), attrs={'__module__': '__main__', '__qualname__': 'A'}, kwargs={}
finished creating class A
Чтобы создать сам класс A
, Python сначала вызывает метакласс __prepare__
, передавая ему имя класса (A
), список базовых классов (пустой кортеж - он называется списком, но на самом деле кортеж), и любые ключевые аргументы (нет). Как отмечает PEP 3115, метакласс должен возвращать словарь или dict
-подобный объект; это просто возвращение пустого словаря, так что нам здесь хорошо.
(я здесь не печатаю cls
, но если вы это сделаете, вы увидите, что это просто <class '__main__.Meta'>
.)
Затем, получив словарь от __prepare__
, Python сначала вызывает метамета __call__
, то есть M0.__call__
, передавая весь набор аргументов как кортеж args
. Затем он заполняет словарь с поддержкой __prepare__
всеми атрибутами для класса, передавая его как attrs
метаклассу __new__
и __init__
. Если вы напечатаете id
словаря, возвращенного из __prepare__
и переданного в __new__
и __init__
, вы увидите, что все они совпадают.
* +1121 * Si,У класса
A
нет методов или элементов данных, здесь мы видим только магические атрибуты
__module__
и
__qualname__
. Мы также не видим ключевых аргументов, поэтому теперь давайте перейдем к созданию класса
B
:
about to create class B
Meta __prepare__: name='B', bases=(<class '__main__.A'>,), kwargs={'foo': 3}
M0 __call__: mmcls=<class '__main__.Meta'>, args=('B', (<class '__main__.A'>,), {'__module__': '__main__', '__qualname__': 'B', '__new__': <staticmethod object at 0x800ad0a58>, '__init__': <function B.__init__ at 0x800ad2840>, '__classcell__': <cell at 0x800a749d8: empty>}), kwargs={'foo': 3}
Meta __new__: mcs=<class '__main__.Meta'>, name='B', bases=(<class '__main__.A'>,), attrs={'__module__': '__main__', '__qualname__': 'B', '__new__': <staticmethod object at 0x800ad0940>, '__init__': <function B.__init__ at 0x800ad27b8>, '__classcell__': <cell at 0x800a745b8: empty>}, kwargs={'foo': 3}
Meta __init__: mcs=<class '__main__.B'>, name='B', bases=(<class '__main__.A'>,), attrs={'__module__': '__main__', '__qualname__': 'B', '__new__': <staticmethod object at 0x800ad0940>, '__init__': <function B.__init__ at 0x800ad27b8>, '__classcell__': <cell at 0x800a745b8: Meta object at 0x802047018>}, kwargs={'foo': 3}
finished creating class B
Этот довольно интересный. Теперь у нас есть один базовый класс, а именно __main__.A
. Класс B
также определяет несколько методов (__new__
и __init__
), и мы видим их в словарях attrs
, передаваемых метаклассам __new__
и __init__
(которые, помните, являются только что заполненными в настоящее время) словарь, возвращаемый метаклассом __prepare__
). Как и прежде, передача происходит через метамета-класс M0.__call__
. Мы также видим один ключевой аргумент повсюду, {'foo': 3}
. В словаре атрибутов мы также можем наблюдать волшебную запись __classcell__
: см. Предоставьте пример __classcell__ для метакласса Python 3.6 для краткого описания того, о чем идет речь, но, скорее всего, супер -короткий, это для того, чтобы заставить super()
работать.
Аргумент ключевого слова передается всем трем методам метакласса, а также мета-мета-классу. (Я не совсем уверен, почему. Обратите внимание, что изменение словаря в любом методе metaclass никак не влияет на него, так как это копия каждого аргумента исходного ключевого слова каждый раз. Однако мы может изменить его в мета-мета-классе: добавьте kwargs.pop('foo', None)
к M0.__call__
, чтобы наблюдать это. )
Теперь, когда у нас есть классы A
и B
, мы можем приступить к процессу создания фактического экземпляра класса B
. Теперь мы видим вызванный метакласс __call__
(не мета-мета-класс):
about to create instance b = B()
Meta __call__: cls=<class '__main__.B'>, args=('hello',), kwargs={'bar': 7}
Можно изменить переданный args
или kwargs
, но я этого не делаю; приведенный выше пример кода вызывает type.__call__(cls, *args, **kwargs)
(через магию super().__call__
). Это, в свою очередь, вызывает B.__new__
и B.__init__
:
B __new__: cls=<class '__main__.B'>, args=('hello',), kwargs={'bar': 7}
B __init__: args=('hello',), kwargs={'bar': 7},
finished creating instance b
, который завершает реализацию нового экземпляра класса B
, который мы затем связываем с именем b
.
Обратите внимание, что B.__new__
говорит:
return super().__new__(cls)
поэтому мы вызываем object.__new__
для создания экземпляра - это более или менее требование для всех версий Python; вы можете «обмануть» только когда вернете одноэлементный экземпляр (в идеале тот, который не подлежит изменению). type.__call__
вызывает B.__init__
для этого объекта, передавая аргументы и ключевые слова-аргументы, которые мы передали. Если мы заменим Meta
s __call__
на:
def __call__(cls, *args, **kwargs):
print("Meta __call__: cls={!r}, "
"args={!r}, kwargs={!r}".format(cls, args, kwargs))
return object.__new__(cls)
мы увидим, что B.__new__
и B.__init__
никогда не называются:
about to create instance b = B()
Meta __call__: cls=<class '__main__.B'>, args=('hello',), kwargs={'bar': 7}
finished creating instance b
По сути, это создаст бесполезный / неинициализированный экземпляр b
. Поэтому очень важно, чтобы метод метакласса __call__
вызывал базовый класс __init__
, обычно вызывая type.__call__
через super().__call__
. Если базовый класс имеет __new__
, метакласс должен сначала вызвать его, опять же, обычно вызывая type.__call__
.
Цитировать раздел 3.3.3.6:
Как только пространство имен класса заполняется путем выполнения тела класса, объект класса создается путем вызова metaclass(name, bases, namespace, **kwds)
(дополнительные ключевые слова, переданные здесь, такие же, как переданные в __prepare__
).
Это объясняет вызов Meta.__call__
при создании b
как экземпляра класса B
, но не факт, что Python сначала вызывает M0.__call__
перед вызовом Meta.__new__
и Meta.__init__
при создании классов A
и B
сами.
В следующем абзаце упоминается запись __classcell__
; затем описывается использование хуков __set_name__
и __init_subclass__
. Ничто здесь не говорит нам, как или почему Python вызывает M0.__call__
на данный момент.
Ранее в разделах с 3.3.3.3 по 3.3.3.5 в документации описывался процесс определения метакласса, подготовки пространства имен класса и выполнения тела класса. Именно здесь должно быть описано действие мета-метакласса , но это не так.
Несколько дополнительных разделов описывают несколько дополнительных ограничений.Одним из важных является 3.3.10, в котором говорится о том, как специальные методы находят через тип объекта, минуя как обычный поиск атрибутов-членов, так и даже (иногда) метакласс getattribute, говоря:
Обход *Таким образом, механизм 1242 * обеспечивает значительные возможности для оптимизации скорости в интерпретаторе за счет некоторой гибкости в обработке специальных методов (специальный метод должен быть должен быть установлен на самом объекте класса, чтобыпоследовательно вызывается интерпретатором).
Обновление 2: В этом-то и заключается секрет уловки: специальный метод __call__
находится через тип типа.Если у метакласса есть метакласс, мета-метакласс предоставляет слот __call__
;в противном случае тип метакласса - type
, поэтому слот __call__
- type.__call__
.