Python单元测试中如何清除类下所有类方法的缓存?
问题根源
无法直接调用cache_clear()的核心原因有两个:
- 装饰器执行顺序为从下往上:
@timed_lru_cache先包装原始方法,再被@classmethod包装,类方法属性不会直接暴露底层缓存对象的方法 - 自定义的
timed_lru_cache没有把内部lru缓存的cache_clear、cache_info等方法透传到外层包装函数上,外部无法直接访问
方案1:修复装饰器透传(推荐,一劳永逸)
直接修改timed_lru_cache实现,把内部lru缓存的清理方法暴露到外层,后续不管是普通函数、实例方法还是类方法,都可以直接调用.cache_clear()清缓存。
修改后的装饰器代码:
from datetime import datetime, timedelta from functools import lru_cache, wraps def timed_lru_cache(seconds, maxsize): def wrapper_cache(func): func = lru_cache(maxsize=maxsize)(func) func.lifetime = timedelta(seconds=seconds) func.expiration = datetime.utcnow() + func.lifetime @wraps(func) def wrapped_func(*args, **kwargs): if datetime.utcnow() >= func.expiration: func.cache_clear() func.expiration = datetime.utcnow() + func.lifetime return func(*args, **kwargs) # 透传lru缓存的原生方法到外层 wrapped_func.cache_clear = func.cache_clear wrapped_func.cache_info = func.cache_info # 可选:新增全量清理方法,清缓存时同步重置过期计时 def full_cache_clear(): func.cache_clear() func.expiration = datetime.utcnow() - func.lifetime wrapped_func.full_cache_clear = full_cache_clear return wrapped_func return wrapper_cache
改完后可以写个简单的工具函数批量清理类下所有方法的缓存,直接在单元测试前置逻辑中调用即可:
def clear_all_classmethod_cache(target_cls): for attr_name in dir(target_cls): attr = getattr(target_cls, attr_name) if callable(attr) and hasattr(attr, 'cache_clear'): attr.cache_clear() # 需要同步重置过期时间时替换为下面的调用 # attr.full_cache_clear() # 单元测试中使用示例(以pytest为例) class TestSomeClass: def setup_method(self): # 每个测试用例执行前自动清空缓存,避免用例间互相影响 clear_all_classmethod_cache(someclass) def test_method1_mock_case1(self): # mock返回值1,执行对应测试逻辑 pass def test_method1_mock_case2(self): # mock返回值2,不会被之前的缓存结果干扰 pass
方案2:不修改原有装饰器,直接实现批量清理
如果暂时无法修改timed_lru_cache的源码,可以利用Python函数的__wrapped__属性(@wraps装饰器会自动给包装函数加这个属性,指向被包装的内层对象),直接拿到内部的lru缓存对象执行清理。
工具函数代码:
def clear_all_classmethod_cache(target_cls): for attr_name, attr in target_cls.__dict__.items(): # 筛选当前类定义的类方法对象 if isinstance(attr, classmethod): # 类方法的__func__属性指向被timed_lru_cache包装后的函数 wrapped_func = attr.__func__ # 拿到内层被lru_cache包装的缓存对象 if hasattr(wrapped_func, '__wrapped__'): lru_cached_func = wrapped_func.__wrapped__ if hasattr(lru_cached_func, 'cache_clear'): lru_cached_func.cache_clear()
这个工具函数不需要修改任何原有业务代码,在单元测试前置逻辑里调用clear_all_classmethod_cache(someclass),就能清空该类下所有类方法的lru缓存,后续调用方法时会重新执行逻辑、读取mock的新返回值。
注意事项
- 不要在测试用例之间依赖缓存状态,最好每个用例执行前都统一清一次缓存,避免用例之间数据污染
- 如果类里有其他带
@wraps装饰的类方法不需要清缓存,可以在工具函数里加自定义判断规则,比如按方法名前缀筛选、加自定义标记过滤等
内容的提问来源于stack exchange,提问作者cynkiller
相关产品推荐
相关产品推荐

