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

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

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

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

我该如何定义一个这样微不足道的、空的上下文管理器?Python库中有现成的吗?

如果我们想在as子句中使用上下文管理器怎么办?

with ctx_mgr as resource:
    <operations on resource>

4
if 1: 替代 with whatever: 怎么样? - Alfe
这与此问题密切相关。 - Michael Litvin
6个回答

131

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

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

自Python 3.4起,可以在第一种情况下(即没有as子句时)使用contextlib.suppress来实现这个目的:

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__()中返回一个在托管块内呱呱叫的东西,dummy_resource也将需要支持,尽管可能什么也不做。

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

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

with ctx_mgr as someDuck:
    someDuck.quack()

来源:一个Python功能请求。非常感谢所有为讨论做出贡献的人。这是我尝试在一个自问自答的问题中总结其结果,以节省大家阅读那个冗长线程的时间。还请参阅Python文档对使用ExitStack的提及。


2
文档在这里https://docs.python.org/3/library/contextlib.html#simplifying-support-for-single-optional-context-managers提到了ExitStack作为一个什么都不做的上下文管理器。 - Gribouillis
4
最终,在2017年11月23日更新中,出现了一个带有“nullcontext”的提交 https://bugs.python.org/issue10049#msg306770 - maxkoryukov
非常感谢@maxkoryukov,我已经更新了答案。 - Julian
2
一个在3.4及以上版本中可用的稍微快一点的无操作上下文管理器(在nullcontext之前可用)是contextlib.suppress。它接受varargs,你可以根本不传递任何异常:with suppress():将不会抑制任何内容,并且它比ExitStack的开销要小得多。 - ShadowRanger
如果我想支持 Python 3.3 之前的版本,并且不想向 NullContextManager() 传递参数,那么这个解决方案是否可行:class NullContextManager: def __enter__(self): pass def __exit__(self, *args): pass - Basj

14

对于Python 3.6及以下版本,包括2.7,有一个简单的解决方案:

from contextlib import contextmanager

@contextmanager
def nullcontext(enter_result=None):
    yield enter_result
自 Python 3.7 起,您应该使用提供的 contextlib.nullcontext

感谢您提供的代码片段。您的代码应该放在调用文件中,对吗? - Cloud Cho

6

1

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

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

0

对于 Python 2.7+,您可以使用

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

为什么要踩?这个代码是有效的,而且准确地回答了问题。它是一个内联的无操作上下文管理器。 - cowlinator
我没有投反对票。嘿,我发现了你的答案,想让你看看我在找到这个stackoverflow帖子之前的代码:namedtuple("FreeScope",("scope",))(lambda:type("FreeContext",tuple(),{"__enter__":lambda_,__=None,___=None,____=None:None,"__exit__":lambda_,__=None,___=None,____=None:None,},)) - Samuel Marks

-5
我刚刚使用了 threading.Lock() 当做一个虚拟的上下文管理器。这是一个临时锁,只被上下文管理器使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接