为什么Python要编译脚本中使用的库,而不是被调用的脚本本身?
例如
如果存在main.py
和module.py
,并且Python是通过执行python main.py
来运行的,则会有一个编译文件module.pyc
,但不会有一个主文件。为什么?
编辑
增加赏金。我认为这个问题没有得到恰当的回答。
如果响应是main.py
目录的潜在磁盘权限,那么Python为什么要编译模块?它们很可能(如果不是更可能)出现在用户没有写访问权限的位置。如果是可写的,Python可以编译main
,或者在另一个目录中编译。
如果原因是收益很小,请考虑脚本将被大量使用(例如在CGI应用程序中)的情况。
文件在导入时编译。这不是安全问题。很简单,如果导入它,python会保存输出。见Fredrik Lundh关于Effbot的this post。
运行脚本时,python将使用*.pyc文件。 如果您有其他需要预编译脚本的原因,可以使用^{} 模块。
编译所有用法
问题编辑答案
模块和脚本的处理方式相同。导入是触发输出被保存的原因。
使用compileall并不能解决这个问题。 除非显式调用,否则由python执行的脚本不会使用
*.pyc
。这有副作用,正如his answer中的Glenn Maynard所说。给出的CGI应用程序示例应该使用FastCGI这样的技术来处理。如果您希望消除编译脚本的开销,那么您可能也希望消除启动python的开销,更不用说数据库连接开销了。
轻量级引导脚本可以使用,甚至可以使用
python -c "import script"
,但是这些脚本的样式有问题。Glenn Maynard为纠正和改进这个答案提供了一些启发。
似乎没人想这么说,但我敢肯定答案很简单:这种行为没有确凿的理由。
到目前为止给出的所有理由基本上都是错误的:
sys.modules
中。运行主脚本只不过是用模块名__main__
导入它。另一个需要注意的问题是:如果运行
python foo.py
,并且foo.pyc存在,那么它将不会被使用。你必须显式地说python foo.pyc
。这是一个非常糟糕的想法:这意味着当.pyc文件不同步时(由于.py文件的更改),Python不会自动重新编译它,因此对.py文件的更改将在您手动重新编译它之前不会使用。如果您升级Python并且.pyc文件格式不再兼容(这种情况经常发生),它也会完全失败,并出现运行时错误。通常,这些都是透明处理的。您不需要将脚本移动到虚拟模块并设置引导脚本来诱使Python缓存它。这是一个老套的解决办法。
我能想出的唯一可能(而且非常不令人信服)的原因是避免主目录被一堆.pyc文件弄乱。(这不是一个真正的原因;如果这是一个真正的问题,那么.pyc文件应该保存为do t files。)甚至没有理由不使用选项来执行此操作。
Python肯定能够缓存主模块。
教育学
我喜欢和讨厌这样的问题,因为有一个复杂的混合情绪,意见,和教育猜测进行,人们开始窃笑,不知何故,每个人都失去了实际的事实,最终完全失去了原来的问题。
因此,许多技术问题至少有一个明确的答案(例如,可以通过执行来验证的答案或引用权威来源的答案),但这些“为什么”问题往往没有一个明确的答案。在我看来,有两种可能的方法可以明确回答计算机科学中的“为什么”问题:
第二种类型的答案更难证实,因为它需要记住编写代码的开发人员,特别是在不容易找到解释特定决策的公共文档的情况下。
到目前为止,这个线程有7个答案,它们只专注于阅读Python开发人员的意图,而在整个批处理中只有一个引用。(它引用了Python手册中的一节,该节确实没有回答OP的问题。)
这是我试图回答“为什么”问题两边的和引文。
源代码
触发编译.pyc的先决条件是什么?让我们看看the source code。(令人恼火的是,GitHub上的Python没有任何发布标记,所以我只告诉您我正在查看
715a6e
。)在
load_source_module()
函数中的import.c:989
中有很有前途的代码。为了简洁起见,我在这里剪了一些。pathname
是指向模块的路径,cpathname
是相同的路径,但具有.pyc扩展名。唯一的直接逻辑是布尔值^{parse_source_module()
与执行流没有实际关联,但我将在这里展示它,因为我稍后会再讨论它。这里最突出的特点是,函数解析和编译一个文件,并返回指向字节码的指针(如果成功)。
现在我们还处在一个死胡同,所以让我们从一个新的角度来探讨这个问题。Python如何加载参数并执行它?在
pythonrun.c
中,有几个函数用于从文件加载代码并执行它。PyRun_AnyFileExFlags()
可以处理交互式和非交互式文件描述符。对于交互式文件描述符,它委托给PyRun_InteractiveLoopFlags()
(这是REPL),对于非交互式文件描述符,它委托给PyRun_SimpleFileExFlags()
。PyRun_SimpleFileExFlags()
检查文件名是否以.pyc
结尾。如果是,则调用run_pyc_file()
,后者直接从文件描述符加载编译后的字节码,然后运行它。在更常见的情况下(即
.py
文件作为参数),PyRun_SimpleFileExFlags()
调用PyRun_FileExFlags()
。这就是我们开始寻找答案的地方。这里的要点是,这两个函数基本上执行与导入程序的
load_source_module()
和parse_source_module()
相同的目的。它调用解析器从Python源代码创建AST,然后调用编译器创建字节码。那么这些代码块是冗余的还是有不同的用途?不同之处在于,一个块从文件加载模块,而另一个块将模块作为参数。模块参数是case-这个
__main__
模块,它是在初始化过程的前面使用一个低级C函数创建的。由于__main__
模块非常独特,所以它不会经过大多数常规的模块导入代码路径,而且作为副作用,它不会经过生成.pyc
文件的代码。总结一下:
__main__
模块没有编译到.pyc的原因是它没有“导入”。是的,它出现在sys.modules中,但是它通过与实际模块导入不同的代码路径到达那里。开发商意图
好吧,现在我们可以看到,这种行为与Python的设计有着更多的关系,而不是源代码中任何明确表达的基本原理,但这并不能回答这样一个问题:这是一个有意的决定,还是仅仅是一个副作用,不足以让任何人感到困扰,值得改变。开源的好处之一是,一旦我们找到了我们感兴趣的源代码,我们就可以使用VCS帮助追溯到导致当前实现的决策。
这里的关键代码行(
m = PyImport_AddModule("__main__");
)可以追溯到1990年,由BDFL自己Guido编写。在过去的几年里,它已经被修改过,但是修改是肤浅的。第一次写入时,脚本参数的主模块的初始化方式如下:这在将
.pyc
文件引入Python之前就已经存在了!难怪当时的设计没有考虑脚本参数的编译。这位commit message神秘地说:这是三天内几十次犯罪中的一次。。。看起来Guido已经深入到了一些黑客/重构中,这是第一个恢复稳定的版本。这个承诺甚至比the Python-Dev mailing list的创建还要早5年!
保存编译的字节码是introduced 6 months later, in 1991。
这仍然早于列表服务,所以我们不知道Guido在想什么。似乎他只是认为导入程序是缓存字节码的最佳连接位置。他是否认为为
__main__
做同样的事情是不清楚的:要么他没有想到,要么他认为这比它值得的麻烦多。我在bugs.python.org上找不到与缓存主模块字节码相关的any bugs,也找不到邮件列表中关于它的任何消息,因此显然没有人认为值得尝试添加它。
总结一下:除了
__main__
之外,所有模块都编译成.pyc
的原因是,这是一个历史怪癖。如何__main__
工作的设计和实现是在.pyc
文件甚至存在之前就被烘焙到代码中的。如果你想知道更多,你需要给Guido发邮件询问。格伦·梅纳德的回答是:
我完全同意。有间接证据支持这一理论,而这条线索中没有其他人提供了一点证据来支持任何其他理论。我对格伦的回答投了高票。
相关问题 更多 >
编程相关推荐