HOWTO · Python

Lösen Sie den ValueError: E/A-Vorgang für geschlossene Datei in Python

Dieser Artikel enthält drei Hauptbeispiele dafür, wie der E/A-Wertfehler verursacht wird, und welche verschiedenen Möglichkeiten es gibt, diesen Fehler zu beheben.

Auf dieser Seite

Um ValueError: I/O operation on closed file zu beheben, müssen alle Lese-, Iterations- und Schreibvorgänge innerhalb der Öffnungsdauer des Dateiobjekts stattfinden. Führe den gesamten Vorgang in einem with-Block aus; Python schließt die Datei, sobald dieser Block endet.

Führe dieses Beispiel aus:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    with path.open("w", encoding="utf-8") as handle:
        handle.write("File is ready.\n")
    with path.open(encoding="utf-8") as handle:
        print(handle.read(), end="")
File is ready.

ValueError: I/O operation on closed file verstehen

ValueError: I/O operation on closed file bedeutet, dass der Code versucht hat, ein Dateiobjekt zu lesen oder zu beschreiben, nachdem es geschlossen wurde. Eine Datei kann geschlossen werden, wenn die Ausführung den with open(...)-Block verlässt, der Code close() aufruft oder ein anderer Teil des Programms dasselbe Objekt schließt. Der Traceback zeigt den fehlgeschlagenen Vorgang. Verfolge das Dateiobjekt bis zu den Stellen zurück, an denen es geöffnet und geschlossen wird.

Eine with-Anweisung verwaltet die Lebensdauer einer Ressource. Sie ist kein Block zur Ausnahmebehandlung: Sobald die Ausführung den eingerückten Block verlässt, schließt Python die Datei automatisch. Die Einrückung bestimmt daher, wie lange die Datei verwendet werden kann. Dasselbe gilt für dateibasierte Iteratoren, die Daten verzögert lesen: Beim Erstellen des Iterators werden möglicherweise noch nicht alle Dateiinhalte eingelesen.

Einen Datei-Iterator reparieren, der nach dem Schließen seines Kontextmanagers verwendet wird

csv.reader liest die Zeilen beim Durchlaufen des Iterators ein. Wird der Reader innerhalb eines Dateikontexts erstellt, die Schleife aber außerhalb ausgeführt, endet zuerst der Kontext. Der Reader hat dann keine geöffnete Datei mehr, aus der er lesen kann. Eine solche fehlerhafte Struktur ist with open(path) as source: rows = csv.reader(source), gefolgt von for row in rows: auf der äußeren Einrückungsebene.

Verschiebe die Schleife in den with-Block, damit jeder Lesevorgang stattfindet, während die Quelldatei geöffnet ist. Dieses vollständige Beispiel erstellt eine eigene CSV-Eingabedatei, liest jede Zeile ein und verlässt den Dateikontext erst, wenn die Iteration abgeschlossen ist.

Führe dieses Beispiel aus:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import csv
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / "people.csv"
    path.write_text("name,role\nAda,engineer\n", encoding="utf-8")
    with path.open(newline="", encoding="utf-8") as source:
        for row in csv.reader(source):
            print(row)

Die entscheidende Korrektur ist die Einrückung der Schleife, nicht eine zusätzliche Kopie aller Zeilen im Speicher. Muss die Verarbeitung erst nach dem Schließen der Datei erfolgen, kannst du die Zeilen gezielt innerhalb des Blocks materialisieren, etwa mit list(csv.reader(source)). Bei großen Dateien vermeidest du es, alle Zeilen gleichzeitig zu speichern, wenn du sie verarbeitest, solange der Kontext noch geöffnet ist.

Eine Datei reparieren, die vor dem Ende einer Schleife geschlossen wird

Eine Datei kann auch regulär geöffnet und dann zu früh geschlossen werden. Beispielsweise schließt for line in source: destination.write(line); destination.close() die Zieldatei bereits während des ersten Schleifendurchlaufs. Wird danach eine weitere Zeile verarbeitet, schlägt der nächste write()-Aufruf fehl, weil die Datei geschlossen ist. Der Aufruf von close() wirkt sich auf das Objekt aus, für das er aufgerufen wird: destination.close() schließt source nicht. Auch ein zweiter Aufruf von destination.close() schließt source nicht.

