Skip to content

Context Managers (with statement) in Python

Mastering Context Managers in Python: A Comprehensive Guide

Opening a file, connecting to a database, or acquiring a lock all share a pattern: you set something up, use it, and must clean it up afterwards — even if an error occurs. Context managers automate this setup-and-teardown through the withwith statement, guaranteeing cleanup happens. In this guide, we’ll explore the withwith statement, the __enter____enter__ and __exit____exit__ methods, and the contextlibcontextlib module.

The Problem Context Managers Solve

Without a context manager, you must remember to release resources manually — and handle errors so cleanup still runs.

manual.py
# Manual cleanup - easy to forget, fragile if an error occurs
file = open("data.txt", "w")
try:
    file.write("Hello")
finally:
    file.close()   # must remember this, even on error
manual.py
# Manual cleanup - easy to forget, fragile if an error occurs
file = open("data.txt", "w")
try:
    file.write("Hello")
finally:
    file.close()   # must remember this, even on error

The withwith statement does all of this for you.

The with Statement

The withwith statement wraps a block of code so that resources are automatically cleaned up when the block ends — whether it ends normally or because of an exception.

The syntax of the withwith statement in Python is as follows:

Syntax
with expression as variable:
    # code that uses the resource
Syntax
with expression as variable:
    # code that uses the resource

The classic example is file handling:

with_file.py
with open("data.txt", "w") as file:
    file.write("Hello, World!")
# file is automatically closed here - even if write() raised an error
print("File closed:", file.closed)
with_file.py
with open("data.txt", "w") as file:
    file.write("Hello, World!")
# file is automatically closed here - even if write() raised an error
print("File closed:", file.closed)

Output:

command
C:\Users\Your Name> python with_file.py
File closed: True
command
C:\Users\Your Name> python with_file.py
File closed: True

Diagram:

diagram with statement flow mermaid
How the with statement manages a resource

What is a Context Manager?

A context manager is any object that implements two special methods:

  • __enter__(self)__enter__(self) — runs when the withwith block is entered. Its return value is bound to the asas variable.
  • __exit__(self, exc_type, exc_value, traceback)__exit__(self, exc_type, exc_value, traceback) — runs when the block exits, for cleanup. It receives details of any exception that occurred.

Building a Class-Based Context Manager

Let’s build a TimerTimer that measures how long the withwith block takes.

timer.py
import time
 
class Timer:
    def __enter__(self):
        self.start = time.time()
        print("Timer started")
        return self           # bound to the 'as' variable
 
    def __exit__(self, exc_type, exc_value, traceback):
        elapsed = time.time() - self.start
        print(f"Elapsed: {elapsed:.2f}s")
        return False          # do not suppress exceptions
 
with Timer() as t:
    total = sum(range(1_000_000))
 
print("Done")
timer.py
import time
 
class Timer:
    def __enter__(self):
        self.start = time.time()
        print("Timer started")
        return self           # bound to the 'as' variable
 
    def __exit__(self, exc_type, exc_value, traceback):
        elapsed = time.time() - self.start
        print(f"Elapsed: {elapsed:.2f}s")
        return False          # do not suppress exceptions
 
with Timer() as t:
    total = sum(range(1_000_000))
 
print("Done")

Output:

command
C:\Users\Your Name> python timer.py
Timer started
Elapsed: 0.03s
Done
command
C:\Users\Your Name> python timer.py
Timer started
Elapsed: 0.03s
Done

The exit Return Value

If __exit____exit__ returns TrueTrue, any exception raised inside the block is suppressed (swallowed). If it returns FalseFalse (or NoneNone), the exception propagates normally after cleanup. Most context managers return FalseFalse.

suppress.py
class Suppressor:
    def __enter__(self):
        return self
 
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is ValueError:
            print("Suppressed a ValueError")
            return True       # swallow ValueError
        return False
 
with Suppressor():
    raise ValueError("boom")   # caught and suppressed
print("Program continues")
suppress.py
class Suppressor:
    def __enter__(self):
        return self
 
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is ValueError:
            print("Suppressed a ValueError")
            return True       # swallow ValueError
        return False
 
with Suppressor():
    raise ValueError("boom")   # caught and suppressed
print("Program continues")

Output:

command
C:\Users\Your Name> python suppress.py
Suppressed a ValueError
Program continues
command
C:\Users\Your Name> python suppress.py
Suppressed a ValueError
Program continues

The contextlib Approach

Writing a whole class can be heavy. The contextlib.contextmanagercontextlib.contextmanager decorator lets you build a context manager from a simple generator function. The code before yieldyield is the setup (__enter____enter__), and the code after yieldyield is the cleanup (__exit____exit__).

contextlib_timer.py
from contextlib import contextmanager
import time
 
@contextmanager
def timer():
    start = time.time()       # setup (like __enter__)
    print("Timer started")
    yield                     # the 'with' body runs here
    elapsed = time.time() - start
    print(f"Elapsed: {elapsed:.2f}s")   # cleanup (like __exit__)
 
with timer():
    total = sum(range(1_000_000))
contextlib_timer.py
from contextlib import contextmanager
import time
 
@contextmanager
def timer():
    start = time.time()       # setup (like __enter__)
    print("Timer started")
    yield                     # the 'with' body runs here
    elapsed = time.time() - start
    print(f"Elapsed: {elapsed:.2f}s")   # cleanup (like __exit__)
 
with timer():
    total = sum(range(1_000_000))

Output:

command
C:\Users\Your Name> python contextlib_timer.py
Timer started
Elapsed: 0.03s
command
C:\Users\Your Name> python contextlib_timer.py
Timer started
Elapsed: 0.03s

Multiple Context Managers

You can manage several resources in one withwith statement by separating them with commas.

multiple.py
with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())
# both files are closed automatically
multiple.py
with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())
# both files are closed automatically

Visualize it

A context manager guarantees cleanup: no matter how the withwith body finishes, __exit____exit__ is called.

diagram How a with block guarantees cleanup mermaid
__enter__ acquires the resource and __exit__ releases it, even if the body raises.

Conclusion

Context managers automate resource setup and cleanup through the withwith statement, guaranteeing teardown even when errors occur. Any object with __enter____enter__ and __exit____exit__ methods is a context manager; __enter____enter__ sets up and returns the resource, while __exit____exit__ cleans up and decides whether to suppress exceptions. For lighter cases, the @contextmanager@contextmanager decorator from contextlibcontextlib turns a generator into a context manager using yieldyield to split setup from cleanup. Reach for withwith whenever you handle files, locks, or connections. For more hands-on examples and in-depth tutorials, explore our resources on Python Central Hub!


Try it: Context Managers Exercises

Exercise 1 – The with Statement

Exercise 2 – Class-Based Context Manager

Exercise 3 – contextlib Generator

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did