如何创建不可作为装饰器的Python上下文管理器?
实现仅作为上下文管理器的装饰器
问题背景
我们使用contextlib.contextmanager定义了上下文管理器,但Python 3.2后它支持被用作装饰器,而我们的API中这种用法会引发bug,需要禁止该用法,同时保留作为上下文管理器的核心功能。
解决方案一:封装移除装饰器能力
自定义contextmanager_without_decorator装饰器,基于标准库实现,返回仅支持上下文管理器协议的对象:
import contextlib def contextmanager_without_decorator(func): # 用标准库contextmanager处理生成器 base_cm = contextlib.contextmanager(func) # 定义仅支持上下文管理器的类,不实现__call__方法 class _ContextOnly: def __init__(self, *args, **kwargs): self._inner_cm = base_cm(*args, **kwargs) def __enter__(self): return self._inner_cm.__enter__() def __exit__(self, exc_type, exc_val, exc_tb): return self._inner_cm.__exit__(exc_type, exc_val, exc_tb) # 返回包装后的类,调用时生成上下文管理器实例 return _ContextOnly
使用示例
@contextmanager_without_decorator def special_context(): print("进入上下文") yield print("退出上下文") # 正确用法:作为上下文管理器 with special_context(): print("执行上下文内逻辑") # 错误用法:尝试作为装饰器会报错 @special_context() def foo(): print("foo") # 执行时抛出 TypeError: '_ContextOnly' object is not callable
解决方案二:主动拦截装饰器调用并报错
如果希望给出更明确的错误提示,可以在被当作装饰器使用时主动抛出异常:
import contextlib def contextmanager_without_decorator(func): base_cm = contextlib.contextmanager(func) def wrapper(*args, **kwargs): cm_instance = base_cm(*args, **kwargs) # 拦截__call__方法,防止被用作装饰器 original_call = getattr(cm_instance, '__call__', None) if original_call: def restricted_call(obj): if callable(obj): raise TypeError("该上下文管理器禁止用作装饰器") return original_call(obj) cm_instance.__call__ = restricted_call return cm_instance return wrapper
使用示例
@contextmanager_without_decorator def special_context(): print("进入上下文") yield print("退出上下文") # 尝试作为装饰器时,直接抛出明确错误 @special_context() def foo(): pass # 执行时抛出 TypeError: 该上下文管理器禁止用作装饰器
方案对比
- 方案一从根源上移除了装饰器能力,因为返回的对象没有
__call__方法,完全符合上下文管理器协议但无法被当作装饰器使用。 - 方案二保留了
contextlib.contextmanager的原有实现,但通过拦截__call__方法,在用户误用时报出清晰的错误信息,更具友好性。
内容的提问来源于stack exchange,提问作者meshy
相关产品推荐
相关产品推荐

