Learn · Run · Break · Fix
A learning series from A Wandering Mind
Eighteen interlinked lessons on strings, lists, files, exceptions, and dictionaries—with original explanations, runnable examples, real output, and supporting CPSC 1301K lectures.
>>> guide = { ... "lessons": 18, ... "modules": 5, ... "python": "3.10+" ... } >>> guide["goal"] = "understand it"
Verifying a File Opened, and Closing It Properly in Python
Opening a file gives your program access to an operating-system resource. Good file handling therefore includes more than getting the path and mode right: your program should also know when that file is open, when it is closed, and how to guarantee cleanup even when later code fails. This lesson compares manual close(), the closed attribute, try/finally, and Python’s preferred with open(...) context-manager pattern.
open(), file objects, and basic file modes.An open file is a resource your program must release
When open() succeeds, Python returns a file object connected to a file managed by the operating system. That connection should not remain open longer than necessary. Closing it releases the underlying resource and makes the program’s intent clear: this part of the code is finished using the file.
For very small scripts, a forgotten close may seem harmless because Python often cleans resources up when the process exits. Relying on process shutdown is poor practice, though. Longer-running programs may open many files, operating systems place limits on open file handles, and buffered output may need to be flushed before another program can safely use the file.
The closed attribute tells you the file object’s state
Every normal file object has a Boolean closed attribute. It is False while the file is open and becomes True after the file is closed. This is a useful verification tool while learning and debugging, although normal application logic usually should not revolve around repeatedly checking it.
file = open("notes.txt", "r", encoding="utf-8")
print(file.closed)
file.close()
print(file.closed)
False
True
The file begins open, so the first value is False. After close(), the same file object still exists as a Python variable, but its underlying file connection is closed and file.closed reports True.
Closed does not mean deleted. Calling close() does not remove the file from disk and does not erase the Python variable. It ends that file object’s active connection to the file.
The with statement closes the file automatically
Modern Python code normally uses a context manager instead of manually pairing every open() with a later close(). The form with open(...) as file: creates the file object for the indented block and guarantees that Python closes it when control leaves that block.
This is safer because cleanup is tied to structure rather than memory. You do not have to remember a closing line at every possible exit path. If an exception interrupts the block, the context manager still performs its cleanup before the exception continues upward.
with open("notes.txt", "r", encoding="utf-8") as file:
print(file.closed)
print(file.closed)
False
True
Inside the with block, the file is open. Immediately after the block finishes, the same object reports True for closed. The indentation visually marks the lifetime of the resource, which makes the code easier to audit.
try/finally shows the cleanup idea explicitly
Before context managers were widely adopted for routine file work, code often used try/finally to guarantee that cleanup happens. The finally block runs whether the protected code succeeds or raises an exception, so it is an appropriate place to release a resource.
You should recognize this pattern because it appears throughout Python and other languages, but for ordinary file handling the with statement is shorter and harder to misuse.
file = open("notes.txt", "r", encoding="utf-8")
try:
print(file.closed)
finally:
file.close()
print(file.closed)
False
True
This works, but compare its structure with Example 2. The context-manager version expresses the same resource-lifetime rule with less code. That is why the rest of this course will generally use with open(...) for file operations.
Context managers matter for writing too
Closing is especially important for output. File objects may buffer writes, meaning Python can temporarily hold some data in memory before sending it to the underlying file. Closing the file flushes remaining buffered data as part of normal cleanup.
The next example writes only one short line, but it demonstrates the pattern you should carry forward: open for writing inside a with block, perform the write while the resource is active, and allow the block to close the file automatically.
with open("report.txt", "w", encoding="utf-8") as file:
file.write("ready\n")
print(file.closed)
True
This lesson is not yet about the details of write(); that arrives in Lesson 14. Here the important point is that the file is closed after the block, so the resource lifetime is controlled even when the operation is output rather than input.
file.close()Ends the connection explicitly. Easy to forget on complex control paths.
file.closedBoolean attribute: False while open, True after closing.
with open(...) as file:Closes automatically when the block ends.
try: ... finally:Useful general cleanup pattern, though usually unnecessary for routine file handling.
A variable can still exist after its file is closed
One subtle point causes many beginner errors: after a with block, the variable name may still refer to the file object. That does not mean the file remains usable. The object is now in a closed state, and operations that require an active file connection raise ValueError.
The same rule applies after a manual close(). A reference to the object survives, but its permitted operations have changed because the resource has been released.
Common mistakes when closing files
All four failures below were executed. Notice that they produce closely related ValueError messages because each one attempts an I/O operation on a closed file.
file = open("notes.txt", "r", encoding="utf-8")
file.close()
print(file.read())
Traceback (most recent call last):
File "/tmp/ipykernel_313/3731909797.py", line 27, in run_err
exec(compile(code, str(work / filename), "exec"), {})
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/awm_lesson12/mistake1_read_closed.py", line 3, in <module>
ValueError: I/O operation on closed file.
Once close() runs, the file object can no longer perform normal reads. Move the read before the close or use a with block that encloses the entire reading operation.
file = open("notes.txt", "r", encoding="utf-8")
file.close()
file.write("hello")
Traceback (most recent call last):
File "/tmp/ipykernel_313/3731909797.py", line 27, in run_err
exec(compile(code, str(work / filename), "exec"), {})
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/awm_lesson12/mistake2_write_closed.py", line 3, in <module>
ValueError: I/O operation on closed file.
This example is closed before the attempted write, so the immediate problem is the closed file. Separately, the file had been opened in read mode, which also would not permit writing while open. When debugging file code, consider both resource state and access mode.
with open("notes.txt", "r", encoding="utf-8") as file:
pass
print(file.readline())
Traceback (most recent call last):
File "/tmp/ipykernel_313/3731909797.py", line 27, in run_err
exec(compile(code, str(work / filename), "exec"), {})
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/awm_lesson12/mistake3_after_with.py", line 3, in <module>
ValueError: I/O operation on closed file.
The variable name still exists, but the context manager closed its file when the indented block ended. Any read that belongs to that file lifetime must stay inside the block.
with open("notes.txt", "r", encoding="utf-8") as file:
file.close()
print(file.read())
Traceback (most recent call last):
File "/tmp/ipykernel_313/3731909797.py", line 27, in run_err
exec(compile(code, str(work / filename), "exec"), {})
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/awm_lesson12/mistake4_manual_close_inside_with.py", line 3, in <module>
ValueError: I/O operation on closed file.
A context manager is already responsible for closing the file. Manually closing it early defeats the purpose of keeping the resource usable throughout the block. Let the context manager own the normal cleanup.
Supporting classroom explanations
This page is original A Wandering Mind instructional material. The CPSC 1301K lectures below reinforce explicit file closing and Python’s context-manager approach to file resource management.
What it adds: a classroom explanation of why open file resources should be closed when a program finishes using them.
What it adds: a focused walkthrough of using a context manager so file cleanup is automatic when the indented block ends.
Try it yourself
These exercises use small files so you can focus on resource lifetime rather than file contents. Predict the closed state before running each answer.
Exercise 1 · Verify manual closing
Open notes.txt for reading, print file.closed, close the file manually, then print file.closed again.
Show one answer
file = open("notes.txt", "r", encoding="utf-8")
print(file.closed)
file.close()
print(file.closed)
False
True
Exercise 2 · Let with close the resource
Open notes.txt with a context manager, print its name inside the block, then print file.closed after the block.
Show one answer
with open("notes.txt", "r", encoding="utf-8") as file:
print(file.name)
print(file.closed)
notes.txt
True
Exercise 3 · Manage a short write
Use with open(...) to open status.txt in write mode, write complete followed by a newline, then print the closed state after the block.
Show one answer
with open("status.txt", "w", encoding="utf-8") as file:
file.write("complete\n")
print(file.closed)
True
What you should carry into Lesson 13
You now know how to verify whether a file object is open, how manual close() changes its state, why try/finally is a general cleanup pattern, and why with open(...) as file: is the preferred approach for ordinary file work. The indentation of the with block defines the period during which file I/O should happen.
Next, you will finally pull content through that open connection. Continue to Reading Files and Processing Lines to compare read(), readline(), readlines(), and direct file iteration.
