我有一个看起来像这样的函数:
import contextlib
@contextlib.contextmanager
def special_context(...):
...
yield
...
将其用作上下文管理器是合适的,如下所示:
with special_context(...):
...
...但它不适合用作装饰器:
# Not OK:
@special_context(...)
def foo():
...
我知道 Python 3.2 为
contextlib.contextmanager
添加了装饰器支持,但在我的 API 中,它指示了一个导致错误的错误。我喜欢contextlib.contextmanager
的人体工程学,但我想防止API被滥用。
是否有类似的构造可用(最好在标准库中),这将使
special_context
成为上下文管理器,而不是装饰器?
具体来说,我想要这样的东西:
@contextmanager_without_decorator
def special_context(...):
...
yield
...
请帮我找到或定义
contextmanager_without_decorator
。
contextlib.ContextDecorator
是 @contextlib.contextmanager
返回值的一部分,它赋予重新装饰的能力(在您的情况下,@special_context(...)
)。防止运行时发生这种情况的最简单方法是禁用 contextlib.ContextDecorator.__call__
。以下是 @contextmanager_without_decorator
的一种可能实现,通过跟踪 @contextlib.contextmanager
的运行时实现来重现:
from __future__ import annotations
import typing as t
import contextlib
import functools
if t.TYPE_CHECKING:
import collections.abc as cx
import typing_extensions as tx
_P = tx.ParamSpec("_P")
_ContextManagerDecoratee: tx.TypeAlias = cx.Callable[_P, cx.Generator["_T_co", None, None]]
_T_co = t.TypeVar("_T_co", covariant=True)
class DisabledContextDecorator(contextlib.ContextDecorator):
__call__: t.ClassVar[None] = None # type: ignore[assignment]
class _GeneratorContextManager(DisabledContextDecorator, contextlib._GeneratorContextManager[_T_co]):
pass
def contextmanager_without_decorator(func: _ContextManagerDecoratee[_P, _T_co], /) -> cx.Callable[_P, _GeneratorContextManager[_T_co]]:
@functools.wraps(func)
def helper(*args: _P.args, **kwds: _P.kwargs) -> _GeneratorContextManager[_T_co]:
return _GeneratorContextManager(func, args, kwds)
return helper
@contextmanager_without_decorator
def special_context() -> cx.Generator[int, None, None]:
yield 1
with special_context() as num:
assert num == 1 # OK
# Re-decoration disabled
@special_context()
def foo(): # TypeError: 'NoneType' object is not callable
...