pytest是否会重复导入模块?类级成员为何被两次初始化
类级成员重复初始化的常见合理场景
- 模块被重复导入:Python 导入系统允许同一物理文件以不同的模块名被加载,只要导入时的路径解析结果不同(比如混用相对导入与绝对导入、
sys.path被动态修改后同文件被识别为不同模块),模块顶层代码会重新执行,类定义会重新生成,类级成员自然会被重新初始化。 - 类定义语句被多次执行:如果类定义写在函数、循环等可被多次调用的代码块中,每次执行类定义语句都会生成全新的类对象,新类的类级成员会走初始化逻辑,和之前的类对象无关联。
- 多进程运行场景:子进程启动后如果重新加载了模块,或者类定义是在子进程创建后才执行的,会触发类成员的二次初始化。
- 热重载机制触发:开发、测试工具自带的热重载功能检测到文件变更后,会重新加载模块,也会导致类定义重新执行。
pytest 场景下该现象的核心原因
pytest 运行时出现该问题,绝大多数是模块重复导入导致的,常见触发逻辑如下:
- pytest 默认会将测试文件所在目录加入
sys.path,如果你的项目代码本身也手动将根目录加入了sys.path,就会导致同一模块存在两种不同的导入路径,比如from test.utils import SqlDump和from utils import SqlDump会被识别为两个完全独立的模块,类会被定义两次,all成员也会被初始化两次。 - 部分 pytest 插件(比如静态检查插件、覆盖率统计插件)会提前加载测试模块做分析,pytest 核心运行阶段又会重新加载一次模块,两次加载都会执行类定义代码。
- 自定义 pytest 钩子函数中如果手动导入了测试模块,和 pytest 自动导入的模块路径不一致也会触发重复加载。
常用修复方案
你可以根据自己的项目情况选择以下方案解决缓存丢失问题:
- 统一项目的导入规则,全部使用基于项目根目录的绝对导入,不要混用相对导入,也不要在代码中动态修改
sys.path。 - 在项目根目录的
pytest.ini中固定导入模式,添加配置import_mode = importlib避免 pytest 自动修改sys.path导致的路径冲突。 - 改用更稳定的缓存实现,避免将缓存绑定到类成员上,比如用
functools.lru_cache实现实例缓存:
import os import re from functools import lru_cache class SqlDump: FIXUP = re.compile(r"^(\s*CREATE SEQUENCE[^;]*)AS INTEGER([^;]*);", flags=re.MULTILINE | re.IGNORECASE | re.DOTALL) PATH = os.path.join(os.path.dirname(__file__), 'test_data/sql_dumps/{script}.sql') def __init__(self, script): self.script = script self.content = self.load() # 用模块级的缓存装饰器替代类成员缓存 @lru_cache(maxsize=None) def get_sql_dump(script: str) -> SqlDump: return SqlDump(script)
内容的提问来源于stack exchange,提问作者Jay Obermark
相关产品推荐
相关产品推荐

