【问题标题】:Multiple variables in a 'with' statement?'with'语句中的多个变量?
【发布时间】:2010-10-27 23:00:06
【问题描述】:

是否可以在 Python 中使用with 语句声明多个变量?

类似:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

...还是同时清理两个资源的问题?

【问题讨论】:

  • 可能是这样:用 [expr1,expr2] 作为 f: 然后使用 f[0] 和 f[1]。
  • 会很好,因为不需要导入某些东西....但它不起作用 AttributeError: 'list' object has no attribute 'exit'
  • 如果python只有闭包,你就不需要with语句
  • 您不需要 使用 with 语句,对吧?您可以将 file_out 和 file_in 设置为 None,然后执行 try/except/finally,在其中打开它们并在 try 中处理它们,如果它们不是 None,则在 finally 中关闭它们。不需要双缩进。
  • 这些答案中的许多不涉及两个以上 with 语句的需要。理论上可能存在需要打开数十个上下文的应用程序,如果施加任何行长度限制,嵌套很快就会崩溃。

标签: python with-statement


【解决方案1】:

Python 3 since v3.1Python 2.7 中是可能的。新的with syntax 支持多个上下文管理器:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

contextlib.nested 不同,这保证ab 将调用它们的__exit__(),即使C() 或其__enter__() 方法引发异常。

您还可以在以后的定义中使用较早的变量(h/t Ahmad 下面):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)

从 Python 3.10 开始,you can use parentheses

with (
    A() as a, 
    B(a) as b, 
    C(a, b) as c,
):
    doSomething(a, c)

【讨论】:

  • 是否可以将字段设置为等于with open('./file') as arg.x = file:with open('./file') as arg.x = file: 中的语句?
  • 另外,有可能:A() 作为 a,B(a) 作为 b,C(a,b) 作为 c:
  • 类 test2: x=1; t2=test2() with open('f2.txt') as t2.x: for l1 in t2.x.readlines(): print(l1); # Charlie Parker # 在 python 3.6 中测试
  • 请注意,as 是可选的。
  • 澄清@SławomirLenart 的意思:如果您需要ab 对象,则需要as,但不需要整个as aas b
【解决方案2】:

请注意,如果将变量拆分为行,在 Python 3.10 之前,您必须使用反斜杠来换行。

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

括号不起作用,因为 Python 会创建一个元组。

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

由于元组缺少__enter__ 属性,您会收到错误(无法描述且无法识别类类型):

AttributeError: __enter__

如果您尝试在括号内使用as,Python 会在解析时捕获错误:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)
SyntaxError: invalid syntax

什么时候能解决这个问题?

https://bugs.python.org/issue12782 中跟踪此问题。

Python 在PEP 617 中宣布他们将用新的解析器替换原来的解析器。因为 Python 的原始解析器是 LL(1),它在“多个上下文管理器”with (A(), B()): 和“值元组”with (A(), B())[0]: 之间cannot distinguish

新的解析器可以正确解析多个用括号括起来的上下文管理器。新解析器已在 3.9 中启用。据报道,在 Python 3.10 中删除旧的解析器之前,这种语法仍然会被拒绝,并且在3.10 release notes 中报告了这种语法变化。但在我的测试中,它也适用于 trinket.io 的 Python 3.9.6。

