【问题标题】:How do I write a null (no-op) contextmanager in Python?如何在 Python 中编写空(无操作)上下文管理器?
【发布时间】:2017-12-24 12:46:06
【问题描述】:

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

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

如何定义这样一个琐碎的、空的上下文管理器? Python 库是否提供现成的库?

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

with ctx_mgr as resource:
    <operations on resource>

【问题讨论】:

  • if 1: 代替with whatever: 怎么样?
  • 这与this question密切相关。

标签: python contextmanager


【解决方案1】:

Python 3.7 及以上版本:使用contextlib.nullcontext,专门为此设计。

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

自 Python 3.4 起,contextlib.suppress 可用于第一种情况,即没有 as 子句时:

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

with ctx_mgr:
    ...

从 Python 3.3 开始,也可以使用类似的解决方法,contextlib.ExitStack,尽管比 suppress 慢(在我的测试中它需要两倍的时间)。

在 Python 3.3 之前,或者如果您需要在 Python 3.7 之前的as 子句,开发人员需要自己推出。 这是一种可能的实现(见底部的注释,但所有错误都是我的):

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 需要支持“有意义”资源所需的所有操作。因此,例如,如果__enter__() 上的有意义的上下文管理器返回托管块内的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。非常感谢所有参与讨论的人。这是我在一个自我回答的问题中总结其结果的尝试,以节省人们阅读该长线程的时间。另请参阅 Python 文档中提到的 this use of ExitStack

【讨论】:

  • 文档在这里提到了 ExitStack 作为无操作上下文管理器 docs.python.org/3/library/…
  • UPD 23 Nov, 2017,最后,有一个带有nullcontextbugs.python.org/issue10049#msg306770的提交
  • 非常感谢@maxkoryukov,我已经更新了答案。
  • 在 3.4 及更高版本中(在 nullcontext 可用之前)提供的更快的无操作上下文管理器是 contextlib.suppress。它需要可变参数,你可以简单地不向它传递任何异常:with suppress(): 不会抑制任何内容,而且它的开销比ExitStack 少得多。
  • 如果我想在 Python 3.3 之前支持,并且如果我不想将参数传递给NullContextManager(),这个解决方案可以吗:class NullContextManager:def __enter__(self): passdef __exit__(self, *args): pass?跨度>
【解决方案2】:

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

from contextlib import contextmanager

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

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

【讨论】:

    【解决方案3】:

    从 Python 3.2 开始,memoryview(b'') 可以用作无操作上下文管理器。见https://docs.python.org/3/library/stdtypes.html#memoryview.release

    优点

    • 无需导入

    • 适用于 3.2+

    • 大约是contextlib.nullcontext的两倍

    缺点

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        对于 Python 2.7+ ,您可以使用

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

        【讨论】:

        • 为什么投反对票?这有效,并准确地回答了问题。这是一个内嵌的无操作上下文管理器
        • [I didn't downvote] 嘿找到了你的答案,我想你可能想在我发现这个 stackoverflow 帖子之前看到我的答案:namedtuple("FreeScope",("scope",))(lambda:type("FreeContext",tuple(),{"__enter__":lambda_,__=None,___=None,____=None:None,"__exit__":lambda_,__=None,___=None,____=None:None,},))
        【解决方案6】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-05-22
          • 1970-01-01
          • 1970-01-01
          • 2021-03-02
          • 1970-01-01
          • 2013-03-16
          • 1970-01-01
          相关资源
          最近更新 更多