HOWTO · Python

How to Solve the ValueError: I/O Operation on Closed File in Python

This article provides three major examples of how the I/O Valueerror is caused and what are the various ways through which this error can be solved.

On this page

To fix ValueError: I/O operation on closed file, keep each read, iteration, or write inside the file object’s open lifetime. Put the full operation inside a with block; Python closes the file when that block ends.

Run this example:

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.

Understand ValueError: I/O operation on closed file

ValueError: I/O operation on closed file means code tried to read from or write to a file object after that object was closed. A file can close because execution left its with open(...) block, because code called close(), or because another part of the program closed the same object. The traceback identifies the operation that failed; trace that file object back to its open and close points.

A with statement manages a resource’s lifetime. It is not an exception-handling block: when execution leaves the indented suite, Python closes the file automatically. Indentation therefore determines how long the file remains usable. The same rule applies if a file-backed iterator reads data lazily: creating the iterator does not necessarily read all the file contents.

Fix a file iterator used after its context manager closes

csv.reader consumes rows as it is iterated. If the reader is created inside a file context but the loop is outside it, the context ends first and the reader no longer has an open file to read. This faulty structure is with open(path) as source: rows = csv.reader(source) followed by for row in rows: at the outer indentation.

Keep the loop inside the with suite so every read occurs while the source is open. This complete example creates its own CSV input, reads each row, and leaves the file context only after iteration finishes.

Run this example:

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)

The key repair is the loop’s indentation, not an extra copy of the rows into memory. If later processing must happen after the file closes, materialize the rows deliberately inside the block, such as with list(csv.reader(source)); for large files, processing them while the context remains open avoids retaining every row at once.

Fix a file closed before a loop finishes

A file may also be opened normally and closed too early. For example, for line in source: destination.write(line); destination.close() closes the destination during the first iteration. If another line is processed, the next write() uses a closed file and fails. The close() call affects the object it is called on: destination.close() does not close source, and a second close on destination does not close source either.

The simple fix is to let a context manager close both files after the loop. This example copies a small, locally created input file into another temporary file and prints the copied lines while both contexts are still active.

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())

There is no need to call close() manually in this pattern. If a loop has a condition for closing a file, check whether that condition belongs after the loop or whether each iteration should use a separate file. Keep cleanup at the same structural level as the work it cleans up.

Fix writes attempted after a file is closed

An explicit close() ends the file’s usable lifetime immediately, even if the code is still indented inside a with suite. In a sequence such as handle.write("first"), handle.close(), then handle.write("second"), the final write is the one that fails. A context manager will close the file when its suite exits, so manual closure inside that suite is redundant and can make later operations fail.

Remove the manual close() and put all intended writes inside the context. For code that does not use a context manager, pair each successful open() with one close() after the last operation, preferably in a finally block so cleanup still occurs when another operation fails. For ordinary file handling, with is shorter and ensures cleanup on both normal and exceptional exit.

The point immediately after close() is a useful boundary to check: even before the with block ends, the file is no longer usable. This self-contained example captures the resulting ValueError so the program can display its message and continue.

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.

Prevent closed-file errors by managing file lifetimes

Use the traceback to locate the failed read(), iteration, write(), or related operation. Then identify the exact file object involved and find where it was opened. Check every path that can close that object, including explicit close() calls, leaving a with block, and helper functions that receive the same object. Finally, compare indentation and loop placement: the failed operation must run before the owning context ends and before any explicit close.

A quick diagnostic checklist is:

  • Which file operation appears on the traceback’s final application line?
  • Which variable is the file object for that operation, and where is it opened?
  • Does execution reach close() or leave the relevant with block before the operation?
  • Is a loop, callback, or lazy iterator being consumed outside the file’s context?
  • Can a context manager own the file around the full set of reads or writes?

These checks distinguish the common cases: consuming a lazy CSV reader after its source closes, closing an output file before a loop finishes, and writing again after an explicit close. Place the work within the file’s lifetime, remove premature closure, and allow the context manager to perform final cleanup.