在多个上下文管理器上创建 "with" 块?
假设你有三个对象,它们是通过上下文管理器获取的,比如一个锁、一个数据库连接和一个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 个回答
你问题的第一部分在 Python 3.1 中是可以实现的。
当有多个项目时,上下文管理器的处理方式就像多个 with 语句嵌套在一起一样:
with A() as a, B() as b: suite
这相当于
with A() as a: with B() as b: suite
在3.1版本中进行了更改: 支持多个上下文表达式
从 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()
在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版本中已经被弃用,推荐使用上面的方法。