多个变量在'声明吗?

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

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

185170 次浏览

我认为你应该这样做:

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)

contextlib.nested支持这个:

import contextlib


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


...
< p > # EYZ0 < br > 引用文档,关于contextlib.nested:

with语句现在支持此功能 直接的功能(没有令人困惑的容易出错的怪癖)

有关更多信息,请参阅拉法沃格德的回答是

Python 3自v3.1Python 2.7中是可能的。新的# EYZ0语法支持多个上下文管理器:

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

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

你也可以在后面的定义中使用之前的变量(下面是h/t 艾哈迈德):

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

请注意,如果将变量拆分为行,在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()):和“值元组”;# EYZ1。

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

在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)

您还可以单独的创建一个上下文管理器(__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.10开始,有了括号括起来的上下文管理器的新特性,它允许如下语法:

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