【问题标题】:Python multi-line with statementPython 多行 with 语句
【发布时间】:2015-09-11 09:23:40
【问题描述】:

在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

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

有没有一种简洁的方法来创建一个允许在其中包含 cmets 的多行 with 语句?

【问题讨论】:

  • 实际上,最大的问题是 PEP-8 对这些东西的看法,因为 PEP-8 将行长限制为 80 个字符,这就是这样做的必要性。
  • 意见时间:PEP-8 很棒,但我认为 80 个字符的限制太低了。它基于旧的终端限制,而不是人类可用性。我不提倡300+字的台词,但120-130左右没问题。
  • @TigerhawkT3 我认为 80 个字符的限制也很低,但是当我处理需要同时打开 5 个文件的项目时,我看到了它的好处。能够查看每个文件要容易得多。不过,我可能会为这个文件做一个例外。
  • PEP-8 是explicitly ok,对于多行with 语句,带有` \ ` 行继续,因为你不能使用隐式继续。但是,如果您想内联 cmets,这对您的情况并没有真正的帮助。

标签: python python-3.x multiline with-statement


【解决方案1】:

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 的功能(例如这个),而 3.9 with docs 禁止这样做形式。另一方面,该功能最终在 3.9 CPython 实现中被激活,并且(大部分?)自动生成的 3.9 full grammar spec 包括括号形式。


在以前的 Python 3 版本中,如果您需要在上下文管理器中散布 cmets,我会使用 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]

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


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


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

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

在新的解析器实现之前是因为( 也可以是上下文管理器表达式的第一个标记,而 CPython 的旧解析器在看到第一个(。这是 PEP 617 新的基于 PEG 的解析器的激励示例之一。

【讨论】:

  • 这是一个很好的解决方案。
  • pypi 上有一个对 Python 2 的 contextlib 改进的反向移植。它提供ExitStack() 等等。
【解决方案2】:

这对我来说似乎最整洁:

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

【讨论】:

    【解决方案3】:

    仅限 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
    

    很遗憾,这种语法无法使用 cmets。

    【讨论】:

    • @Justin 它适用于 3.90a6,您可以使用 pyenv 安装 :)
    • @justin 3.9 发布
    • @ThomasGrainger 感谢您的 ping。我试过了,它确实适用于 Python 3.9
    • official docswith 语句不允许使用括号。看起来当他们在文档中生成full grammar spec 时,他们是从一个具有带括号规则的语法文件生成它并将该规则保留在其中,所以我猜这个更改现在处于半文档化的边缘。
    • @user2357112supportsMonica 很公平,不过这应该是 cpython 中的 PR。 Guido 对新的 PEG 语法感到非常自豪。他很快就向我展示了带括号的 with 语句现在可以使用了。
    【解决方案4】:

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

    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 中间的 cmets 的方法。

    【讨论】:

    • 有趣的事实:他 OP。
    • @DevShark 这可以满足我的要求,但是有没有一种方法可以做到这点不那么疯狂?这就是我想知道的问题。有时我希望 python 能够说,“在这一行的末尾有一个运算符,需要在它后面加上一些东西,所以我最好检查下一行是否有另一个操作数。”
    • 无意冒犯;我的乐趣来自@DevShark 缺乏认识。但是,如果您经常只需要多花几分钟来自己弄清楚一些事情,也许您可​​以在提问之前这样做?更加自信。 :)
    • @Downvoter 你能解释一下这个答案有什么不好的地方,以便我改进我未来的答案吗?
    • @Justin 如果您通常在最终崩溃并提出问题后解决问题,我可以建议您身边的橡皮鸭吗? ;-) 无论哪种方式,如果只是在不久之后自己回答问题,在线发布问题很少是一件坏事。为后人所用,为他人谋福利!
    【解决方案5】:

    我会通过在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
    

    【讨论】:

      【解决方案6】:

      类似于TigerhawkT3's answer,但缩进不会触发pycodestyle's error E124

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多