Python解释器如何判断何时编译并更新.pyc文件?
我知道 .pyc
文件是由 Python 解释器生成的,里面包含了字节码,就像这个问题所说的那样。
我以为 Python 解释器是通过时间戳来判断 .pyc
文件是否比 .py
文件更新,如果是的话,就在执行时跳过重新编译的步骤。(就像 makefile 的做法)
所以,我做了一个测试,但似乎我错了。
- 我写了一个
t.py
文件,内容是print '123'
,然后写了一个t1.py
文件,内容是import t
。运行命令python t1.py
后,输出了123
,并生成了t.pyc
,一切都如我所料。 - 然后我把
t.py
修改为print '1234'
,并用touch t.pyc
更新了t.pyc
的时间戳。 - 再次运行
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 个回答
你想的没错,实际上这跟最后一次更新的时间戳有关。如果一个 .py
文件在生成 .pyc
文件之后被更新了,那么字节码会重新生成。这个行为就像 make
命令一样,只会重新编译那些新修改过的文件。
当你导入这个模块时,.pyc
文件会被更新。所以你的测试没有成功,可能是因为你执行了代码,而不是导入它。
看起来时间戳是直接存储在 *.pyc
文件里的。Python 解释器并不依赖文件的最后修改时间,可能是为了避免在复制源代码时出现不兼容的字节码问题。
如果你查看 Python 中 import
语句的实现,你会发现有一个检查过时的机制在 _validate_bytecode_header()
函数里。这个函数会提取第 4 到第 7 个字节(包括这两个字节),然后将其与源文件的时间戳进行比较。如果不匹配,字节码就会被认为是过时的,因此需要重新编译。
在这个过程中,它还会检查源文件的长度是否与生成特定字节码时使用的源文件长度(存储在第 8 到第 11 个字节)相符。
在 Python 的实现中,如果其中一个检查失败,字节码加载器会抛出一个 ImportError
,这个错误会被 SourceLoader.get_code()
捕获,从而触发字节码的重新编译。
注意:这就是在 Python 版本的 importlib
中的实现方式。我想在原生版本中没有功能上的区别,但我的 C 语言有点生疏,没法深入研究编译器代码。