Почему Python компилирует библиотеки, которые используются в script, но не script, который называется сам?
Добавление награды. Я не думаю, что это правильно ответил.
Ответ 3
педагогика
Я так люблю и ненавижу подобные вопросы, потому что там сложная смесь эмоций, мнений и образованных предположений продолжается, и люди начинают обрываться, и почему-то все теряют след фактов и, в конце концов, теряют след оригинал вопрос в целом.
Многие технические вопросы по SO имеют по крайней мере один окончательный ответ (например, ответ, который может быть проверен путем исполнения или ответ, который ссылается на авторитетный источник), но эти вопросы "почему" часто не имеют всего лишь одного окончательного ответа. На мой взгляд, есть два возможных способа окончательного ответа на вопрос "почему" в информатике:
- Указывая на исходный код, который реализует объект, вызывающий озабоченность. Это объясняет "почему" в техническом смысле: какие предпосылки необходимы, чтобы вызвать это поведение?
- Указывая на удобочитаемые артефакты (комментарии, сообщения фиксации, списки адресов электронной почты и т.д.), написанные разработчиками, участвующими в принятии этого решения. Это реальный смысл "почему" , что я предполагаю, что OP заинтересован в: почему разработчики Python сделали это, казалось бы, произвольное решение?
Второй тип ответа более сложно подкрепить, так как он требует понимания разработчиков, написавших код, особенно если нет простой в использовании публичной документации, объясняющей конкретное решение.
На сегодняшний день в этой ветке есть 7 ответов, которые сосредоточены исключительно на чтении намерений разработчиков Python, и все же есть только одна цитата во всей партии. (И он приводит раздел руководства Python, который не отвечает на вопрос OP.)
Здесь моя попытка ответить на обе стороны вопроса "почему" вместе с цитатами.
Исходный код
Каковы предпосылки, которые вызывают компиляцию .pyc? Посмотрите исходный код. (Раздражающе, у Python на GitHub нет никаких тегов релиза, поэтому я просто скажу вам, что я смотрю 715a6e
.)
В load_source_module()
есть многообещающий код в import.c:989
. Я кратко вырезал несколько фрагментов для краткости.
static PyObject *
load_source_module(char *name, char *pathname, FILE *fp)
{
// snip...
if (/* Can we read a .pyc file? */) {
/* Then use the .pyc file. */
}
else {
co = parse_source_module(pathname, fp);
if (co == NULL)
return NULL;
if (Py_VerboseFlag)
PySys_WriteStderr("import %s # from %s\n",
name, pathname);
if (cpathname) {
PyObject *ro = PySys_GetObject("dont_write_bytecode");
if (ro == NULL || !PyObject_IsTrue(ro))
write_compiled_module(co, cpathname, &st);
}
}
m = PyImport_ExecCodeModuleEx(name, (PyObject *)co, pathname);
Py_DECREF(co);
return m;
}
pathname
- это путь к модулю, а cpathname
- это один и тот же путь, но с расширением .pyc. Единственной прямой логикой является логическое sys.dont_write_bytecode
. Остальная часть логики - это просто обработка ошибок. Поэтому мы не можем найти ответ, но мы можем хотя бы увидеть, что любой код, который вызывает это, приведет к .pyc файлу в большинстве конфигураций по умолчанию. Функция parse_source_module()
не имеет никакого реального отношения к потоку выполнения, но я покажу ее здесь, потому что я вернусь к ней позже.
static PyCodeObject *
parse_source_module(const char *pathname, FILE *fp)
{
PyCodeObject *co = NULL;
mod_ty mod;
PyCompilerFlags flags;
PyArena *arena = PyArena_New();
if (arena == NULL)
return NULL;
flags.cf_flags = 0;
mod = PyParser_ASTFromFile(fp, pathname, Py_file_input, 0, 0, &flags,
NULL, arena);
if (mod) {
co = PyAST_Compile(mod, pathname, NULL, arena);
}
PyArena_Free(arena);
return co;
}
Важным аспектом здесь является то, что функция анализирует и компилирует файл и возвращает указатель на байт-код (если он успешный).
Теперь мы все еще в тупике, поэтому давайте подходим к этому под новым углом. Как Python загружает его аргумент и выполняет его? В pythonrun.c
существует несколько функций для загрузки кода из файла и его выполнения. PyRun_AnyFileExFlags()
может обрабатывать как интерактивные, так и неинтерактивные файловые дескрипторы. Для интерактивных файловых дескрипторов он делегирует PyRun_InteractiveLoopFlags()
(это REPL) и для неинтерактивных файловых дескрипторов, он делегирует PyRun_SimpleFileExFlags()
. PyRun_SimpleFileExFlags()
проверяет, заканчивается ли имя файла в .pyc
. Если это так, то он вызывает run_pyc_file()
, который непосредственно загружает скомпилированный байт-код из файлового дескриптора и затем запускает его.
В более общем случае (т.е. .py
файл в качестве аргумента) PyRun_SimpleFileExFlags()
вызывает PyRun_FileExFlags()
. Здесь мы начинаем находить наш ответ.
PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename, int start, PyObject *globals,
PyObject *locals, int closeit, PyCompilerFlags *flags)
{
PyObject *ret;
mod_ty mod;
PyArena *arena = PyArena_New();
if (arena == NULL)
return NULL;
mod = PyParser_ASTFromFile(fp, filename, start, 0, 0,
flags, NULL, arena);
if (closeit)
fclose(fp);
if (mod == NULL) {
PyArena_Free(arena);
return NULL;
}
ret = run_mod(mod, filename, globals, locals, flags, arena);
PyArena_Free(arena);
return ret;
}
static PyObject *
run_mod(mod_ty mod, const char *filename, PyObject *globals, PyObject *locals,
PyCompilerFlags *flags, PyArena *arena)
{
PyCodeObject *co;
PyObject *v;
co = PyAST_Compile(mod, filename, flags, arena);
if (co == NULL)
return NULL;
v = PyEval_EvalCode(co, globals, locals);
Py_DECREF(co);
return v;
}
Существенным моментом здесь является то, что эти две функции в основном выполняют ту же задачу, что и импортер load_source_module()
и parse_source_module()
. Он вызывает синтаксический анализатор для создания AST из исходного кода Python, а затем вызывает компилятор для создания байтового кода.
Значит, эти блоки кода избыточны или служат для разных целей? Разница в том, что один блок загружает модуль из файла, а другой блок принимает модуль в качестве аргумента. Этот аргумент модуля - в этом случае - модуль __main__
, который создается ранее в процессе инициализации с использованием низкоуровневой C-функции. Модуль __main__
не проходит через большинство путей кода импорта нормального модуля, потому что он настолько уникален, и в качестве побочного эффекта он не проходит через код, который создает файлы .pyc
.
Подводя итог: причина, по которой модуль __main__
не компилируется в .pyc, заключается в том, что он не "импортирован". Да, он появляется в sys.modules, но он получает там через очень различный путь кода, чем импортируются реальные модули.
Назначение разработчика
Итак, теперь мы можем видеть, что поведение больше связано с дизайном Python, чем с любым явно выраженным обоснованием в исходном коде, но это не отвечает на вопрос, является ли это преднамеренным решением или просто побочный эффект, который никого не беспокоит, чтобы его стоило изменить. Одним из преимуществ открытого исходного кода является то, что, когда мы нашли интересующий нас исходный код, мы можем использовать VCS, чтобы помочь вернуться к решениям, которые привели к настоящей реализации.
Одна из основных строк кода здесь (m = PyImport_AddModule("__main__");
) относится к 1990 году и была написана самим BDFL, Гвидо. Он был изменен за прошедшие годы, но изменения являются поверхностными. Когда он был впервые записан, основной модуль для аргумента script был инициализирован следующим образом:
int
run_script(fp, filename)
FILE *fp;
char *filename;
{
object *m, *d, *v;
m = add_module("`__main__`");
if (m == NULL)
return -1;
d = getmoduledict(m);
v = run_file(fp, filename, file_input, d, d);
flushline();
if (v == NULL) {
print_error();
return -1;
}
DECREF(v);
return 0;
}
Это существовало до того, как файлы .pyc
были даже введены в Python! Неудивительно, что дизайн в то время не учитывал компиляцию для аргументов script. сообщение фиксации загадочно говорит:
"Компиляция" версии
Это был один из нескольких десятков коммитов за 3-дневный период... похоже, что Гвидо глубоко входил в хакерство/рефакторинг, и это была первая версия, которая вернулась к стабильности. Это обязательство даже предшествует созданию списка рассылки Python-Dev примерно на пять лет!
Сохранение скомпилированного байт-кода было введено через 6 месяцев, в 1991 году.
Это все еще предшествует подаче списка, поэтому мы не имеем реального представления о том, что думал Гвидо. Похоже, он просто подумал, что импортер - лучшее место для захвата в целях кэширования байтекодов. Считал ли он, что идея делать то же самое для __main__
неясно: либо это не произошло с ним, либо он думал, что это больше проблем, чем того стоило.
Я не могу найти любые ошибки на bugs.python.org, связанные с кэшированием байт-кодов для основного модуля, а также не могу найти любые сообщения в списке рассылки об этом, так что, по-видимому, никто другой не считает, что стоит попробовать его добавить.
Подводя итог: причина, по которой все модули скомпилированы в .pyc
, за исключением __main__
, заключается в том, что это причуда истории. Дизайн и реализация для того, как __main__
работает, испекли в код до .pyc
файлов даже существовал. Если вы хотите узнать больше, вам нужно будет отправить письмо по электронной почте Guido и спросить.
Гленн Мейнард отвечает:
Никто, кажется, не хочет говорить об этом, но я уверен, что ответ просто: нет веских оснований для такого поведения.
Я согласен на 100%. Имеются косвенные доказательства в поддержку этой теории, и никто в этой теме не предоставил ни единого доказательства доказательств для поддержки какой-либо другой теории. Я поддержал ответ Гленна.