python code terminal screen

Photo by Daniil Komov on Pexels


We’ve all been there: opening a file, connecting to a database, or locking a thread, only to realize halfway through that an exception is going to crash your script and leave your resources dangling like a sad party balloon. Enter Python’s context managers—the elegant, blood-pressure-lowering feature that handles your cleanup duty so you can go back to pretending you know what you’re doing.

Welcome back to the Old Lane 17 workshop. Today, we are talking about resource management. If your Python codebase is currently littered with nested try...except...finally blocks just to make sure a file gets closed or a database connection gets dropped, grab a coffee. We need to talk.

The Problem: The Pyramid of Doom

Imagine you’re writing a script to process a log file. The amateur hour approach looks something like this:

python
# Please, I beg of you, do not do this
file = open("production.log", "r")
try:
    data = file.read()
    # Do some risky parsing here
    process_data(data)
finally:
    file.close()
    print("Closed the file, phew.")

Sure, it works. But it’s verbose, ugly, and the moment you need to open two files and acquire a lock, your code starts aggressively leaning to the right like the Tower of Pisa.

The Clean Solution: The with Statement

Python gave us the with statement specifically to save us from our own boilerplate. Under the hood, a context manager handles the setup and teardown phases automatically.

Here is how that exact same log-processing snippet looks when you let Python do the heavy lifting:

python
# Clean, readable, and practically bulletproof
with open("production.log", "r") as file:
    data = file.read()
    process_data(data)

# The file is automatically closed here. Magic!

Even if process_data(data) throws an unhandled exception that sends your program straight to the shadow realm, Python guarantees that file.close() gets executed.

Roll Your Own: The @contextmanager Decorator

What happens when you need custom behavior—like timing how long a database query takes, or temporarily changing a working directory? You don’t need to write a clunky class with __enter__ and __exit__ methods unless you really want to flex. Instead, use the contextlib module.

Here is a quick, practical timer context manager:

python
import time
from contextlib import contextmanager

@contextmanager
def timer(operation_name):
    start_time = time.time()
    print(f"[{operation_name}] Starting...")
    try:
        yield  # Control drops back to the 'with' block here
    finally:
        elapsed = time.time() - start_time
        print(f"[{operation_name}] Finished in {elapsed:.4f} seconds.")

# Using your custom context manager
with timer("Heavy Data Crunching"):
    # Pretend we are doing hard work
    time.sleep(1.5)

The Gotcha: Swallowing Exceptions Accidentally

A quick word to the wise: when writing custom context managers, be careful with your try...except blocks inside the generator. If you catch an exception and don’t re-raise it, Python assumes you handled it, and your with block will swallow errors silently. That’s a fantastic way to spend three hours debugging why your database is empty.

Keep your cleanup in finally blocks, let errors bubble up unless you explicitly mean to catch them, and keep your terminal workflows clean. Until next time, write less code and let Python do the chores.


Published by the Old Lane Developer Desk.

Disclaimer: This article was generated and formatted with the assistance of Artificial Intelligence.