You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:36:13