ValueError: I/O operation on closed file (tested fix)
ValueError: I/O operation on closed file. means you read, wrote or iterated a file after it was closed, usually because the code runs after its with block ended. We reproduced 4 causes in Python 3.13 and tested each fix.
1. Using the file after the with block
with open(...) closes the file when the indented block ends. A line that is dedented by mistake runs after the close:
with open("/tmp/cl/a.txt", "w") as f:
f.write("one\n")
try:
f.write("two\n") # indentation: this line is outside the with block
except ValueError as e:
print("ValueError:", e)
print(f.closed)
Output:
ValueError: I/O operation on closed file.
True
Fix the indentation so every read and write is inside the block.
2. Returning the file object from a function
def lines(path):
with open(path) as f:
return f # the file closes as the function returns
f = lines("/tmp/cl/a.txt")
try:
f.read()
except ValueError as e:
print("ValueError:", e)
def lines(path):
with open(path) as f:
return f.readlines() # read inside the block
print(lines("/tmp/cl/a.txt"))
Output:
ValueError: I/O operation on closed file.
['one\n']
Returning from inside with still closes the file. Return the data, or make the function a generator that yields lines while the file is open.
3. csv.reader and csv.writer outliving the file
import csv
with open("/tmp/cl/a.csv", "w", newline="") as f:
w = csv.writer(f)
try:
w.writerow(["x", 1]) # writer outlives the file
except ValueError as e:
print("ValueError:", e)
with open("/tmp/cl/a.csv") as f:
rows = csv.reader(f)
try:
list(rows) # reader is lazy: it reads only when you iterate
except ValueError as e:
print("ValueError:", e)
with open("/tmp/cl/a.csv") as f:
rows = list(csv.reader(f)) # materialise inside the block
print(rows)
Output:
ValueError: I/O operation on closed file.
ValueError: I/O operation on closed file.
[]
Both hold a reference to the file but don't keep it open. csv.reader is lazy, so creating it inside the block and iterating outside fails. Call list() inside the block. (The final list is empty only because the failed writerow never wrote anything.)
4. StringIO, iteration and double close
import io, sys
buf = io.StringIO()
buf.close()
try:
buf.getvalue()
except ValueError as e:
print("StringIO:", e)
f = open("/tmp/cl/a.txt")
f.close()
f.close() # closing twice is fine
try:
for line in f:
pass
except ValueError as e:
print("iterate:", e)
Output:
StringIO: I/O operation on closed file
iterate: I/O operation on closed file.
io.StringIO uses the same message without the trailing period. Read getvalue() before closing. Calling close() twice is harmless; check f.closed when you aren't sure. Related: OSError: [Errno 9] Bad file descriptor.
Frequently asked questions
Why do I get I/O operation on closed file inside a loop?
The loop, or the line inside it, is not indented under the with block, so it runs after the file was closed.
Why does csv.reader raise I/O operation on closed file?
The reader is lazy. Iterate it, or call list(reader), inside the with block.
How do I check whether a file is closed?
Use f.closed, which is True after close() or after the with block ends.
Written with AI assistance. Measured on 2026-10-22 on Debian with Python 3.13.5: writes after a with block, returning a file from a function, csv.reader/writer used after close, StringIO, iteration and double close. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.