Кілька змінних у операторі "з"?


391

Чи можливо оголосити більше однієї змінної за допомогою a 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: "список" об'єкта не має атрибута " вихід "
pufferfish

Якщо python щойно закрив, вам не знадобиться заява
BT

Вам не потрібно використовувати оператор із заявою, правда? Ви можете просто встановити file_out та file_in на None, а потім спробувати / за винятком /, нарешті, там, де ви відкриєте їх та обробіть їх у спробі, а потім, нарешті, закрийте їх, якщо вони не None. Для цього не потрібні подвійні відступи.
М Кац

1
Багато з цих відповідей не стосуються потреби у заявах більше двох. Теоретично можуть бути програми, які потребують відкриття десятків контекстів, вкладення дуже швидко розпадається, якщо накладаються будь-які обмеження довжини рядків.
ThorSummoner

Відповіді:


666

Це можливо в Python 3 з v3.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)

1
чи можна встановити поля, рівні рівним чомусь із заявою як у with open('./file') as arg.x = file:?
Чарлі Паркер

13
Також можливо: з A () як a, B (a) як b, C (a, b) як c:
Ahmad Yoosofan

тест класу2: x = 1; t2 = test2 () з відкритим ('f2.txt') як t2.x: для l1 в t2.x.readlines (): print (l1); # Чарлі Паркер # випробуваний на
пітоні

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 : тепер-з оператором підтримує цю функціональність безпосередньо (без заплутаних химерностей).

Див. Відповідь Рафала Доугірда для отримання додаткової інформації.


34
Мені дуже шкода, але я вважаю, що nestedменеджер контексту - це помилка, і його ніколи не слід використовувати. У цьому прикладі, якщо відкриття другого файлу викликає виняток, перший файл взагалі не закриється, таким чином, повністю знищуючи мету використання контекстних менеджерів.
Rafał Dowgird

Чому ти це кажеш? У документації йдеться про те, що використання вкладених даних еквівалентно вкладеним 'з'
Джеймс Хопкін

@Rafal: погляд на посібник, схоже, вказує на те, що python належним чином укладає твердження. Справжня проблема полягає в тому, якщо другий файл видає виняток після закриття.
Невідомо

10
@James: Ні, еквівалентний код у документах на docs.python.org/library/contextlib.html#contextlib.nested відрізняється від стандартних вкладених withблоків. Менеджери створюються в порядку перед введенням з блоками: m1, m2, m3 = A (), B (), C () Якщо B () або C () не спрацьовує за винятком, то ваша єдина надія на правильне завершення A ( ) є сміттєзбірником.
Rafał Dowgird

8
Застаріло з версії 2.7 . Примітка: тепер-з оператором підтримує цю функцію безпосередньо (без заплутаних химерностей, схильних до помилок).
miku

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
Правда, це не велика справа, але, "імпортувати це" (він же "дзен Python"), "квартира краще, ніж вкладена" - саме тому ми додали contextlib.nested до стандартної бібліотеки. У BTW, 3.1 може бути новий синтаксис "з A () як a, B () як b:" (патч в ньому, поки що про це не йдеться BDFL) для більш прямої підтримки (так очевидно, що рішення бібліотеки не є " t вважається ідеальним ... але уникати небажаних вкладень, безумовно, широко поширена мета серед основних розробників Python).
Алекс Мартеллі

2
@Alex: Дуже вірно, але ми також повинні врахувати, що "читабельність має значення".
Ендрю Заєць

4
@Andrew: Я думаю, що один рівень відступів краще виражає передбачувану логіку програми, яка полягає в тому, щоб "атомно" створити дві змінні та очистити їх згодом разом (я розумію, що це насправді не відбувається). Подумайте питання винятком є переривник угоди , хоча
рибу фугу

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

Це також означає, що ви можете використовувати псевдоніми з першого виразу в другому (корисно при роботі з db-з'єднаннями / курсорами):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.