为什么python不需要编译_为什么Python编译模块而不是正在运行的脚本?

指向实现关注项的源代码。这解释了"为什么"从技术意义上讲:唤起这种行为需要哪些先决条件?

指向参与制定决策的开发人员编写的人类可读工件(评论,提交消息,电子邮件列表等)。这才是真正意义上的"为什么"我认为OP感兴趣的是:为什么Python的开发人员做出这个看似随意的决定?

醇>

第二种类型的答案更难以证实,因为它需要了解编写代码的开发人员的想法,特别是如果没有易于查找的公共文档解释特定决策。

到目前为止,这个主题有7个答案,专注于阅读Python开发人员的意图,但整批中只有一个引用。 (它引用了Python手册的一部分,不回答O​​P的问题。)

这是我尝试回答""为什么"问题以及引用。

源代码

触发编译.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%。有支持这一理论的间接证据,这个主题中没有其他人提供过一丝证据来支持任何其他理论。我赞成格伦的答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值