<h2>教育学</h2>
<p>我喜欢和讨厌这样的问题,因为有一个复杂的混合情绪,意见,和教育猜测进行,人们开始窃笑,不知何故,每个人都失去了实际的事实,最终完全失去了原来的问题。</p>
<p>因此,许多技术问题至少有一个明确的答案(例如,可以通过执行来验证的答案或引用权威来源的答案),但这些“为什么”问题往往没有一个明确的答案。在我看来,有两种可能的方法可以明确回答计算机科学中的“为什么”问题:</p>
<ol>
<li>通过指向实现关注项的源代码。这从技术角度解释了“为什么”:引发这种行为需要什么先决条件?</li>
<li>通过指向参与决策的开发人员编写的可读工件(注释、提交消息、电子邮件列表等)。这就是我认为OP感兴趣的真正意义上的“为什么”:Python的开发人员为什么做出这个看似武断的决定?</li>
</ol>
<p>第二种类型的答案更难证实,因为它需要记住编写代码的开发人员,特别是在不容易找到解释特定决策的公共文档的情况下。</p>
<p>到目前为止,这个线程有7个答案,它们只专注于阅读Python开发人员的意图,而在整个批处理中只有<em>一个引用</em>。(它引用了Python手册中的一节,该节确实没有回答OP的问题。)</p>
<p>这是我试图回答“为什么”问题两边的<em>和引文。</p>
<h2>源代码</h2>
<p>触发编译.pyc的先决条件是什么?让我们看看<a href="https://github.com/python-git/python.git" rel="noreferrer">the source code</a>。(令人恼火的是,GitHub上的Python没有任何发布标记,所以我只告诉您我正在查看<code>715a6e</code>。)</p>
<p>在<code>load_source_module()</code>函数中的<code>import.c:989</code>中有很有前途的代码。为了简洁起见,我在这里剪了一些。</p>
<pre><code>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;
}
</code></pre>
<p><code>pathname</code>是指向模块的路径,<code>cpathname</code>是相同的路径,但具有.pyc扩展名。唯一的直接逻辑是布尔值<a href="https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode" rel="noreferrer">^{<cd6>}</a>。其余的逻辑只是错误处理。因此,我们寻求的答案不在这里,但我们至少可以看到,在大多数默认配置下,调用此函数的任何代码都将生成一个.pyc文件。函数<code>parse_source_module()</code>与执行流没有实际关联,但我将在这里展示它,因为我稍后会再讨论它。</p>
<pre><code>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;
}
</code></pre>
<p>这里最突出的特点是,函数解析和编译一个文件,并返回指向字节码的指针(如果成功)。</p>
<p>现在我们还处在一个死胡同,所以让我们从一个新的角度来探讨这个问题。Python如何加载参数并执行它?在<code>pythonrun.c</code>中,有几个函数用于从文件加载代码并执行它。<code>PyRun_AnyFileExFlags()</code>可以处理交互式和非交互式文件描述符。对于交互式文件描述符,它委托给<code>PyRun_InteractiveLoopFlags()</code>(这是REPL),对于非交互式文件描述符,它委托给<code>PyRun_SimpleFileExFlags()</code>。<code>PyRun_SimpleFileExFlags()</code>检查文件名是否以<code>.pyc</code>结尾。如果是,则调用<code>run_pyc_file()</code>,后者直接从文件描述符加载编译后的字节码,然后运行它。</p>
<p>在更常见的情况下(即<code>.py</code>文件作为参数),<code>PyRun_SimpleFileExFlags()</code>调用<code>PyRun_FileExFlags()</code>。这就是我们开始寻找答案的地方。</p>
<pre><code>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;
}
</code></pre>
<p>这里的要点是,这两个函数基本上执行与导入程序的<code>load_source_module()</code>和<code>parse_source_module()</code>相同的目的。它调用解析器从Python源代码创建AST,然后调用编译器创建字节码。</p>
<p>那么这些代码块是冗余的还是有不同的用途?不同之处在于,一个块从文件加载模块,而另一个块将模块<em>作为参数</em>。模块参数是case-这个<code>__main__</code>模块,它是在初始化过程的前面使用一个低级C函数创建的。由于<code>__main__</code>模块非常独特,所以它不会经过大多数常规的模块导入代码路径,而且作为副作用,它不会经过生成<code>.pyc</code>文件的代码。</p>
<p><strong>总结一下:<code>__main__</code>模块没有编译到.pyc的原因是它没有“导入”。</strong>是的,它出现在sys.modules中,但是它通过与实际模块导入不同的代码路径到达那里。</p>
<h2>开发商意图</h2>
<p>好吧,现在我们可以看到,这种行为与Python的设计有着更多的关系,而不是源代码中任何明确表达的基本原理,但这并不能回答这样一个问题:这是一个有意的决定,还是仅仅是一个副作用,不足以让任何人感到困扰,值得改变。开源的好处之一是,一旦我们找到了我们感兴趣的源代码,我们就可以使用VCS帮助追溯到导致当前实现的决策。</p>
<p>这里的关键代码行(<code>m = PyImport_AddModule("__main__");</code>)可以追溯到1990年,由BDFL自己Guido编写。在过去的几年里,它已经被修改过,但是修改是肤浅的。第一次写入时,脚本参数的主模块的初始化方式如下:</p>
<pre><code>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;
}
</code></pre>
<p>这在将<code>.pyc</code>文件引入Python之前就已经存在了!难怪当时的设计没有考虑脚本参数的编译。这位<a href="https://github.com/python-git/python/commit/57c96de96f810d86084ed28334f63f9e5deede47" rel="noreferrer">commit message</a>神秘地说:</p>
<blockquote>
<p>"Compiling" version</p>
</blockquote>
<p>这是三天内几十次犯罪中的一次。。。看起来Guido已经深入到了一些黑客/重构中,这是第一个恢复稳定的版本。这个承诺甚至比<a href="https://mail.python.org/pipermail/python-dev/" rel="noreferrer">the Python-Dev mailing list</a>的创建还要早5年!</p>
<p>保存编译的字节码是<a href="https://github.com/python-git/python/commit/b1ee959e290ef4878178effbcc0c30b40a73551c" rel="noreferrer">introduced 6 months later, in 1991</a>。</p>
<p>这仍然早于列表服务,所以我们不知道Guido在想什么。似乎他只是认为导入程序是缓存字节码的最佳连接位置。他是否认为为<code>__main__</code>做同样的事情是不清楚的:要么他没有想到,要么他认为这比它值得的麻烦多。</p>
<p>我在bugs.python.org上找不到与缓存主模块字节码相关的<a href="http://bugs.python.org/issue?%40columns=id%2Cactivity%2Ctitle%2Ccreator%2Cassignee%2Cstatus%2Ctype&%40sort=-activity&%40filter=status&%40action=searchid&ignore=file%3Acontent&%40search_text=<code>__main__</code>+pyc&submit=search&status=-1%2C1%2C2%2C3" rel="noreferrer">any bugs</a>,也找不到邮件列表中关于它的任何消息,因此显然没有人认为值得尝试添加它。</p>
<p><strong>总结一下:除了<code>__main__</code>之外,所有模块都编译成<code>.pyc</code>的原因是,这是一个历史怪癖。</strong>如何<code>__main__</code>工作的设计和实现是在<code>.pyc</code>文件甚至存在之前就被烘焙到代码中的。如果你想知道更多,你需要给Guido发邮件询问。</p>
<p>格伦·梅纳德的回答是:</p>
<blockquote>
<p>Nobody seems to want to say this, but I'm pretty sure the answer is simply: there's no solid reason for this behavior.</p>
</blockquote>
<p>我完全同意。有间接证据支持这一理论,而这条线索中没有其他人提供了一点证据来支持任何其他理论。我对格伦的回答投了高票。</p>