Die einfache Lösung ist, beide Dateien von einem Kontextmanager nach dem Ende der Schleife schließen zu lassen. Dieses Beispiel kopiert eine kleine, lokal erstellte Eingabedatei in eine weitere temporäre Datei und gibt die kopierten Zeilen aus, solange beide Kontexte noch geöffnet sind.

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    source_path = Path(directory) / "source.txt"
    destination_path = Path(directory) / "copy.txt"
    source_path.write_text("first\nsecond\n", encoding="utf-8")

    with source_path.open(encoding="utf-8") as source:
        with destination_path.open("w", encoding="utf-8") as destination:
            for line in source:
                destination.write(line)
                print(line.rstrip())

In diesem Muster ist kein manueller Aufruf von close() nötig. Wenn eine Schleife eine Bedingung zum Schließen einer Datei enthält, prüfe, ob diese Bedingung erst nach der Schleife gelten sollte oder ob bei jedem Durchlauf eine separate Datei verwendet werden soll. Die Bereinigung sollte auf derselben Strukturebene stehen wie die Arbeit, für die sie zuständig ist.

Schreibvorgänge nach dem Schließen einer Datei reparieren

Ein ausdrücklicher Aufruf von close() beendet die Nutzungsdauer der Datei sofort, auch wenn der Code noch in einem with-Block eingerückt ist. In einer Folge wie handle.write("first"), handle.close() und danach handle.write("second") ist der letzte Schreibvorgang derjenige, der fehlschlägt. Ein Kontextmanager schließt die Datei beim Verlassen seines Blocks. Ein manueller Aufruf von close() innerhalb dieses Blocks ist daher überflüssig und kann dazu führen, dass spätere Vorgänge fehlschlagen.

Entferne den manuellen Aufruf von close() und führe alle vorgesehenen Schreibvorgänge innerhalb des Kontexts aus. Wird kein Kontextmanager verwendet, sollte zu jedem erfolgreichen open() genau ein close() nach dem letzten Vorgang gehören, vorzugsweise in einem finally-Block, damit die Bereinigung auch bei einem Fehler während eines anderen Vorgangs erfolgt. Für die übliche Dateiverarbeitung ist with kürzer und sorgt sowohl bei normalem Ablauf als auch beim Auftreten einer Ausnahme für die Bereinigung.

Der Zeitpunkt unmittelbar nach close() ist eine hilfreiche Grenze, die du prüfen solltest: Die Datei kann dann bereits nicht mehr verwendet werden, obwohl der with-Block noch läuft. Dieses eigenständige Beispiel fängt den daraus entstehenden ValueError ab, damit das Programm die Meldung ausgeben und anschließend fortfahren kann.

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    with path.open("w+", encoding="utf-8") as handle:
        handle.write("ready")
        handle.close()
        try:
            handle.read()
        except ValueError as error:
            print(type(error).__name__ + ": " + str(error))
ValueError: I/O operation on closed file.

Fehler mit geschlossenen Dateien durch korrekt verwaltete Dateilebensdauern vermeiden

Verwende den Traceback, um den fehlgeschlagenen Aufruf von read(), die Iteration, write() oder einen verwandten Vorgang zu finden. Ermittle dann das betroffene Dateiobjekt und die Stelle, an der es geöffnet wird. Prüfe alle Wege, auf denen dieses Objekt geschlossen werden kann: ausdrückliche Aufrufe von close(), das Verlassen eines with-Blocks und Hilfsfunktionen, denen dasselbe Objekt übergeben wird. Vergleiche schließlich Einrückung und Position der Schleife: Der fehlgeschlagene Vorgang muss stattfinden, bevor der zuständige Kontext endet und bevor die Datei ausdrücklich geschlossen wird.

Eine kurze Checkliste zur Diagnose:

  • Welcher Dateivorgang steht in der letzten Zeile des Tracebacks, die zu deiner Anwendung gehört?
  • Welche Variable enthält das Dateiobjekt für diesen Vorgang, und wo wird es geöffnet?
  • Wird close() aufgerufen oder der betreffende with-Block verlassen, bevor der Vorgang stattfindet?
  • Wird eine Schleife, ein Callback oder ein verzögert ausgelesener Iterator außerhalb des Dateikontexts durchlaufen?
  • Kann ein Kontextmanager die Datei für die gesamte Folge der Lese- oder Schreibvorgänge verwalten?

Mit diesen Fragen lassen sich die häufigen Fälle unterscheiden: Ein verzögert ausgelesener CSV-Reader wird erst verwendet, nachdem seine Quelldatei geschlossen wurde; eine Ausgabedatei wird vor dem Ende einer Schleife geschlossen; oder nach einem ausdrücklichen Schließen wird erneut geschrieben. Führe die Arbeit während der Lebensdauer der Datei aus, vermeide ein vorzeitiges Schließen und überlasse dem Kontextmanager die abschließende Bereinigung.