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?


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)

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 .


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.


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)

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]

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.