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


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, похоже, связано с этой проблемой.


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]

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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.