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

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:
Thecontextlib
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:
- It’s “not flashy” — it’s a utility, not a headliner like async or type hints.
- It feels advanced — many developers don’t realize they can make their own
with
statements. - 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:
- Identify repeated setup/teardown patterns in your code — file handling, API sessions, temporary state changes.
- Wrap them in a
@contextmanager
function for cleaner, safer handling. - Explore built-ins like
suppress
,redirect_stdout
, andExitStack
— they can replace messy try-except-finally code. - 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.