Кто звонит в метакласс
Это фактически связано с обсуждением здесь на SO.
Краткая версия
def meta(name, bases, class_dict)
return type(name, bases, class_dict)
class Klass(object):
__metaclass__ = meta
meta()
вызывается при выполнении Klass
объявления класса.
Какая часть внутреннего кода (python internal) на самом деле вызывает meta()
?
Длинная версия
Когда класс объявлен, некоторый код должен выполнить соответствующие проверки атрибутов и посмотреть, есть ли __metaclass__
, объявленный в типе. Если такой существует, он должен выполнить вызов метода в этом метаклассе с хорошо известными атрибутами (class_name, bases, class_dict)
. Мне не совсем понятно, какой код отвечает за этот вызов.
Я сделал некоторое копание в CPython (см. ниже), но мне очень хотелось бы иметь что-то ближе к определенному ответу.
Вариант 1: Вызывается напрямую
Метаклассовый вызов жестко связан с разбором классов. Если да, есть ли какие-либо доказательства для этого?
Вариант 2: он вызывается type.__new__()
Код в type_call()
вызывает type_new()
, который в свою очередь вызывает _PyType_CalculateMetaclass()
. Это говорит о том, что разрешение метакласса действительно выполняется во время вызова type()
при попытке выяснить, какое значение нужно вернуть из type()
Это будет соответствовать понятию, что "класс" является "вызываемым, возвращающим объект".
Вариант 3: Что-то другое
Все мои догадки могут быть совершенно неправильными, конечно.
Некоторые примеры, с которыми мы столкнулись в чате:
Пример 1:
class Meta(type):
pass
class A:
__metaclass__ = Meta
A.__class__ == Meta
Это то, что возвращает Meta.__new__()
, поэтому это кажется законным. Метакласс ставит себя как A.__class__
Пример 2:
class Meta(type):
def __new__(cls, class_name, bases, class_dict):
return type(class_name, bases, class_dict)
class A(object):
__metaclass__ = Meta
A.__class__ == type
Изменить 2: исправить исходную версию, правильно вывести Meta
из type
.
Кажется, все в порядке, но я не уверен, что это делает то, что я думаю. Также: Каков канонический метод, чтобы заставить его вести себя, как в примере 1?
Редактирование 3: Использование type.__new__(...)
похоже на работу, как ожидалось, что также кажется в пользу варианта 2.
Может ли кто-нибудь с более глубоким знанием внутренней магии питона просветить меня?
Изменить: A для довольно кратких праймеров на метаклассах: http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/. Также есть некоторые действительно красивые диаграммы, ссылки, а также подчеркивает различия между python 2 и 3.
Изменить 3: для Python 3 есть хороший ответ. Python 3 использует __build_class__
для создания объекта класса. Путь к коду - все равно - другой в Python 2.
Ответы
Ответ 1
Вы можете найти ответ относительно легко. Сначала найдем код операции для создания класса.
>>> def f():
class A(object):
__metaclass__ = type
>>> import dis
>>> dis.dis(f)
2 0 LOAD_CONST 1 ('A')
3 LOAD_GLOBAL 0 (object)
6 BUILD_TUPLE 1
9 LOAD_CONST 2 (<code object A at 0000000001EBDA30, file "<pyshell#3>", line 2>)
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_FAST 0 (A)
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
Итак, код операции BUILD_CLASS
. Теперь давайте искать источник для этого термина (легко сделать в github mirror).
Вы получите несколько результатов, но наиболее интересным из них является Python/ceval.c
, который объявляет функцию static PyObject * build_class(PyObject *, PyObject *, PyObject *);
и имеет оператор case для BUILD_CLASS
, Поиск по файлу, и вы можете найти определение функции BUILD_CLASS
, начиная со строки 4430. И в строке 4456 мы находим бит кода, который вы ищете:
result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods,
NULL);
Таким образом, ответ - это метакласс, который разрешен и вызывается функцией, которая отвечает за выполнение кода операции BUILD_CLASS
.
Ответ 2
В Python 3 метакласс вызывается в коде для __build_class__
встроенной функции (которая вызывается для обработки операторов class
). Эта функция является новой в Python 3, и эквивалентная функция C build_class
в Python 2 не публикуется публично на уровне Python. Однако вы можете найти источник в python/ceval.c
В любом случае, соответствующий вызов объекта metaclass в реализации Python 3 __build_class__
:
cls = PyEval_CallObjectWithKeywords(meta, margs, mkw);
Переменная meta
- это метакласс (либо type
, либо другой метакласс, найденный из аргумента или из типа базового класса). margs
- это кортеж с позиционными аргументами (name, bases, dct)
, а mkw
- словарь с аргументами ключевого слова для метакласса (только для Python 3).
Код Python 2 делает нечто похожее:
result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods,
NULL);
Ответ 3
Мета-классы "создаются" интерпретатором при выполнении определения класса.