Несколько переменных в операторе "с"?
Можно ли объявить более одной переменной с помощью инструкции 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)
... или одновременно очищает два ресурса?
Ответы
Ответ 1
Это возможно в Python 3 начиная с версии 3.1 и Python 2.7. Новый синтаксис with
поддерживает несколько контекстных менеджеров:
with A() as a, B() as b, C() as c:
doSomething(a,b,c)
В отличие от contextlib.nested
, это гарантирует, что a
и b
будут вызывать свои __exit__()
, даже если метод C()
или метод __enter__()
вызывают исключение.
Вы также можете использовать более ранние переменные в более поздних определениях (h/t Ахмад ниже):
with A() as a, B(a) as b, C(a, b) as c:
doSomething(a, c)
Ответ 2
contextlib.nested
поддерживает это:
import contextlib
with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):
...
Update:
Чтобы процитировать документацию, в отношении contextlib.nested
:
Устаревший с версии 2.7: теперь оператор with-statement поддерживает это функциональность напрямую (без запутанной ошибки, склонной к ошибкам).
См. ответ Рафаля Даугирд для получения дополнительной информации.
Ответ 3
Обратите внимание: если вы разбиваете переменные на строки, вы должны использовать обратную косую черту для переноса строк.
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)
Ошибка синтаксиса: неверный синтаксис
https://bugs.python.org/issue12782 похоже, связано с этим вопросом.
Ответ 4
Я думаю, вы хотите сделать это вместо этого:
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)
Ответ 5
Начиная с Python 3.3, вы можете использовать класс ExitStack
из модуля contextlib
.
Он может управлять динамическим числом контекстно-зависимых объектов, что означает, что он окажется особенно полезным, если вы не знаете, сколько файлов вы собираетесь обрабатывать.
Канонический вариант использования, упомянутый в документации, управляет динамическим числом файлов.
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]
Ответ 6
В 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)