510

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

就像是:

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)

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

4

8 回答 8

823

从 v3.1Python 2.7开始,它在 Python 3 中是可能的。新with语法支持多个上下文管理器:

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

与 不同contextlib.nested,这保证a了'b__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 开始,您可以使用括号

with (
    A() as a, 
    B(a) as b, 
    C(a, b) as c,
):
    doSomething(a, c)
于 2009-07-02T11:25:50.873 回答
70

请注意,如果将变量拆分为行,在 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]:

新的解析器可以正确解析被括号包围的多个上下文管理器。新解析器已在 3.9 中启用。据报道,在 Python 3.10 中删除旧的解析器之前,该语法仍将被拒绝,并且此语法更改已在3.10 发行说明中报告。但在我的测试中,它也适用于 trinket.io 的 Python 3.9.6。

于 2018-06-20T23:32:09.757 回答
61

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 的回答

于 2009-05-21T14:55:42.423 回答
24

从 Python 3.3 开始,您可以使用模块中的ExitStackcontextlib

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

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

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]
于 2018-05-10T06:18:05.900 回答
17

我想你想这样做:

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)
于 2009-05-21T14:55:22.890 回答
5

从 Python 3.10 开始,有一个带括号的上下文管理器的新功能,它允许以下语法:

with (
    A() as a,
    B() as b
):
    do_something(a, b)
于 2021-02-26T15:37:46.390 回答
2

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

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

相当于

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

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

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
于 2019-09-18T14:56:52.157 回答
1

您还可以创建上下文管理器(__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给人的印象是创建的对象也可以在语句之外进一步使用。如果您的上下文管理器不是这样,则错误印象可能与可读性尝试相对应。

文档说:

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

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

于 2020-10-21T13:12:43.867 回答