Skip to main content
Every time you open a file, acquire a database connection, or grab a thread lock, you need a reliable way to release that resource when you are done — even if your code raises an exception midway through. Context managers give you exactly that guarantee through the with statement. Once you see how they work, you will use them everywhere.

What a Context Manager Does

A context manager wraps a block of code with two operations:
  1. Setup — runs before your block executes (open the file, acquire the lock, start a timer)
  2. Teardown — runs after your block finishes, regardless of whether an exception was raised
Without a context manager you would need a try / finally block every time — more lines, more chances to forget the cleanup.

How with Works: __enter__ and __exit__

Any object that implements __enter__ and __exit__ can be used with with. Python calls them automatically:
The value returned by __enter__ is bound to the variable after as.

Writing Your Own Context Manager: Class-Based

If you return True from __exit__, Python suppresses any exception that was raised inside the with block. Return False (or None) to let exceptions propagate normally.

Writing Your Own Context Manager: Generator-Based

For most use cases, the @contextmanager decorator from contextlib is simpler than writing a full class. You write a generator function, yield the value, and Python handles the rest.
The finally block runs even when an exception is raised inside the with block, giving you the same guarantee as __exit__.

Exception Handling Inside the Generator

When an exception escapes the with block, Python injects it back into the generator at the suspended yield using generator.throw(). You can catch it inside the generator:

Built-In Context Managers

File I/O

open() returns a context manager that closes the file handle on exit — even if an exception occurs mid-read.

Thread Locks

threading.Lock() acquires the lock on enter and releases it on exit, preventing deadlocks.

Temporary Directory

tempfile.TemporaryDirectory() creates a temp dir and deletes it (with all contents) when the block exits.

Decimal precision

decimal.localcontext() temporarily changes arithmetic precision within a block without affecting global settings.

Timing Code with a Context Manager

A context manager makes a clean, reusable timer:

How FastAPI Uses the Same Pattern

FastAPI’s Depends() system lets you use generator-based context managers as dependencies. FastAPI calls next() to run the setup, injects the yielded value into your endpoint, and then resumes the generator after the endpoint returns — guaranteeing cleanup before the HTTP response is sent.
This is exactly the same yield + finally pattern you write with @contextmanager — FastAPI just automates the wiring.

When to Reach for a Context Manager

1

Any resource that must be released

Files, database connections, network sockets, thread locks, GPU memory — anything that needs cleanup belongs in a context manager.
2

Wrapping setup/teardown around a block

Starting a transaction, entering a profiler, redirecting stdout — anything with a matching before/after pair.
3

Suppressing or logging specific exceptions

contextlib.suppress(FileNotFoundError) is a one-liner context manager that silently ignores specific exceptions.