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

391

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

... или проблема очистки двух ресурсов одновременно?

рыба фугу
источник
Может быть так: с [expr1, expr2] как f: и затем используйте f [0] и f [1].
JBASKO
Было бы неплохо, потому что не нужно импортировать что-то .... но это не работает AttributeError: у объекта 'list' нет атрибута ' exit '
pufferfish
Если бы у python были только замыкания, вам не понадобилось бы утверждение with
BT
Вам не нужно использовать оператор with, верно? Вы можете просто установить для file_out и file_in значение None, а затем выполнить попытку / исключить / finally, где вы их открываете и обрабатываете в попытке, а затем, наконец, закрываете их, если они не равны None. Для этого не требуется двойной отступ.
М Кац
1
Многие из этих ответов не касаются необходимости более двух утверждений. Теоретически могут существовать приложения, которым нужно открывать десятки контекстов, вложение очень быстро распадается при наложении ограничений на длину строки.
ThorSummoner

Ответы:

667

Это возможно в 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__()метод вызывает исключение.

Вы также можете использовать более ранние переменные в более поздних определениях (ч / т Ахмад ниже):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)
Рафал Доугирд
источник
1
Можно ли установить поля, равные чему-то в с оператором, как в with open('./file') as arg.x = file:?
Чарли Паркер
13
Кроме того, это возможно: с A () как a, B (a) как b, C (a, b) как c:
Ахмад Йусофан
класс test2: x = 1; t2 = test2 () с открытым ('f2.txt') как t2.x: для l1 в t2.x.readlines (): print (l1); # Чарли Паркер # протестирован на python 3.6
Ахмад Юсофан
1
пожалуйста, обратите внимание, asэто необязательно.
Славомир Ленарт
уточнить, что @ SławomirLenart говорит: asтребуется, если вам нужен объект aили b, но целиком as aили as bне требуется
Ciprian Tomoiagă
56

contextlib.nested поддерживает это:

import contextlib

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

   ...

Обновление:
Цитировать документацию, касающуюся contextlib.nested:

Устаревший с версии 2.7 : оператор with теперь поддерживает эту функцию напрямую (без запутанных ошибок, склонных к ошибкам).

См . Ответ Рафала Доугирда для получения дополнительной информации.

Алекс Мартелли
источник
34
Мне жаль это говорить, но я считаю, что nestedменеджер контекста является ошибкой и никогда не должен использоваться. В этом примере, если при открытии второго файла возникает исключение, первый файл вообще не будет закрыт, что полностью разрушит цель использования контекстных менеджеров.
Рафал Доугирд
Почему ты это сказал? В документации сказано, что использование nested эквивалентно вложению 'with's
Джеймс Хопкин
@Rafal: взгляд на руководство показывает, что python правильно вкладывает операторы with. Реальная проблема в том, что второй файл выдает исключение при закрытии.
неизвестно
10
@James: Нет, эквивалентный код в документации по адресу docs.python.org/library/contextlib.html#contextlib.nested отличается от стандартных вложенных withблоков. Менеджеры создаются по порядку перед входом с блоками: m1, m2, m3 = A (), B (), C (). Если B () или C () завершаются неудачно с исключением, тогда ваша единственная надежда на правильное завершение A ( ) является сборщиком мусора.
Рафал Доугирд
8
Устаревшая с версии 2.7 . Примечание. Оператор with теперь поддерживает эту функцию напрямую (без запутанных ошибок).
мику
36

Обратите внимание, что если вы разбиваете переменные на строки, вы должны использовать обратную косую черту для переноса строк.

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: неверный синтаксис

https://bugs.python.org/issue12782, похоже, связано с этой проблемой.

nyanpasu64
источник
16

Я думаю, что вы хотите сделать это вместо:

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
Вот как я сейчас это делаю, но тогда вложение вдвое глубже, чем я хочу (имею в виду), чтобы оно было ...
pufferfish
Я думаю, что это самый чистый подход - любой другой подход будет труднее читать. Ответ Алекса Мартелли, кажется, ближе к тому, что вы хотите, но гораздо менее читабелен. Почему гнездится такая забота?
Эндрю Хэйр
7
По общему признанию, это не страшно, но для «import this» (он же «Zen of Python») «flat лучше вложенного» - вот почему мы добавили contextlib.nested в стандартную библиотеку. Кстати, 3.1 мог бы иметь новый синтаксис «с A () как a, B () как b:» (патч уже включен, пока нет никакого объявления BDFL об этом) для более прямой поддержки (так что очевидно, что библиотечное решение не ' Это считается идеальным ... но избегание нежелательного вложения, безусловно, является широко распространенной целью среди основных разработчиков Python).
Алекс Мартелли
2
@ Алекс: Очень верно, но мы также должны учитывать, что «Читаемость имеет значение».
Эндрю Хэйр
4
@ Эндрю: Я думаю, что один уровень отступа лучше отражает предполагаемую логику программы, которая заключается в том, чтобы «атомарно» создать две переменные и очистить их позже вместе (я понимаю, что на самом деле это не то, что происходит). Полагаю, что проблема с исключениями заключается в нарушении условий соглашения
pufferfish
12

Начиная с 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]
timgeb
источник
0

В 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)
Евгений Ярмаш
источник