Python标准库全局替换time.sleep:优化提前导入场景的方案
问题描述
为Slash框架编写测试夹具时需要修改time.sleep的行为,因无法使用pytest,需自行实现猴子补丁。目前已能替换通过import time引入的time.sleep,但无法处理在夹具实例化前通过from time import sleep导入的引用。当前使用gc.get_referrers追踪并替换引用,代码如下:
self._real_sleep = time.sleep for ref in gc.get_referrers(time.sleep): if (isinstance(ref, dict) and "__name__" in ref and "sleep" in ref and ref["sleep"] is self._real_sleep): self._monkey_patch(ref)
该方法可行但不够优雅,使用Python 3.11且无第三方依赖,寻求更优、更安全的基于标准库的全局补丁方案。
解决方案
1. 基础替换:修改time模块本身的sleep属性
首先替换time.sleep,确保后续导入都会获取到模拟实现:
import time # 保存原函数 _real_sleep = time.sleep # 定义模拟sleep def mock_sleep(seconds): # 自定义模拟逻辑,比如直接返回不等待 pass # 替换time模块中的sleep time.sleep = mock_sleep
2. 遍历已加载模块,替换模块级sleep引用
通过sys.modules遍历所有已导入的模块,替换模块全局命名空间中指向原time.sleep的sleep引用:
import sys for module_name, module in list(sys.modules.items()): # 跳过系统内置模块和空模块,避免不必要的处理 if module_name.startswith('__') or module is None: continue try: current_sleep = getattr(module, 'sleep', None) if current_sleep is _real_sleep: setattr(module, 'sleep', mock_sleep) except (AttributeError, ImportError): # 忽略无法访问属性的模块 continue
3. 处理局部命名空间引用(基于gc优化)
对于函数、类内部通过from time import sleep导入的局部引用,仍需借助gc追踪,但优化判断逻辑,只处理必要的字典类型命名空间:
import gc for ref in gc.get_referrers(_real_sleep): if isinstance(ref, dict) and ref.get('sleep') is _real_sleep: ref['sleep'] = mock_sleep
相比原代码,去掉了__name__的检查(并非所有命名空间都包含该属性),只判断sleep键对应的值是否为原函数,逻辑更简洁且覆盖范围更广。
4. 封装为上下文管理器(可选)
将补丁逻辑封装为上下文管理器,方便在测试夹具中使用,确保测试完成后恢复原函数,避免影响其他测试:
import time import sys import gc from contextlib import contextmanager @contextmanager def patch_sleep(mock_sleep_func): _real_sleep = time.sleep patched_refs = [] try: # 替换time模块的sleep time.sleep = mock_sleep_func patched_refs.append((time, 'sleep', _real_sleep)) # 替换已加载模块的模块级sleep for module_name, module in list(sys.modules.items()): if module_name.startswith('__') or module is None: continue try: if getattr(module, 'sleep', None) is _real_sleep: setattr(module, 'sleep', mock_sleep_func) patched_refs.append((module, 'sleep', _real_sleep)) except (AttributeError, ImportError): continue # 替换gc追踪到的其他引用 for ref in gc.get_referrers(_real_sleep): if isinstance(ref, dict) and ref.get('sleep') is _real_sleep: ref['sleep'] = mock_sleep_func patched_refs.append((ref, 'sleep', _real_sleep)) yield finally: # 恢复所有被修改的引用 for obj, attr_name, original in patched_refs: if isinstance(obj, dict): obj[attr_name] = original else: setattr(obj, attr_name, original)
使用示例:
def test_mock_sleep(): def my_mock(seconds): print(f"模拟睡眠 {seconds} 秒") with patch_sleep(my_mock): from time import sleep sleep(3) # 会调用my_mock
方案优势
- 模块级遍历直接高效,覆盖绝大多数导入场景,比单纯依赖
gc更可靠 gc部分逻辑优化,减少不必要的判断,更简洁安全- 上下文管理器封装确保测试前后环境一致,避免遗留副作用
内容的提问来源于stack exchange,提问作者superstator
相关产品推荐
相关产品推荐

