Python 多行 with 语句

Python multi-line with statement

在 python 中创建多行 with 的干净方法是什么?我想在单个 with 中打开多个文件,但它离右边足够远,我希望它在多行中打开。像这样:

class Dummy:
    def __enter__(self): pass
    def __exit__(self, type, value, traceback): pass

with Dummy() as a, Dummy() as b,
     Dummy() as c:
    pass

可惜,那是一个SyntaxError。所以我尝试了这个:

with (Dummy() as a, Dummy() as b,
      Dummy() as c):
    pass

也是语法错误。但是,这有效:

with Dummy() as a, Dummy() as b,\
     Dummy() as c:
    pass

但是如果我想发表评论怎么办?这不起作用:

with Dummy() as a, Dummy() as b,\
     # my comment explaining why I wanted Dummy() as c\
     Dummy() as c:
    pass

\ 的位置也没有任何明显变化。

是否有一种干净的方法来创建允许在其中评论的多行 with 语句?

这不是很干净,但你可以这样做:

with Dummy() as a, Dummy() as b, (
     #my comment
     Dummy()) as c:
    pass

没有语法错误,但不是最干净的。您也可以这样做:

with Dummy() as a, Dummy() as b, Dummy(
     #my comment
     ) as c:
    pass

考虑找到一种不使用 with 中间注释的方法。

这对我来说似乎最整洁:

with open('firstfile', 'r') as (f1 # first
  ), open('secondfile', 'r') as (f2 # second
  ):
    pass

As of Python 3.10,现在可以用括号括起整个上下文管理器组,正如您最初尝试的那样:

with (Dummy() as a, Dummy() as b,
      # comment about c
      Dummy() as c):
    pass

这在 3.9 中在技术上也是可行的,但在某种半文档化的边缘。

一方面,它在 3.10 中被记录为新的,3.9 不应该 引入依赖于 new parser implementation, and the 3.9 with docs forbid this form. On the other hand, the functionality ended up getting activated in the 3.9 CPython implementation, and the (mostly?) auto-generated 3.9 full grammar spec 的任何功能(像这个)包括括号形式。


在以前的 Python 3 版本中,如果您需要在上下文管理器中穿插注释,我会使用 contextlib.ExitStack:

from contextlib import ExitStack

with ExitStack() as stack:
    a = stack.enter_context(Dummy()) # Relevant comment
    b = stack.enter_context(Dummy()) # Comment about b
    c = stack.enter_context(Dummy()) # Further information

这相当于

with Dummy() as a, Dummy() as b, Dummy() as c:

这样做的好处是您可以循环生成上下文管理器,而无需单独列出每个上下文管理器。该文档给出了一个例子,如果你想打开一堆文件,并且你有一个列表中的文件名,你可以做

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]

如果您的上下文管理器占据了太多屏幕 space 以至于您想在它们之间放置注释,您可能有足够的空间想要使用某种循环。


正如 Deathless 先生在评论中提到的,PyPI 上有一个名为 contextlib2contextlib backport。如果您使用的是 Python 2,则可以使用 ExitStack.

的后向移植实现

顺便说一句,你不能做类似的事情的原因

with (
        ThingA() as a,
        ThingB() as b):
    ...

在新的解析器实现之前是因为 ( 也可以是上下文管理器表达式的第一个标记,而 CPython 的旧解析器无法分辨当它看到第一个 ( 时应该解析什么规则。这是 PEP 617 新的基于 PEG 的解析器的激励示例之一。

喜欢, but with indenting that doesn't trigger pycodestyle's error E124

with (
        open('firstfile', 'r')) as f1, (  # first
        open('secondfile', 'r')) as f2:  # second
    pass

IMO 它仍然很难看,但至少它通过了 linter。

我会通过在 with 语句之前或行本身添加注释来使事情简单易读:

# my comment explaining why I wanted Dummy() as c
with Dummy() as a, Dummy() as b,\
     Dummy() as c: # or add the comment here
    pass

Python 仅限 3.9+:

with (
    Dummy() as a,
    Dummy() as b,
    # my comment explaining why I wanted Dummy() as c
    Dummy() as c,
):
    pass

Python≤3.8:

with \
    Dummy() as a, \
    Dummy() as b, \
    Dummy() as c:
    pass

遗憾的是,无法使用此语法进行注释。