621 Stimmen

Mehrere Variablen in einer 'with'-Anweisung?

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

Etwa so:

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 liegt das Problem darin, zwei Ressourcen gleichzeitig zu bereinigen?

992voto

Rafał Dowgird Punkte 40450

Es ist möglich in Python 3 seit Version 3.1 y Python 2.7 . Die neue with Syntax unterstützt mehrere Kontextmanager:

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

Anders als die contextlib.nested Dies garantiert, dass a y b haben ihre __exit__() aufgerufen wird, auch wenn C() oder es ist __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)

Ab Python 3.10, können Sie Klammern verwenden :

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

93voto

nyanpasu64 Punkte 2407

Wenn Sie die Variablen in Zeilen aufteilen, müssen Sie vor Python 3.10 Backslashes verwenden, um die Zeilenumbrüche zu umschließen.

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

Klammern funktionieren nicht, da Python stattdessen ein Tupel erzeugt.

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

Da Tupel keine __enter__ erhalten Sie eine Fehlermeldung (undeskriptiv und identifiziert nicht den Klassentyp):

AttributeError: __enter__

Wenn Sie versuchen, die as innerhalb von Klammern, fängt Python den Fehler beim Parsen ab:

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

Wann wird das Problem behoben sein?

Dieses Thema wird verfolgt in https://bugs.python.org/issue12782 .

Python angekündigt in PEP 617 dass sie den ursprünglichen Parser durch einen neuen ersetzen würden. Da der ursprüngliche Parser von Python LL(1) ist, ist er kann nicht unterscheiden zwischen "mehreren Kontextmanagern" with (A(), B()): und "Tupel von Werten" with (A(), B())[0]: .

Der neue Parser kann mehrere von Klammern umgebene Kontextmanager korrekt analysieren. Der neue Parser ist in 3.9 aktiviert worden. Es wurde berichtet, dass diese Syntax immer noch zurückgewiesen wird, bis der alte Parser in Python 3.10 entfernt wird, und diese Syntaxänderung wurde in der 3.10 Versionshinweise . Aber in meinen Tests, es funktioniert in trinket.io's Python 3.9.6 als gut.

66voto

Alex Martelli Punkte 805329

contextlib.nested unterstützt dies:

import contextlib

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

   ...

Aktualisierung:
In der Dokumentation heißt es dazu contextlib.nested :

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

Ver Die Antwort von Rafa Dowgird für weitere Informationen.

31voto

timgeb Punkte 72448

Seit Python 3.3 können Sie die Klasse ExitStack von der contextlib Modul.

Es kann eine dynamisch Anzahl der kontextabhängigen Objekte, was bedeutet, dass es sich als besonders nützlich erweist, wenn Sie nicht wissen, wie viele Dateien Sie bearbeiten werden.

Der kanonische Anwendungsfall, der in der Dokumentation erwähnt wird, 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]

20voto

Andrew Hare Punkte 332190

Ich denke, Sie sollten stattdessen dies 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)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X