Несколько переменных в операторе "с"?

Можно ли объявить более одной переменной с помощью инструкции 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)