Wiele zmiennych w instrukcji „z”?

391

Czy można zadeklarować więcej niż jedną zmienną przy użyciu withinstrukcji w Pythonie?

Coś jak:

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)

... czy problem polega na oczyszczeniu dwóch zasobów jednocześnie?

rozdymka tygrysia
źródło
Może tak: z [wyrażenie1, wyrażenie2] jako f: a następnie użyj f [0] if [1].
jbasko
Byłoby miło, ponieważ nie trzeba niczego importować .... ale to nie działa AttributeError: obiekt „list” nie ma atrybutu „ exit
pufferfish
Jeśli python miałby tylko zamknięcia, nie potrzebowałbyś instrukcji with
BT
Nie musisz używać instrukcji with, prawda? Możesz po prostu ustawić file_out i file_in na None, a następnie wykonać próbę / oprócz / wreszcie tam, gdzie je otworzysz i przetworzyć w próbie, a następnie w końcu zamknąć je, jeśli nie są to None. Nie potrzeba do tego podwójnego wcięcia.
M Katz
1
Wiele z tych odpowiedzi nie dotyczy potrzeby więcej niż dwóch z oświadczeniami. Teoretycznie mogą istnieć aplikacje, które muszą otwierać dziesiątki kontekstów, zagnieżdżanie rozpada się bardzo szybko, narzucone są ograniczenia długości linii.
ThorSummoner,

Odpowiedzi:

666

Jest to możliwe w Pythonie 3 od wersji 3.1 i Pythona 2.7 . Nowa withskładnia obsługuje wiele menedżerów kontekstu:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

W odróżnieniu od contextlib.nestedgwarantuje to, że ai bbędzie miał swój __exit__()„nazywa nawet jeśli C()czy jest to __enter__()metoda zgłasza wyjątek.

Możesz także użyć wcześniejszych zmiennych w późniejszych definicjach (h / t Ahmad poniżej):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)
Rafał Dowgird
źródło
1
czy jest możliwe ustawienie pól równych czemuś z instrukcją jak w with open('./file') as arg.x = file:?
Charlie Parker
13
Możliwe jest również: z A () jako a, B (a) jako b, C (a, b) jako c:
Ahmad Yoosofan
test klasy2: x = 1; t2 = test2 () z otwartym („f2.txt”) jako t2.x: dla l1 w t2.x.readlines (): print (l1); # Charlie Parker # przetestowany w pythonie 3.6
Ahmad Yoosofan,
1
Uwaga, asjest opcjonalny.
Sławomir Lenart,
wyjaśnić, co mówi @ SławomirLenart: asjest wymagane, jeśli potrzebujesz przedmiotu alub b, ale całość as alub as bnie jest wymagane
Ciprian Tomoiagă
56

contextlib.nested popiera to:

import contextlib

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

   ...

Aktualizacja:
Aby zacytować dokumentację dotyczącą contextlib.nested:

Przestarzałe od wersji 2.7 : Instrukcja with obsługuje teraz tę funkcję bezpośrednio (bez mylących skłonności do błędów).

Więcej informacji można znaleźć w odpowiedzi Rafała Dowgirda .

Alex Martelli
źródło
34
Przykro mi to mówić, ale myślę, że nestedmenedżer kontekstu jest błędem i nigdy nie powinien być używany. W tym przykładzie, jeśli otwarcie drugiego pliku spowoduje wyjątek, pierwszy plik w ogóle nie zostanie zamknięty, co całkowicie zniszczy cel używania menedżerów kontekstu.
Rafał Dowgird
Dlaczego to mówisz? Dokumentacja mówi, że użycie zagnieżdżonego jest równoważne zagnieżdżeniu „z”
James Hopkin
@Rafal: rzut oka na instrukcję wskazuje, że Python poprawnie zagnieżdża instrukcje with. Prawdziwy problem polega na tym, że drugi plik zgłasza wyjątek po zamknięciu.
Nieznany
10
@James: Nie, równoważny kod w dokumentach na docs.python.org/library/contextlib.html#contextlib.nested różni się od standardowych zagnieżdżonych withbloków. Menedżery są tworzone w kolejności przed wejściem do bloków: m1, m2, m3 = A (), B (), C () Jeśli B () lub C () zawiedzie z wyjątkiem, to jedyną nadzieją na prawidłowe sfinalizowanie A ( ) jest śmieciarzem.
Rafał Dowgird
8
Przestarzałe od wersji 2.7 . Uwaga: Instrukcja with obsługuje teraz tę funkcję bezpośrednio (bez mylących skłonności do błędów).
miku
36

