python/cpython · error · ValueError

I/O operation on closed file.

Error message

I/O operation on closed file.

What it means

Raised by IOBase._checkClosed (Lib/_pyio.py:477) when any I/O operation is attempted on a file object whose close() has already run. The closed property is set to True by close(), and nearly every public method (read, write, seek, flush, __enter__) funnels through this guard. It is a ValueError, signaling a lifecycle bug in the caller's code rather than an OS-level failure.

Source

Thrown at Lib/_pyio.py:477

        """Internal: raise UnsupportedOperation if file is not writable
        """
        if not self.writable():
            raise UnsupportedOperation("File or stream is not writable."
                                       if msg is None else msg)

    @property
    def closed(self):
        """closed: bool.  True iff the file has been closed.

        For backwards compatibility, this is a property, not a predicate.
        """
        return self.__closed

    def _checkClosed(self, msg=None):
        """Internal: raise a ValueError if file is closed
        """
        if self.closed:
            raise ValueError("I/O operation on closed file."
                             if msg is None else msg)

    ### Context manager ###

    def __enter__(self):  # That's a forward reference
        """Context management protocol.  Returns self (an instance of IOBase)."""
        self._checkClosed()
        return self

    def __exit__(self, *args):
        """Context management protocol.  Calls close()"""
        self.close()

    ### Lower-level APIs ###

    # XXX Should these be present even if unimplemented?

    def fileno(self):

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Move all file operations inside the `with open(...) as f:` block so close happens only after the last use.
  2. If the file must outlive the current scope, open it without `with` and explicitly close it in a finally after the final consumer is done.
  3. Guard calls with `if not f.closed:` before operating on a shared or long-lived file object.
  4. Audit for double-ownership: find every close(), detach(), or `with` that touches the object and ensure exactly one owner closes it.

Example fix

// before
def load(path):
    with open(path, 'rb') as f:
        return f
f = load('data.bin')
f.read()  # ValueError: I/O operation on closed file.

# after
def load(path):
    with open(path, 'rb') as f:
        return f.read()
data = load('data.bin')
Defensive patterns

Strategy: validation

Validate before calling

def safe_read(f, n=-1):
    if f.closed:
        raise ValueError('file already closed; reopen before use')
    return f.read(n)

Type guard

def is_open_binary(f) -> bool:
    return isinstance(f, io.IOBase) and not f.closed

Try / catch

try:
    data = f.read()
except ValueError as e:
    if 'closed file' in str(e):
        f = open(path, 'rb')  # reopen and retry once
        data = f.read()
    else:
        raise

Prevention

When it happens

Trigger: Calling f.read()/f.write()/f.seek()/f.flush()/f.__enter__() after f.close(); using a file object after its `with` block has exited (the context manager's __exit__ calls close()); accessing a file that another function closed; double-processing a file in a loop where the first iteration closed it.

Common situations: Returning a file object from a `with open(...)` block and using it later; helper functions that take ownership of and close a stream the caller still uses; background threads or generators that close shared files; refactoring that moved a close() above later operations.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/f716e0da6f49856c. Report an issue: GitHub.