指向实现关注项的源代码。这解释了"为什么"从技术意义上讲:唤起这种行为需要哪些先决条件?
指向参与制定决策的开发人员编写的人类可读工件(评论,提交消息,电子邮件列表等)。这才是真正意义上的"为什么"我认为OP感兴趣的是:为什么Python的开发人员做出这个看似随意的决定?
醇>
第二种类型的答案更难以证实,因为它需要了解编写代码的开发人员的想法,特别是如果没有易于查找的公共文档解释特定决策。
到目前为止,这个主题有7个答案,专注于阅读Python开发人员的意图,但整批中只有一个引用。 (它引用了Python手册的一部分,不回答OP的问题。)
这是我尝试回答""为什么"问题以及引用。
源代码
触发编译.pyc的前提条件是什么?我们来看the source code。 (令人恼火的是,GitHub上的Python没有任何发布标签,所以我只是告诉你我正在看715a6e。)
import.c:989函数中的load_source_module()代码很有用。为简洁起见,我在这里删掉了一些内容。
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()的功能相同。它调用解析器从Python源代码创建AST,然后调用编译器创建字节码。
这些代码块是多余的还是它们用于不同的目的?区别在于一个块从文件加载模块,而另一个块将模块作为参数。该模块参数是 - 在这种情况下 - __main__模块,它是在初始化过程中使用低级C函数创建的。 __main__模块没有经过大多数普通模块导入代码路径,因为它是如此独特,而且作为副作用,它不会通过生成{{1}的代码。文件。
总结一下:.pyc模块未编译为.pyc的原因是它没有"#34;导入"。是它出现在sys.modules中,但它通过一个与实际模块导入完全不同的代码路径到达那里。
开发者意图
好的,我们现在可以看到这种行为更多地与Python的设计有关,而不是源代码中任何明确表达的理由,但这并不能回答这是否是故意决定的问题或者只是一个副作用,不会打扰任何人值得改变。开源的一个好处是,一旦我们找到了我们感兴趣的源代码,我们就可以使用VCS来帮助追溯导致当前实现的决策。
这里关键的代码行之一(__main__)可以追溯到1990年,由BDFL自己编写,Guido。它在中间进行了修改,但修改是肤浅的。首次编写时,脚本参数的主模块初始化如下:
m = PyImport_AddModule("__main__");
在将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;
}文件引入Python之前就已经存在了!难怪当时的设计没有考虑脚本参数的编译。 commit message神秘地说:
"编译"版本
这是3天内几十次提交之一...... Guido似乎深陷一些黑客攻击/重构,这是第一个恢复稳定的版本。这个提交甚至早于the Python-Dev mailing list创建大约五年!
这仍然在列表服务之前,所以我们并不知道Guido在想什么。看起来他只是认为导入器是为了缓存字节码而挂钩的最佳位置。他是否认为为.pyc做同样的事情的想法尚不清楚:要么他没有发生,要么他认为这比它的价值更麻烦。
我无法在bugs.python.org上找到与缓存主模块字节码相关的any bugs,也无法在邮件列表中找到有关它的任何消息,所以显然没有其他人认为尝试添加它是值得的。
总结一下:将所有模块编译到__main__除.pyc以外的原因是它是历史的一个小问题。如何设计和实现{在__main__文件存在之前,{1}}工作已经被编入代码中。如果你想了解更多,你需要通过电子邮件发送Guido并询问。
格伦梅纳德的回答说:
似乎没有人想这样说,但我很确定答案很简单:这种行为没有充分理由。
我同意100%。有支持这一理论的间接证据,这个主题中没有其他人提供过一丝证据来支持任何其他理论。我赞成格伦的答案。