Zauważ, że jeśli podzielisz zmienne na linie, musisz użyć ukośników odwrotnych do zawijania nowych linii.

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

Nawiasy nie działają, ponieważ zamiast tego Python tworzy krotkę.

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

Ponieważ w krotkach brakuje __enter__atrybutu, pojawia się błąd (nieopisowy i nie identyfikuje typu klasy):

AttributeError: __enter__

Jeśli spróbujesz użyć as w nawiasach, Python łapie błąd podczas analizy:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)

Błąd składni: nieprawidłowa składnia

Wydaje się, że https://bugs.python.org/issue12782 ma związek z tym problemem.

nyanpasu64
źródło
16

Myślę, że zamiast tego chcesz to zrobić:

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)
Andrew Hare
źródło
5
Tak właśnie to obecnie robię, ale wtedy zagnieżdżanie jest dwa razy głębsze, niż chcę (to znaczy), aby było ...
pufferfish
Myślę jednak, że jest to najczystsze podejście - każde inne podejście będzie trudniejsze do odczytania. Odpowiedź Alexa Martellego wydaje się być bliższa temu, czego chcesz, ale jest znacznie mniej czytelna. Dlaczego zagnieżdżanie się jest takie niepokojące?
Andrew Hare
7
Co prawda nie jest to wielka sprawa, ale za „zaimportuj to” (alias „Zen of Python”) „mieszkanie jest lepsze niż zagnieżdżone” - dlatego do standardowej biblioteki dodaliśmy kontekstlib.nested. BTW, 3.1 może mieć nową składnię „z A () jako a, B () jako b:” (łatka jest w, jak dotąd brak wypowiedzenia BDFL) w celu uzyskania bardziej bezpośredniego wsparcia (tak wyraźnie rozwiązanie biblioteki nie jest Uważany za idealny ... ale unikanie niechcianego zagnieżdżania jest zdecydowanie powszechnym celem wśród głównych programistów Pythona).
Alex Martelli
2
@Alex: Bardzo prawda, ale musimy również wziąć pod uwagę, że „Czytelność się liczy”.
Andrew Hare
4
@Andrew: Myślę, że jeden poziom wcięcia lepiej wyraża zamierzoną logikę programu, polegającą na „atomowym” utworzeniu dwóch zmiennych i wyczyszczeniu ich później razem (zdaję sobie sprawę, że tak się nie dzieje). Pomyśl, że problem wyjątku jest przełomowy
pufferfish
12

Od wersji Python 3.3 możesz używać klasy ExitStackz contextlibmodułu.

Może zarządzać dynamiczną liczbą obiektów kontekstowych, co oznacza, że ​​okaże się szczególnie przydatny, jeśli nie wiesz, ile plików zamierzasz obsłużyć.

Kanoniczny przypadek użycia wymieniony w dokumentacji zarządza dynamiczną liczbą plików.

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

Oto ogólny przykład:

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)

Wynik:

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]
timb
źródło
0

W Pythonie 3.1+ można określić wiele wyrażeń kontekstowych, które będą przetwarzane tak, jakby withzagnieżdżono wiele instrukcji:

with A() as a, B() as b:
    suite

jest równa

with A() as a:
    with B() as b:
        suite

Oznacza to również, że możesz użyć aliasu z pierwszego wyrażenia w drugim (przydatne podczas pracy z połączeniami / kursorami db):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Eugene Yarmash
źródło