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

如何创建不可作为装饰器的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:15:15