如何在 Python 中编写空(无操作)上下文管理器?

How do I write a null (no-op) contextmanager in Python?

有时我需要一个什么都不做的虚拟上下文管理器。然后它可以用作更有用但可选的上下文管理器的替代品。例如:

ctx_mgr = <meaningfulContextManager> if <condition> else <nullContextManager>
with ctx_mgr:
    ...

如何定义如此琐碎、空洞的上下文管理器? Python 图书馆是否提供现成的?

我们希望将上下文与 as 子句一起使用的情况如何?

with ctx_mgr as resource:
    <operations on resource>

Python3.7及以上:使用contextlib.nullcontext,为此专门设计

在 Python 3.7 之前,标准库不提供专门为这些用例设计的上下文管理器,但有一些解决方法。

因为 Python 3.4contextlib.suppress 可以用于第一种情况,即当没有 as 子句时:

ctx_mgr = <meaningfulContextManager> if <condition> else contextlib.suppress()

with ctx_mgr:
    ...

自 Python 3.3 以来,也可以使用类似的解决方法,contextlib.ExitStack,尽管比 suppress 慢(需要两倍只要在我的测试中)。

在 Python 3.3 之前,或者 如果你需要在 [=56= 之前的 as 子句 ] 3.7、开发者需要自己滚。 这是一种可能的实现方式(见底部的注释,但所有错误都是我的):

class NullContextManager(object):
    def __init__(self, dummy_resource=None):
        self.dummy_resource = dummy_resource
    def __enter__(self):
        return self.dummy_resource
    def __exit__(self, *args):
        pass

然后可以写:

ctx_mgr = <meaningfulContextManager> if <condition> else NullContextManager(dummy_resource)

with ctx_mgr as resource:
    <operations on resource>

当然,dummy_resource 需要支持 "meaningful" 资源所需的所有操作。因此,例如,如果有意义的上下文管理器,在 __enter__()、returns 上对托管块内的 quack() 进行的某些操作,dummy_resource 也将需要支持它,尽管可能什么都不做。

class DummyDuck(object):
    def quack()
        # Ssssh...
        pass

ctx_mgr = <meaningfulContextManager> if <condition> else NullContextManager(DummyDuck())

with ctx_mgr as someDuck:
    someDuck.quack()

来源:A Python feature request. Many thanks to all those who contributed to that discussion. This is my attempt at summarising its outcome in a self-answered question, to save people time reading that long thread. Also see Python documentation's mention of this use of ExitStack.

我只是将 threading.Lock() 用作虚拟上下文管理器。临时锁,仅供上下文管理器使用。

自 Python 3.2 起,memoryview(b'') 可用作空操作上下文管理器。参见 https://docs.python.org/3/library/stdtypes.html#memoryview.release

优点

  • 无需导入

  • 适用于 3.2+

  • 大约是 contextlib.nullcontext

  • 的两倍

缺点

  • 您可能想添加 # no-op 评论。

Python 3.6 及以下版本(包括 2.7)的简单解决方案:

from contextlib import contextmanager

@contextmanager
def nullcontext(enter_result=None):
    yield enter_result

从 Python 3.7 开始,您应该改用提供的 contextlib.nullcontext

对于 Python 2.7+ ,您可以使用

import contextlib
with (lambda noop_func: contextlib.contextmanager(noop_func))(lambda: (yield))():
    print("hi")

Python 2.7 的简单解决方案,答案中尚未提及:

from contextlib import nested
with nested():
    ...