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
- Move all file operations inside the `with open(...) as f:` block so close happens only after the last use.
- 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.
- Guard calls with `if not f.closed:` before operating on a shared or long-lived file object.
- 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
- Use `with open(...) as f:` for every file whose lifetime fits one scope.
- Never return file objects from functions that opened them inside a with-block — return the data instead.
- Establish a single owner responsible for close(); document it at the API boundary.
- Check f.closed before operating on streams received from other modules.
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
- flush on closed file
- raw stream already detached
- __getstate__ on closed file
- getvalue on closed file
- getbuffer on closed file
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/f716e0da6f49856c.
Report an issue: GitHub.