Подтвердить что ты не робот

Почему Python компилирует модули, но не работает script?

Почему Python компилирует библиотеки, которые используются в script, но не script, который называется сам?

Например,

Если есть main.py и module.py, а Python запускается, выполняя python main.py, будет скомпилированный файл module.pyc, но не один для основного. Почему?

Edit

Добавление награды. Я не думаю, что это правильно ответил.

  • Если ответ представляет собой потенциальные разрешения на доступ к диску для каталога main.py, почему модули компиляции Python? Они так же вероятны (если не чаще) появляться в том месте, где у пользователя нет доступа на запись. Python может скомпилировать main, если он доступен для записи, или, альтернативно, в другом каталоге.

  • Если причина в том, что преимущества будут минимальными, рассмотрим ситуацию, когда script будет использоваться много раз (например, в приложении CGI).

4b9b3361

Ответ 1

Файлы скомпилированы при импорте. Это не безопасность. Просто, если вы импортируете его, python сохраняет результат. См. этот пост от Fredrik Lundh на Effbot.

>>>import main
# main.pyc is created

При запуске script python будет не использовать файл *.pyc. Если у вас есть еще одна причина, по которой вы хотите предварительно скомпилировать script, вы можете использовать модуль compileall.

python -m compileall .

compileall Использование

python -m compileall --help
option --help not recognized
usage: python compileall.py [-l] [-f] [-q] [-d destdir] [-x regexp] [directory ...]
-l: don't recurse down
-f: force rebuild even if timestamps are up-to-date
-q: quiet operation
-d destdir: purported directory name for error messages
   if no directory arguments, -l sys.path is assumed
-x regexp: skip files matching the regular expression regexp
   the regexp is searched for in the full path of the file

Ответы на вопрос Редактировать

  • Если ответ является потенциальным разрешением на доступ к диску для каталога main.py, почему модули компиляции Python?

    Модули и скрипты обрабатываются одинаково. Импортирование - это то, что запускает вывод для сохранения.

  • Если причина в том, что преимущества будут минимальными, рассмотрим ситуацию, когда script будет использоваться много раз (например, в приложении CGI).

    Использование compileall не решает этого. Скрипты, выполняемые python, не будут использовать *.pyc, если явно не вызывать. У этого есть отрицательные побочные эффекты, о чем говорится в Гленн Мейнард в его ответе.

    Пример, приведенный в приложении CGI, должен быть действительно рассмотрен с использованием такой техники, как FastCGI. Если вы хотите устранить накладные расходы на компиляцию вашего script, вам может потребоваться устранить накладные расходы при запуске python, не говоря уже о переполнении подключения к базе данных.

    Можно использовать легкий бутстрап script или даже python -c "import script", но они имеют сомнительный стиль.

Гленн Мейнард дал некоторое вдохновение для исправления и улучшения этого ответа.

Ответ 2

Никто, кажется, не хочет говорить об этом, но я уверен, что ответ просто: нет веских оснований для такого поведения.

Все приведенные до сих пор причины принципиально неверны:

  • В главном файле нет ничего особенного. Он загружается как модуль и отображается в sys.modules, как и любой другой модуль. Запуск основного script - это не что иное, как импорт его с именем модуля __main__.
  • Нет проблем с невозможностью сохранения .pyc файлов из-за каталогов только для чтения; Python просто игнорирует его и движется дальше.
  • Преимущество кэширования script такое же, как и кэширование любого модуля: не тратить время на перекомпиляцию script при каждом запуске. Документы подтверждают это явно ( "Таким образом, время запуска script может быть уменьшено..." ).

Еще одна проблема: если вы запустите python foo.py и foo.pyc, она не будет использоваться. Вы должны явно сказать python foo.pyc. Это очень плохая идея: это означает, что Python не будет автоматически перекомпилировать файл .pyc, если он не синхронизируется (из-за изменения файла .py), поэтому изменения в .py файле не будут использоваться, пока вы вручную не перекомпилируете его, Он также завершится неудачно с RuntimeError, если вы обновите Python, а формат файла .pyc больше не совместим, что происходит регулярно. Как правило, все это обрабатывается прозрачно.

Вам не нужно перемещать script в фиктивный модуль и настраивать загрузку script, чтобы обмануть Python в его кеширование. Это хакерское решение.

Единственная возможная (и очень неубедительная) причина, которую я могу придумать, заключается в том, чтобы избежать того, чтобы ваш домашний каталог загромождал кучу файлов .pyc. (Это не настоящая причина, если это было актуальной проблемой, тогда файлы .pyc должны быть сохранены как dotfiles.) Конечно, нет причин даже не иметь возможности сделать это.

Python должен определенно иметь возможность кэшировать основной модуль.

Ответ 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%. Имеются косвенные доказательства в поддержку этой теории, и никто в этой теме не предоставил ни единого доказательства доказательств для поддержки какой-либо другой теории. Я поддержал ответ Гленна.

Ответ 4

Начиная с:

Программа не работает быстрее, когда она считывается из файла .pyc или .pyo, чем когда она считывается из .py файла; что быстрее .pyc или .pyo файлы - это скорость, с которой они загружаются.

Нет необходимости генерировать .pyc файл для основного script. Только файлы, которые могут быть загружены много раз, должны быть скомпилированы.

Edited

Кажется, вы не поняли. Во-первых, знание всей идеи компиляции в файл .pyc состоит в том, чтобы сделать тот же самый файл, выполняющийся быстрее во второй раз. Однако подумайте, выполнил ли Python компиляцию script. Интерпретатор будет писать байт-код в файл .pyc при первом запуске, это требует времени. Так что это даже будет работать немного медленнее. Вы можете утверждать, что после этого он будет работать быстрее. Ну, это просто выбор. Кроме того, поскольку this говорит:

Явный лучше, чем неявный.

Если вам требуется ускорение с помощью файла .pyc, его следует скомпилировать вручную и явно запустить файл .pyc.

Ответ 5

Чтобы ответить на ваш вопрос, обратитесь к 6.1.3. "Скомпилированные" файлы Python в официальном документе Python.

Когда script запускается, указывая его имя в командной строке, байт-код для script никогда не записывается в файл .pyc или .pyo. Таким образом, время запуска script может быть уменьшено путем перемещения большей части его кода в модуль и наличия небольшого бутстрапа script, который импортирует этот модуль. Также можно указать файл .pyc или .pyo непосредственно в командной строке.

Ответ 6

Поскольку выполняемый script может быть где-то, где неуместно создавать файлы .pyc, например /usr/bin.