Skip to content

Context Managers (with statement) in Python

Mastering Context Managers in Python: A Comprehensive Guide

Section titled “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 with statement, guaranteeing cleanup happens. In this guide, we’ll explore the with statement, the __enter__ and __exit__ methods, and the contextlib module.

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

The with statement does all of this for you.

The with 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 with statement in Python is as follows:

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)

Output:

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

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

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

Let’s build a Timer that measures how long the with 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")

Output:

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

If __exit__ returns True, any exception raised inside the block is suppressed (swallowed). If it returns False (or None), the exception propagates normally after cleanup. Most context managers return False.

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

Writing a whole class can be heavy. The contextlib.contextmanager decorator lets you build a context manager from a simple generator function. The code before yield is the setup (__enter__), and the code after yield is the cleanup (__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))

Output:

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

You can manage several resources in one with 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

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

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

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


Exercise 2 – Class-Based Context Manager

Section titled “Exercise 2 – Class-Based Context Manager”

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading