Mehrere Variablen in einer 'with'-Anweisung?


391

Ist es möglich, mehr als eine Variable mit a zu deklarieren? with Anweisung in Python ?

Etwas wie:

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)

... oder ist es das Problem, zwei Ressourcen gleichzeitig zu bereinigen?


Vielleicht so: mit [expr1, expr2] als f: und dann f [0] und f [1].
Jbasko

Wäre schön gewesen, weil man nichts importieren muss ... aber es funktioniert nicht AttributeError: 'list' Objekt hat kein Attribut ' exit '
pufferfish

Wenn Python nur Schließungen hätte, würden Sie die with-Anweisung nicht benötigen
BT

Sie müssen nicht brauchen ein mit Aussage, Recht zu benutzen? Sie können file_out und file_in einfach auf None setzen, dann einen Versuch / außer / finally ausführen, wo Sie sie öffnen und beim Versuch verarbeiten, und dann beim endgültigen Schließen, wenn sie nicht None sind. Dafür ist keine doppelte Einrückung erforderlich.
M Katz

1
Viele dieser Antworten befassen sich nicht mit der Notwendigkeit von mehr als zwei mit Aussagen. Theoretisch kann es Anwendungen geben, die Dutzende von Kontexten öffnen müssen. Die Verschachtelung fällt sehr schnell auseinander, wenn Einschränkungen hinsichtlich der Zeilenlänge auferlegt werden.
ThorSummoner

Antworten:


667

Es ist in Python 3 seit v3.1 und Python 2.7 möglich . Die neue withSyntax unterstützt mehrere Kontextmanager:

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

Im Gegensatz zum contextlib.nestedgarantiert dies, dass aund bwird ihre __exit__()aufgerufen, auch wenn C()oder seine __enter__()Methode eine Ausnahme auslöst.

Sie können auch frühere Variablen in späteren Definitionen verwenden (h / t Ahmad unten):

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

1
Ist es möglich, Felder gleich etwas in mit Anweisung wie in zu setzen with open('./file') as arg.x = file:?
Charlie Parker

13
Es ist auch möglich: mit A () als a, B (a) als b, C (a, b) als c:
Ahmad Yoosofan

Klassentest2: x = 1; t2 = test2 () mit open ('f2.txt') als t2.x: für l1 in t2.x.readlines (): print (l1); # Charlie Parker # getestet in Python 3.6
Ahmad Yoosofan

1
Bitte beachten Sie, asist optional.
Sławomir Lenart

um zu klären, was @ SławomirLenart sagt: asist erforderlich, wenn Sie das Objekt benötigen aoder b, aber das Ganze as aoder as bnicht erforderlich
Ciprian Tomoiagă

56

contextlib.nested unterstützt dies:

import contextlib

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

   ...

Update:
Um die Dokumentation zu zitieren, betreffend contextlib.nested:

Veraltet seit Version 2.7 : Die with-Anweisung unterstützt diese Funktionalität jetzt direkt (ohne die verwirrenden fehleranfälligen Macken).

Siehe Rafał Dowgird Antwort für weitere Informationen.


34
Es tut mir leid, das zu sagen, aber ich denke, dass der nestedKontextmanager ein Fehler ist und niemals verwendet werden sollte. Wenn in diesem Beispiel das Öffnen der zweiten Datei eine Ausnahme auslöst, wird die erste Datei überhaupt nicht geschlossen, wodurch der Zweck der Verwendung von Kontextmanagern vollständig zerstört wird.
Rafał Dowgird

Warum sagst du das? Die Dokumentation besagt, dass die Verwendung von verschachtelten gleichbedeutend mit verschachtelten 'mit' ist
James Hopkin

@ Rafal: Ein Blick in das Handbuch scheint darauf hinzudeuten, dass Python die with-Anweisungen richtig verschachtelt. Das eigentliche Problem ist, wenn die zweite Datei beim Schließen eine Ausnahme auslöst.
Unbekannt

10
@James: Nein, der entsprechende Code in den Dokumenten unter docs.python.org/library/contextlib.html#contextlib.nested unterscheidet sich von den verschachtelten Standardblöcken with. Die Manager werden in der Reihenfolge vor dem Eingeben der mit Blöcken erstellt: m1, m2, m3 = A (), B (), C () Wenn B () oder C () mit Ausnahme fehlschlägt, besteht Ihre einzige Hoffnung darauf, A ( ) ist der Müllsammler.
Rafał Dowgird

8
Veraltet seit Version 2.7 . Hinweis: Die with-Anweisung unterstützt diese Funktionalität jetzt direkt (ohne die verwirrenden fehleranfälligen Macken).
Miku

36

Beachten Sie, dass Sie beim Aufteilen der Variablen in Zeilen Backslashes verwenden müssen, um die Zeilenumbrüche zu umbrechen.

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

Klammern funktionieren nicht, da Python stattdessen ein Tupel erstellt.

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

Da Tupeln ein __enter__Attribut fehlt , erhalten Sie einen Fehler (nicht beschreibend und identifiziert den Klassentyp nicht):

AttributeError: __enter__

Wenn Sie versuchen, asin Klammern zu verwenden, fängt Python den Fehler beim Analysieren ab:

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

SyntaxError: Ungültige Syntax

https://bugs.python.org/issue12782 scheint mit diesem Problem in Zusammenhang zu stehen.


16

Ich denke, Sie möchten dies stattdessen tun:

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
So mache ich es derzeit, aber dann ist die Verschachtelung doppelt so tief, wie ich es will (meine) ...
Kugelfisch

Ich denke, dies ist der sauberste Ansatz - jeder andere Ansatz wird schwerer zu lesen sein. Die Antwort von Alex Martelli scheint näher an dem zu sein, was Sie wollen, ist aber viel weniger lesbar. Warum ist das Verschachteln so ein Problem?
Andrew Hare

7
Zugegeben, keine große Sache, aber laut "import this" (auch bekannt als "Zen of Python") ist "flat ist besser als verschachtelt" - deshalb haben wir contextlib.nested zur Standardbibliothek hinzugefügt. Übrigens könnte 3.1 eine neue Syntax haben "mit A () als a, B () als b:" (der Patch ist in, noch keine BDFL-Erklärung darüber) für direktere Unterstützung (so klar ist die Bibliothekslösung nicht '). t wird als perfekt angesehen ... aber das Vermeiden unerwünschter Verschachtelungen ist definitiv ein weit verbreitetes Ziel unter den Kernentwicklern von Python.
Alex Martelli

2
@ Alex: Sehr wahr, aber wir müssen auch berücksichtigen, dass "Lesbarkeit zählt".
Andrew Hare

4
@ Andrew: Ich denke, eine Einrückungsstufe drückt die beabsichtigte Logik des Programms besser aus, nämlich "atomar" zwei Variablen zu erstellen und sie später gemeinsam zu bereinigen (mir ist klar, dass dies nicht der Fall ist). Denken Sie, dass das Ausnahmeproblem ein Deal Breaker ist
Kugelfisch

12

Seit Python 3.3 können Sie die Klasse ExitStackaus dem contextlibModul verwenden.

Es kann eine dynamische Anzahl kontextsensitiver Objekte verwalten, was bedeutet, dass es sich als besonders nützlich erweist, wenn Sie nicht wissen, wie viele Dateien Sie verarbeiten werden.

Der in der Dokumentation erwähnte kanonische Anwendungsfall ist die Verwaltung einer dynamischen Anzahl von Dateien.

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

Hier ist ein allgemeines Beispiel:

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)

Ausgabe:

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

In Python 3.1+ können Sie mehrere Kontextausdrücke angeben, die so verarbeitet werden, als wären mehrere withAnweisungen verschachtelt:

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

ist äquivalent zu

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

Dies bedeutet auch, dass Sie den Alias ​​aus dem ersten Ausdruck im zweiten verwenden können (nützlich, wenn Sie mit Datenbankverbindungen / Cursorn arbeiten):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.