Python解释器如何判断何时编译并更新.pyc文件?

21 投票
2 回答
4112 浏览
提问于 2025-04-18 07:10

我知道 .pyc 文件是由 Python 解释器生成的,里面包含了字节码,就像这个问题所说的那样。

我以为 Python 解释器是通过时间戳来判断 .pyc 文件是否比 .py 文件更新,如果是的话,就在执行时跳过重新编译的步骤。(就像 makefile 的做法)

所以,我做了一个测试,但似乎我错了。

  1. 我写了一个 t.py 文件,内容是 print '123',然后写了一个 t1.py 文件,内容是 import t。运行命令 python t1.py 后,输出了 123,并生成了 t.pyc,一切都如我所料。
  2. 然后我把 t.py 修改为 print '1234',并用 touch t.pyc 更新了 t.pyc 的时间戳。
  3. 再次运行 python t1.py,我以为会得到 123,结果却得到了 1234。所以看起来 Python 解释器还是知道 t.py 被更新了。

接着我想知道,Python 解释器在每次运行 python t1.py 时是否都会编译并生成 t.pyc。但当我多次运行 python t1.py 时发现,只要 t.py 没有更新,t.pyc 就不会被更新。

所以,我的问题是:Python 解释器是如何知道何时编译和更新 .pyc 文件的?

更新

因为 Python 解释器是使用存储在 .pyc 文件中的时间戳。我认为这记录了 .pyc 最后一次更新的时间。当导入时,它会将这个时间戳与 .py 文件的时间戳进行比较。

所以我尝试这样破解:把操作系统的时间改成一个更早的时间,然后编辑 .py 文件。我以为再次导入时,.py 看起来比 .pyc 旧,Python 解释器就不会更新 .pyc但我又错了。

那么,Python 解释器是以完全相等的方式比较这两个时间戳,而不是以旧或新的方式吗?

在完全相等的方式中,我的意思是 .pyc 中的时间戳记录了 .py 最后一次修改的时间。当导入时,它会将这个时间戳与当前的 .py 的时间戳进行比较,如果不相同,就会重新编译并更新 .pyc

2 个回答

0

你想的没错,实际上这跟最后一次更新的时间戳有关。如果一个 .py 文件在生成 .pyc 文件之后被更新了,那么字节码会重新生成。这个行为就像 make 命令一样,只会重新编译那些新修改过的文件。

当你导入这个模块时,.pyc 文件会被更新。所以你的测试没有成功,可能是因为你执行了代码,而不是导入它。

10

看起来时间戳是直接存储在 *.pyc 文件里的。Python 解释器并不依赖文件的最后修改时间,可能是为了避免在复制源代码时出现不兼容的字节码问题。

如果你查看 Python 中 import 语句的实现,你会发现有一个检查过时的机制在 _validate_bytecode_header() 函数里。这个函数会提取第 4 到第 7 个字节(包括这两个字节),然后将其与源文件的时间戳进行比较。如果不匹配,字节码就会被认为是过时的,因此需要重新编译。

在这个过程中,它还会检查源文件的长度是否与生成特定字节码时使用的源文件长度(存储在第 8 到第 11 个字节)相符。

在 Python 的实现中,如果其中一个检查失败,字节码加载器会抛出一个 ImportError,这个错误会被 SourceLoader.get_code() 捕获,从而触发字节码的重新编译。

注意:这就是在 Python 版本的 importlib 中的实现方式。我想在原生版本中没有功能上的区别,但我的 C 语言有点生疏,没法深入研究编译器代码。

撰写回答