This Python Feature Has Been There for Years — But Nobody Talks About It

It’s not async, not match-case, and not a fancy library

This Python Feature Has Been There for Years — But Nobody Talks About It
Photo by Julia Taubitz on Unsplash

The Forgotten Gem in Python’s Standard Library

If you’ve been using Python for a while, you probably think you know the language well. You use list comprehensions, decorators, and maybe even dataclasses. You might sprinkle in some pathlib or functools.lru_cache for optimization.

But there’s one feature that’s been sitting quietly in the standard library for years — often overlooked, rarely mentioned, and yet surprisingly powerful:

The contextlib module.

Wait — contextlib? That boring-sounding thing you might have scrolled past in the docs?

Stick with me. By the end of this article, you’ll see why contextlib can make your code not only cleaner but also more expressive — and why it deserves far more love than it gets.

Most Python developers know about with statements for managing resources:

with open("file.txt") as f: 
    data = f.read()

The idea is simple: you enter a block of code, something gets set up; you leave, it gets cleaned up automatically — no matter what happens inside.

But what if you could create your own custom with statements?

That’s where contextlib shines.

The Core Idea: Custom Context Managers Without the Boilerplate

Before contextlib, creating your own context manager meant writing a whole class with __enter__ and __exit__ methods. Functional, yes — but clunky.

contextlib changes the game with @contextmanager.

Here’s the difference:

Without contextlib:

class DatabaseConnection: 
    def __enter__(self): 
        self.conn = connect_to_db() 
        return self.conn 
 
    def __exit__(self, exc_type, exc_value, traceback): 
        self.conn.close() 
 
with DatabaseConnection() as db: 
    db.query("SELECT * FROM users")

With contextlib:

from contextlib import contextmanager 
 
@contextmanager 
def database_connection(): 
    conn = connect_to_db() 
    try: 
        yield conn 
    finally: 
        conn.close() 
 
with database_connection() as db: 
    db.query("SELECT * FROM users")

Cleaner. Shorter. Easier to read.

Real-World Uses That Will Make You Want to Try It

Here are some surprisingly practical things you can do with contextlib beyond database connections.

1. Temporarily Change Working Directory

import os 
from contextlib import contextmanager 
 
@contextmanager 
def change_dir(destination): 
    prev_dir = os.getcwd() 
    os.chdir(destination) 
    try: 
        yield 
    finally: 
        os.chdir(prev_dir) 
 
with change_dir("/tmp"): 
    # All file ops happen in /tmp 
    open("test.txt", "w").write("Hello")

No need to remember to change back — it’s automatic.

2. Suppress Specific Exceptions

Python 3.4 introduced contextlib.suppress — a simple way to ignore certain errors:

from contextlib import suppress 
 
with suppress(FileNotFoundError): 
    os.remove("non_existent_file.txt")

No messy try-except blocks.

3. Redirect Standard Output

Need to silence noisy functions? Use redirect_stdout:

import io 
from contextlib import redirect_stdout 
 
f = io.StringIO() 
with redirect_stdout(f): 
    print("This won't show up in console")

Great for testing or hiding logs during automation.

4. Exit Early Without Raising Errors

contextlib.ExitStack lets you manage multiple context managers dynamically:

from contextlib import ExitStack 
 
with ExitStack() as stack: 
    files = [stack.enter_context(open(f)) for f in ("a.txt", "b.txt")] 
    # Work with both files safely

You can add or remove contexts at runtime — perfect for handling variable resources.

Why Nobody Talks About It

I think contextlib flies under the radar for three reasons:

  1. It’s “not flashy” — it’s a utility, not a headliner like async or type hints.
  2. It feels advanced — many developers don’t realize they can make their own with statements.
  3. It’s well-behaved — when used correctly, it just works quietly in the background.

But that’s exactly why it’s so powerful: it’s invisible when you don’t need it, and magical when you do.

My Favorite Trick: Temporary Settings Override

Here’s one I actually use in production for temporarily changing config settings:

@contextmanager 
def temp_config(config, **overrides): 
    original = config.copy() 
    config.update(overrides) 
    try: 
        yield 
    finally: 
        config.clear() 
        config.update(original) 
 
settings = {"debug": False, "cache": True} 
 
with temp_config(settings, debug=True): 
    print(settings)  # {'debug': True, 'cache': True} 
 
print(settings)  # {'debug': False, 'cache': True}

It keeps tests clean and avoids side effects.

How to Start Using It Today

If you’re new to contextlib, here’s the plan:

  1. Identify repeated setup/teardown patterns in your code — file handling, API sessions, temporary state changes.
  2. Wrap them in a @contextmanager function for cleaner, safer handling.
  3. Explore built-ins like suppress, redirect_stdout, and ExitStack — they can replace messy try-except-finally code.
  4. Refactor gradually — you don’t need to rewrite everything, just start with small wins.

The Takeaway

The best Python features aren’t always the newest or the most hyped. Sometimes, they’re the quiet, humble ones hiding in the standard library, waiting for you to notice.

contextlib is one of those — and once you start using it, you’ll wonder how you ever wrote Python without it.


The next time you find yourself writing boilerplate setup and cleanup code, remember — Python’s been giving you the perfect tool for years. You just had to look.