是否可以在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)

... 还是同时清理两个资源才是问题所在?


当前回答

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

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

不像contextlib。嵌套的,这保证了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)

其他回答

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

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

你也可以将创建上下文管理器(__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语句的头中创建上下文管理器。

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

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

不像contextlib。嵌套的,这保证了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)

contextlib。Nested支持:

import contextlib

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

   ...

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

2.7版后已移除:with-statement现在支持此功能 直接使用功能(没有容易出错的令人困惑的怪癖)。

更多信息请参见rafawarsaw Dowgird的回答。

从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]