Python Basics: Managing Cleanup with contextlib
An open file needs closing even when processing fails. Python’s with statement gives that cleanup a clear place. Start with built-in context managers, then use contextlib when a script needs more control.
Use Python 3.12 or newer on Linux. These independent examples use in-memory text streams, so they do not create data files.
Let a context manager close a stream
Save as demo_contextlib_1.py:
from io import StringIO
with StringIO("apples\npears\n") as stream:
print("First:", stream.readline().strip())
print("Closed inside:", stream.closed)
print("Closed after:", stream.closed)Run it from the directory where you saved it:
python3 demo_contextlib_1.pyOutput:
First: apples
Closed inside: False
Closed after: TrueStringIO behaves like a text file held in memory. For a disk file, use with open(path, encoding="utf-8") as stream:. The indented block is where you work with the stream; read any needed data before it closes.
Add your own cleanup
Save as demo_contextlib_2.py:
from contextlib import contextmanager
from io import StringIO
@contextmanager
def report_stream(text):
stream = StringIO(text)
print("Opened")
try:
yield stream
finally:
stream.close()
print("Closed:", stream.closed)
try:
with report_stream("report\n") as report:
print(report.readline().strip())
raise ValueError("bad row")
except ValueError as error:
print("Handled:", error)Run it from the directory where you saved it:
python3 demo_contextlib_2.pyOutput:
Opened
report
Closed: True
Handled: bad row@contextmanager turns a generator into a context manager. Yield exactly once: that value becomes report. Put cleanup in finally so it runs on normal exit and when the block raises. The error still reaches the outer handler. For ordinary files, their built-in context manager is enough.
Manage a variable number of streams
Save as demo_contextlib_3.py:
from contextlib import ExitStack
from io import StringIO
with ExitStack() as stack:
streams = [
stack.enter_context(StringIO(text))
for text in ("north\n", "south\n")
]
print("Rows:", [stream.read().strip() for stream in streams])
print("All closed:", all(stream.closed for stream in streams))Run it from the directory where you saved it:
python3 demo_contextlib_3.pyOutput:
Rows: ['north', 'south']
All closed: TrueExitStack groups context managers discovered at runtime. It exits them in reverse entry order, including those already entered if a later entry fails. Keep the stack inside with; garbage collection does not trigger its callbacks. Cleanup on exiting a block cannot protect against a process being forcibly killed.
References
Acknowledgements
This post was created with assistance from AI.