Python解释器运行源码文件时,会先将源码编译为字节码,默认情况下会将字节码缓存到.pyc文件中,下次运行相同源码时可以直接加载缓存的字节码,减少编译开销。但.pyc缓存并非永久有效,存在明确的失效策略。
pyc缓存的基本生成逻辑
当Python解释器导入一个模块时,会检查对应目录下的__pycache__文件夹(Python3.2+版本)中是否存在匹配的.pyc文件。如果不存在,就会编译源码生成.pyc文件并保存。生成缓存的核心判断依据是源码文件的元信息,这些信息会被写入.pyc文件的头部。
pyc失效的核心策略
Python判断.pyc是否失效主要依据两个核心维度,只要任意一个维度不匹配,就会判定缓存失效,重新编译源码生成新的.pyc文件。
1. 源码文件的修改时间匹配
Python会将源码文件的最后修改时间戳写入.pyc文件的头部,每次加载缓存时,会对比当前源码文件的修改时间和.pyc中记录的时间是否一致。如果源码被修改过,修改时间发生变化,缓存就会失效。
我们可以通过简单的代码验证这个逻辑,首先创建一个测试模块test_module.py,内容如下:
# test_module.py
def add(a, b):
return a + b
第一次导入该模块后,查看__pycache__目录下的.pyc文件,然后修改test_module.py的内容,再次导入时就会发现Python重新编译了模块,生成了新的.pyc文件。
2. 源码文件的大小匹配
除了修改时间,Python还会记录源码文件的字节大小到.pyc头部。如果源码文件被修改后,修改时间刚好和之前一致(比如通过特殊方式重置了修改时间),但文件大小发生了变化,缓存同样会被判定为失效。
3. Python解释器版本匹配
不同版本的Python生成的字节码格式存在差异,因此.pyc文件中还会记录生成该缓存的Python版本信息。如果使用的Python解释器版本和生成.pyc时的版本不一致,缓存也会直接失效。比如用Python3.8生成的.pyc,在Python3.9环境下运行时会重新编译。
特殊场景下的失效情况
除了上述核心策略,还有一些特殊场景会导致.pyc缓存失效:
- 如果源码文件被删除,对应的.pyc缓存也会在下次导入时被清理,不过不会立即删除。
- 如果手动删除了
__pycache__目录下的.pyc文件,下次导入模块时会重新生成缓存。 - 当使用
-B参数运行Python时,解释器会禁止生成.pyc缓存,自然也不会使用已有的缓存文件。
相关验证代码示例
我们可以通过py_compile模块查看.pyc文件的头部信息,验证缓存的元信息记录情况:
import py_compile
import struct
def read_pyc_header(pyc_path):
with open(pyc_path, 'rb') as f:
# 读取魔术数,4字节
magic = f.read(4)
# 读取Python版本相关标记,4字节(部分版本可能不同)
flags = struct.unpack('<I', f.read(4))[0]
# 读取时间戳,4字节
timestamp = struct.unpack('<I', f.read(4))[0]
# 读取源码大小,4字节
source_size = struct.unpack('<I', f.read(4))[0]
return {
'magic': magic.hex(),
'flags': flags,
'timestamp': timestamp,
'source_size': source_size
}
# 先编译测试模块生成pyc
py_compile.compile('test_module.py', 'test_module.pyc')
header_info = read_pyc_header('test_module.pyc')
print(f"缓存记录的时间戳:{header_info['timestamp']}")
print(f"缓存记录的源码大小:{header_info['source_size']}")
开发中的注意事项
了解.pyc的失效策略后,开发过程中可以注意以下几点:
- 不要手动修改.pyc文件,否则会破坏头部结构导致缓存失效。
- 部署程序时,如果源码没有变更,可以直接携带.pyc缓存提升加载速度,但要确保目标环境的Python版本和生成缓存时一致。
- 如果需要强制让缓存失效,直接修改源码文件的修改时间或者大小即可,不需要手动删除.pyc文件。