【讨论】:

    【解决方案3】:

    contextlib.nested 支持这个:

    import contextlib
    
    with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):
    
       ...
    

    更新:
    引用文档,关于contextlib.nested

    自 2.7 版起已弃用:with 语句现在支持此功能 直接的功能(没有令人困惑的容易出错的怪癖)。

    请参阅Rafał Dowgird's answer 了解更多信息。

    【讨论】:

    • 我很抱歉这么说,但我认为nested 上下文管理器是一个错误,永远不应该使用。在本例中,如果打开第二个文件引发异常,则根本不会关闭第一个文件,从而完全破坏了使用上下文管理器的目的。
    • 你为什么这么说?文档说使用嵌套等同于嵌套'with's
    • @Rafal:看一下手册似乎表明 python 正确地嵌套了 with 语句。真正的问题是,如果第二个文件在关闭时抛出异常。
    • @James:不,docs.python.org/library/contextlib.html#contextlib.nested 文档中的等效代码与标准嵌套的 with 块不同。管理器是按 输入 with 块之前创建的:m1、m2、m3 = A()、B()、C() 如果 B() 或 C() 因异常而失败,那么您的正确完成 A() 的唯一希望是垃圾收集器。
    • Deprecated since version 2.7。注意:with 语句现在直接支持此功能(没有容易混淆的错误怪癖)。
    【解决方案4】:

    从 Python 3.3 开始,您可以使用来自 contextlib 模块的类 ExitStack

    它可以管理 动态 数量的上下文感知对象,这意味着如果您不知道要处理多少个文件,它会特别有用。

    文档中提到的规范用例是管理动态数量的文件。

    with ExitStack() as stack:
        files = [stack.enter_context(open(fname)) for fname in filenames]
        # All opened files will automatically be closed at the end of
        # the with statement, even if attempts to open files later
        # in the list raise an exception
    

    这是一个通用示例:

    from contextlib import ExitStack
    
    class X:
        num = 1
    
        def __init__(self):
            self.num = X.num
            X.num += 1
    
        def __repr__(self):
            cls = type(self)
            return '{cls.__name__}{self.num}'.format(cls=cls, self=self)
    
        def __enter__(self):
            print('enter {!r}'.format(self))
            return self.num
    
        def __exit__(self, exc_type, exc_value, traceback):
            print('exit {!r}'.format(self))
            return True
    
    xs = [X() for _ in range(3)]
    
    with ExitStack() as stack:
        print(stack._exit_callbacks)
        nums = [stack.enter_context(x) for x in xs]
        print(stack._exit_callbacks)
    print(stack._exit_callbacks)
    print(nums)
    

    输出:

    deque([])
    enter X1
    enter X2
    enter X3
    deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
    exit X3
    exit X2
    exit X1
    deque([])
    [1, 2, 3]
    

    【讨论】:

      【解决方案5】:

      我想你想这样做:

      from __future__ import with_statement
      
      with open("out.txt","wt") as file_out:
          with open("in.txt") as file_in:
              for line in file_in:
                  file_out.write(line)
      

      【讨论】:

      • 这就是我目前的做法,但是嵌套深度是我想要(意思是)它的两倍......
      • 我认为这是最干净的方法 - 任何其他方法都将更难阅读。 Alex Martelli 的答案似乎更接近你想要的,但可读性要差得多。为什么嵌套如此受关注?
      • 诚然,这没什么大不了的,但是,根据“import this”(又名“Zen of Python”),“平面优于嵌套”——这就是我们将 contextlib.nested 添加到标准的原因图书馆。顺便说一句,3.1 可能有一个新语法“with A() as a, B() as b:”(补丁在,但到目前为止还没有关于它的 BDFL 声明)以获得更直接的支持(很明显,库解决方案不是t 被认为是完美的......但避免不必要的嵌套绝对是核心 Python 开发人员广泛共享的目标)。
      • @Alex:非常正确,但我们还必须考虑“可读性很重要”。
      • @Andrew:我认为一级缩进更好地表达了程序的预期逻辑,即“原子地”创建两个变量,然后一起清理它们(我意识到这实际上不是发生什么了)。认为异常问题是一个交易破坏者
      【解决方案6】:

      从 Python 3.10 开始,Parenthesized context managers 有了一个新功能,它允许以下语法:

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

      【讨论】:

      • 爱它!最后! (我将能够在 2030 年左右将它与 debian 一起使用。??)
      【解决方案7】:

      在 Python 3.1+ 中,您可以指定多个上下文表达式,它们将像嵌套多个 with 语句一样被处理:

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

      等价于

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

      这也意味着您可以在第二个表达式中使用第一个表达式的别名(在使用数据库连接/游标时很有用):

      with get_conn() as conn, conn.cursor() as cursor:
          cursor.execute(sql)
      

      【讨论】:

        【解决方案8】:

        您还可以separate 创建上下文管理器(__init__ 方法)并输入上下文(__enter__ 方法)以提高可读性。所以不要写这段代码:

        with Company(name, id) as company, Person(name, age, gender) as person, Vehicle(brand) as vehicle:
            pass
        

        你可以写这段代码:

        company = Company(name, id)
        person = Person(name, age, gender)
        vehicle = Vehicle(brand)
        
        with company, person, vehicle:
            pass
        

        请注意,在with 语句之外创建上下文管理器给人的印象是,创建的对象也可以在语句之外进一步使用。如果您的上下文管理器不是这样,则错误印象可能与可读性尝试相对应。

        documentation 说:

        大多数上下文管理器的编写方式意味着它们只能在 with 语句中有效使用一次。这些一次性上下文管理器必须在每次使用时重新创建 - 再次尝试使用它们会触发异常或无法正常工作。

        这个常见的限制意味着通常建议直接在使用它们的 with 语句的标题中创建上下文管理器。

        【讨论】:

        • 请注意,很多上下文管理器在上下文之后是没有意义的。在上下文之前定义它们的名称意味着无论上下文如何,该名称都是有效的,即在它之后也是如此。虽然不是绑定限制(行为相同),但这可能被视为避免单独定义以提高可读性和清晰度的原因。
        • @MisterMiyagi 啊,很好,谢谢。我已经阅读了更多内容并更新了答案。我有时会在上下文之后使用上下文管理器:d = Document('foo'); with d: d.write('bar'); print('OK', d.path),但我不知道这个细节。
        猜你喜欢
        • 2020-11-09
        • 2020-02-29
        • 1970-01-01
        • 2012-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多