Use context managers to close files reliably
In this article (6 sections)
Use a with statement to give an open file a clear lifetime. The file is closed when control leaves the block, including when an exception propagates. That makes resource cleanup predictable and keeps ownership visible to the reader.
Closing a file does not mean the written dataset is complete, valid or atomically published. Those are separate guarantees that require their own design.
Observe closure on both paths
Run this self-contained example from the Python reporting lab. It uses a temporary file and deliberately raises an exception after reading it.
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
source = Path(directory) / "sample.txt"
source.write_text("order_id,amount\nR1,19.00\n", encoding="utf-8")
with source.open(encoding="utf-8") as normal:
assert not normal.closed
assert normal.readline().strip() == "order_id,amount"
assert normal.closed
try:
with source.open(encoding="utf-8") as exceptional:
assert exceptional.read().startswith("order_id")
raise ValueError("deliberate_validation_failure")
except ValueError as error:
assert str(error) == "deliberate_validation_failure"
assert exceptional.closed
print("File closed after normal and exceptional exits")The with-statement reference describes how context managers enter and exit their managed scope. File objects implement that protocol; the assertions make the cleanup observable.
Put processing inside the resource lifetime
csv.DictReader reads from its underlying handle as it is iterated. Creating the reader inside a with block and trying to consume it after the block closes the file is a lifetime error.
Either finish iteration inside the block or deliberately materialize the required records before leaving it. Materialization consumes memory, so do not convert a large file to a list solely to avoid thinking about resource ownership.
The lab's loader receives an open handle and consumes it during the call. The caller owns opening and closing that handle. This separation also makes it easy to test the loader with an in-memory text stream.
Do not assume cleanup reverses a partial write
If a program opens a destination with mode w, the prior contents may be truncated before later validation fails. A with statement will close the file, but it will not restore those prior bytes.
Validate inputs and calculate outputs before opening the final destination when practical. For a production single-file handoff, a common design writes a temporary file in the target filesystem and replaces the final path only after successful completion, subject to the filesystem's guarantees.
Multiple related files need additional coordination. The lab writes accepted rows, rejected rows and a summary as separate files; it explicitly does not claim an atomic three-file transaction. A failure between writes can leave an incomplete output directory.
Keep exception behavior visible
A context manager can be designed to suppress an exception, but ordinary file cleanup should not be mistaken for error handling. The example catches its deliberate ValueError outside the with block so the failure remains explicit.
Avoid adding a broad except around writing merely to print done afterward. A report that could not be written should fail visibly, even if its input file was correctly closed.
Test the failure you care about
Resource tests should inspect observable behavior. Here, closed confirms cleanup. A report-write test should additionally inspect output existence, content and failure status. Those assertions answer different questions.
On some platforms, an unclosed file can interfere with rename or deletion; on others, the same operation may appear to work. Relying on one platform's permissive behavior can hide a lifecycle bug. Explicit ownership is clearer across environments.
Exercise: create a CSV reader inside a with block and deliberately consume it after closure. Observe the failure, then move iteration into the block. Explain when returning a list is acceptable and when a streaming interface is preferable.
NeuraPath's Data Analytics with Generative AI course connects Python resource handling with dependable reporting. Correct cleanup is one part of reliability; output integrity still needs its own checks.
Continue learning
This article is part of the Python foundations for analysts sequence. Use the neighbouring tasks when you need the prerequisite or the next application.
- Review the prerequisite or neighbouring task in Separate configuration from analysis code.
- Continue with Write a reusable Python module from a notebook.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI