Python多行with语句

63

在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 语句?


实际上,最重要的问题是PEP-8对此事的规定,因为PEP-8将行长限制在80个字符,这就是为什么需要这样做的原因。 - Justin
1
@TigerhawkT3 我认为80个字符的限制也太低了,但是当我在处理需要同时打开5个文件的项目时,它确实有好处。这样可以更容易地查看每个文件。不过,对于这个文件,我可能会例外。 - Justin
8
PEP-8明确表示在多行with语句中使用\进行行续,因为您无法使用隐式续行。然而,如果您想内联注释,这并不能真正帮助您的情况。 - Eric Appelt
6个回答

70

从 Python 3.10 开始,现在可以将所有上下文管理器放在括号中,就像最初尝试的那样:

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

在技术上,3.9 版本也可以实现这个功能,但它处于一种半文档化的状态。

一方面,它被记录为 3.10 中的新功能,而 3.9 并不应该引入任何依赖于新解析器实现的功能(比如这个)。同时,3.9 的 with 文档禁止使用这种形式。另一方面,这个功能最终在 3.9 的 CPython 实现中被激活了,(大部分?)自动生成的 3.9 完整的语法规范包括带圆括号的形式。


在之前的 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]

如果你的上下文管理器占用了大量屏幕空间,以至于你想在它们之间添加注释,那么可能有足够的理由使用某种类型的循环。


就像 Mr. Deathless 在评论中提到的那样,PyPI 上有一个名为 contextlib2contextlib 回溯 版本。如果你使用的是 Python 2,可以使用回溯的实现来调用 ExitStack


顺便说一句,你不能像这样做的原因是:

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

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


3
有一个将contextlib改进后移植到Python 2的版本在pypi上。它提供了ExitStack()等功能。 - Mr. Deathless

17

仅支持 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

很抱歉,使用这种语法无法添加评论。


@justin 3.9已发布。 - Thomas Grainger
目前还不是官方支持的功能。很可能会成为3.10版的官方功能,但目前它是与官方语法不符的未经记录的偏差。当PyPy或其他项目开始支持3.9时,在其他Python 3.9实现中它可能无法正常工作。 - user2357112
1
with 语句的官方文档不允许使用括号。看起来在文档中生成 完整的语法规范时,他们从一个带有括号规则的语法文件中生成了该文档,并将该规则留了下来。因此,我想这个更改现在处于半文档化的状态。 - user2357112
1
@user2357112支持Monica,好的,这应该在cpython中进行PR。Guido对新的PEG语法感到非常自豪。他很快向我展示了带括号的with语句现在可以工作了。 - Neil G
@NeilG:他们实际上无法正式添加支持,因为3.9官方支持使用旧的LL(1)解析器或新的PEG解析器进行编译。3.10是第一个放弃LL(1)解析器支持的版本,因此它是第一个可以正式使用需要PEG解析器的语法特性的版本。PEG解析器是3.9的默认解析器,但奇怪/自定义的Python 3.9构建可能不会使用它。 - ShadowRanger
显示剩余3条评论

12

我认为这样最整洁:

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

3

这并不是特别优雅的方式,但你可以这样做:

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中间的注释的情况下完成此操作。

10
有趣的事实:他就是原帖作者。 - TigerhawkT3
1
@DevShark 这个做到了我想要的效果,但有没有一种不那么疯狂的方法来实现呢?这就是我想知道的问题。有时候我希望Python能够说:“这行代码末尾有一个运算符,需要在其后面加上另一个操作数,所以我最好检查下一行。” - Justin
2
没有冒犯之意;我觉得有趣的是@DevShark 没有意识到这点。但是,如果你通常只需要多花几分钟来自己解决问题,或许在提问之前可以先尝试一下?要更加自信哦。 :) - TigerhawkT3
3
@Downvoter,您能否解释一下这个答案有什么问题,以便我在未来改进我的回答? - Justin
5
@Justin,如果你通常在最终崩溃并问问题后才能弄清楚事情,我可以建议你身边放一个小黄鸭作为倾诉对象;-) 无论如何,即使只是为了很快回答自己的问题,将问题发布在网上往往也不会有坏处。 这对于后人和他人都有好处! - Victor Zamanian
显示剩余2条评论

3
我会在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

0

TigerhawkT3的回答一样,但是缩进不会触发pycodestyle的错误E124

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

我个人认为它仍然不够美观,但至少它通过了代码检查。


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