如何将@contextmanager包装器改造为可直接调用的标准函数
实现方案
有三种常用的实现方式,你可以根据自己的业务场景选择:
方案1:拆分独立接口(最推荐)
直接在API层提供两个语义明确的接口,完全避免误用,维护成本最低:
from contextlib import contextmanager # 供需要上下文的场景使用 @contextmanager def begin_foo_context(param): with myobj.begin_foo(param): yield # 供直接调用的场景使用 def begin_foo(param): with begin_foo_context(param): pass
客户端调用示例:
# 场景1:需要在上下文中执行业务逻辑 with begin_foo_context(param): # 你的业务代码 ... # 场景2:仅执行入口、出口逻辑 begin_foo(param)
方案2:单接口加参数控制
如果不想拆分接口,可以加可选参数切换行为:
from contextlib import contextmanager @contextmanager def _begin_foo_cm(param): with myobj.begin_foo(param): yield def begin_foo(param, use_context:bool = True): if use_context: return _begin_foo_cm(param) with _begin_foo_cm(param): pass
客户端调用示例:
# 场景1:默认行为兼容原有写法 with begin_foo(param): ... # 场景2:指定参数关闭上下文模式 begin_foo(param, use_context=False)
方案3:无参数自动兼容两种用法
如果不想修改原有调用代码,也不想加参数,可以返回同时支持上下文协议和可调用的包装对象:
from contextlib import contextmanager class CMAdapter: def __init__(self, cm): self._cm = cm def __enter__(self): return self._cm.__enter__() def __exit__(self, *exc_info): return self._cm.__exit__(*exc_info) def __call__(self): with self._cm: pass @contextmanager def _begin_foo_cm(param): with myobj.begin_foo(param): yield def begin_foo(param): return CMAdapter(_begin_foo_cm(param))
客户端调用示例:
# 场景1:原有with写法完全不用修改 with begin_foo(param): ... # 场景2:调用返回值即可 begin_foo(param)()
内容的提问来源于stack exchange,提问作者Grodriguez
相关产品推荐
相关产品推荐

