创建不是装饰器的上下文管理器的最简洁方法?

问题描述 投票:0回答:1

我有一个看起来像这样的函数:

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

python python-decorators
1个回答
0
投票

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
    ...
© www.soinside.com 2019 - 2024. All rights reserved.