什么时候会刷新 .pyc 文件?

102
我理解“.pyc”文件是“ .py”文件的编译版本,在运行时创建,以使程序运行更快。但是我观察到了几件事情:
1.修改“py”文件后,程序行为会发生变化。这表明“py”文件已编译或至少经过某种哈希处理或比较时间戳,以判断它们是否应重新编译。
2.删除所有“.pyc”文件(rm * .pyc)有时会改变程序行为。这表明它们没有在“ .py”更新时进行编译。
问题:
1.它们如何决定何时进行编译?
2.有没有办法确保它们在开发期间进行更严格的检查?

16
谨防使用 rm *.pyc 命令删除 .pyc 文件,因为它无法删除嵌套文件夹中的 .pyc 文件。建议使用 find . -name '*.pyc' -delete 命令来代替。 - Zags
6
关于你的问题,可能需要说明一点:从‘.pyc’或‘.pyo’文件读取程序并不比从‘.py’文件读取时运行得更快;唯一快的是‘.pyc’或‘.pyo’文件加载速度。 - maggie
@maggie,加载时间和执行时间有什么区别? - user5306470
3
@Dani:加载时间指读取并编译程序所需的时间。执行时间是程序实际运行的时间,这发生在加载完成后。如果你想要更加技术性,时间类型包括加载时间、编译时间、链接时间和执行时间。创建一个 .pyc 文件可以消除编译时间的部分。 - Eric Klien
@EricKlien 谢谢你啊。 - user5306470
2个回答

92

.pyc文件只有在被其他脚本导入时才会被创建(可能会被覆盖)。如果进行了导入,Python将检查.pyc文件的内部时间戳是否比相应的.py文件更旧。 如果是,则加载.pyc; 如果不是或者.pyc文件还不存在,则Python将编译.py文件成.pyc并加载它。

"更严格的检查"是什么意思?


3
我能够通过使用“rm *.pyc”命令来修复问题。我知道,如果强制重建所有文件,则可以解决一些问题,这表明这些文件本身没有被重新编译。我猜想如果它们确实使用时间戳,那么就没有办法使这种行为更加严格,但问题仍然存在。 - Aaron Schif
17
这不是完全正确的。时间戳不需要匹配(实际上它们通常不匹配)。.pyc文件的时间戳必须比相应的.py文件的时间戳,才会触发重新编译。 - Tim Pietzcker
5
@Aaron,你是否可能更改了 .py 文件,并在此过程中使它们变得更旧(例如通过从另一个目录复制它们,使用保留“修改时间”的操作)? - greggo
1
@greggo,我正在使用Git并从存储库更新,所以在某种程度上是这样。那可能就可以了。谢谢。 - Aaron Schif
1
我认为@TimPietzcker的评论是不正确的。这里是Python 3.6的相关源代码:https://github.com/python/cpython/blob/4134f154ae2f621f25c5d698cc0f1748035a1b88/Lib/importlib/_bootstrap_external.py#L470。`.py`文件的最后修改时间必须与嵌入在`.pyc`文件头中的时间戳完全匹配。(`.pyc`文件本身的时间戳是无关紧要的,而且更高版本的Python在`.pyc`头中根本不使用时间戳。) - Mark Dickinson
显示剩余2条评论

35

.pyc文件在对应的代码元素被导入时生成,如果相应的代码文件已经更新,则会进行更新。如果删除了.pyc文件,则它们将自动重新生成。然而,在删除相应的代码文件时,.pyc文件不会自动删除。

在文件级别的重构过程中,这可能会导致一些非常有趣的错误。

首先,您可能会推送只能在您的计算机上运行但在其他人的计算机上无法运行的代码。如果您删除了某些文件却有一些未解决的引用,只要手动删除相关的.pyc文件,这些文件仍然可以在本地工作,因为.pyc文件可以在导入中使用。更糟糕的是,配置正确的版本控制系统只会将.py文件推送到中央存储库,而不是.pyc文件,这意味着您的代码可能通过“导入测试”(是否所有内容都可以正常导入)测试,并且在他人的计算机上无法正常工作。

其次,如果将包转换为模块,则可能会出现一些非常严重的错误。当您将包(带有__init__.py文件的文件夹)转换为模块(一个.py文件)时,曾经表示该包的.pyc文件仍然存在。特别是,__init__.pyc仍然存在。因此,如果您拥有名为foo的包,并有一些不重要的代码,然后稍后删除该包并创建一个带有def bar(): pass函数的文件foo.py并运行:

from foo import bar

你获得:

ImportError: cannot import name bar

由于Python仍在使用foo软件包中旧的.pyc文件,其中没有定义bar。这可能会在Web服务器上引起特别严重的问题,因为完全正常运行的代码可能会因为.pyc文件而崩溃。

出于这两个原因(以及可能的其他原因),您的部署代码和测试代码应删除.pyc文件,例如使用以下Bash命令:

find . -name '*.pyc' -delete

另外,从Python 2.6开始,您可以使用-B标志运行Python以不使用.pyc文件。有关更多详细信息,请参见如何避免 .pyc 文件?

另请参阅:如何从项目中删除所有 .pyc 文件?


当你转换一个模块(一个带有__init__.py文件的文件夹)时......那将是一个包,而不是一个模块。 - bgrant
2
特别是,__init__.pyc文件会保留下来。为什么呢?因为包是一个目录,删除包意味着删除目录,因此不会剩余任何文件。 - Piotr Dobrogost
3
恰当管理的源代码控制不包括将pyc文件提交到源代码中。因此,尽管您可以在本地副本中删除包括pyc文件在内的文件夹,但对于执行git pull操作的其他人来说,这些文件并不会被删除。如果您的部署涉及使用git pull,则可能会导致服务器崩溃。 - Zags
有很多理由不信任你的开发环境是否代表了你的代码将要部署的环境,这个 .pyc 问题就是其中之一,同时还有:依赖操作系统和实用程序补丁级别、 .so 文件、配置文件、其他 Python 库(如果你没有在虚拟环境中运行)以及晦涩的环境变量等。为了彻底找到所有此类问题,你需要在 Git 存储库中创建一个干净的副本或者作为包发布到 PyPi 风格服务器,并在新的虚拟机上进行完全克隆或安装。其中一些潜在问题使得这个 .pyc 问题相形见绌。 - Chris Johnson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接