在多个上下文管理器上创建 "with" 块?

333 投票
4 回答
142100 浏览
提问于 2025-04-15 23:51

假设你有三个对象,它们是通过上下文管理器获取的,比如一个锁、一个数据库连接和一个IP套接字。你可以这样获取它们:

with lock:
   with db_con:
       with socket:
            #do stuff

但是有没有办法在一个代码块里同时获取它们呢?类似于这样:

with lock,db_con,socket:
   #do stuff

此外,如果有一个未知长度的对象数组,这些对象都有上下文管理器,是否可以以某种方式做到:

a=[lock1, lock2, lock3, db_con1, socket, db_con2]
with a as res:
    #now all objects in array are acquired

如果答案是否定的,那是因为需要这样的功能说明设计不够好,还是我应该在PEP中建议一下呢?:-P

4 个回答

36

你问题的第一部分在 Python 3.1 中是可以实现的。

当有多个项目时,上下文管理器的处理方式就像多个 with 语句嵌套在一起一样:

with A() as a, B() as b:
    suite

这相当于

with A() as a:
    with B() as b:
        suite

在3.1版本中进行了更改: 支持多个上下文表达式

105

从 Python 3.10 开始,你可以使用 带括号的上下文管理器!感谢 @iforapsy 的贡献!

with (
    mock.patch('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') as a,
    mock.patch('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') as b,
    mock.patch('cccccccccccccccccccccccccccccccccccccccccc') as c,
):
    do_something()

对于 Python 版本低于 3.10 的情况

@interjay 的回答是正确的。不过,如果你需要处理比较长的上下文管理器,比如 mock.patch 的上下文管理器,你会发现想要换行写的时候就有点麻烦。结果你不能用括号把它们包起来,所以你得用反斜杠来换行。下面是这样写的样子:

with mock.patch('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') as a, \
        mock.patch('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') as b, \
        mock.patch('cccccccccccccccccccccccccccccccccccccccccc') as c:
    do_something()
555

Python 2.7 和 3.1 及以上版本中,你可以这样写:

with A() as X, B() as Y, C() as Z:
    do_something()

这通常是最好的方法,但如果你有一个长度不确定的上下文管理器列表,你就需要使用下面的方法之一。


Python 3.3中,你可以通过使用contextlib.ExitStack来输入一个长度不确定的上下文管理器列表:

with ExitStack() as stack:
    for mgr in ctx_managers:
        stack.enter_context(mgr)
    # ...

这让你在添加上下文管理器到ExitStack时可以创建它们,这样可以避免使用contextlib.nested时可能出现的问题(下面会提到)。

contextlib2为Python 2.6和2.7提供了一个ExitStack的回溯版本


Python 2.6 及以下版本中,你可以使用contextlib.nested

from contextlib import nested

with nested(A(), B(), C()) as (X, Y, Z):
    do_something()

这相当于:

m1, m2, m3 = A(), B(), C()
with m1 as X:
    with m2 as Y:
        with m3 as Z:
            do_something()

注意,这和通常使用嵌套的with并不完全相同,因为A()B()C()会在进入上下文管理器之前被调用。如果这些函数中的一个抛出异常,这样做就会出错。

contextlib.nested在较新的Python版本中已经被弃用,推荐使用上面的方法。

撰写回答