Mastering Python Context Managers and the with Statement
Learn to build custom context managers in Python. Understand __enter__ and __exit__ methods to handle resource management and ensure clean code execution.

Previously in this course, we used the with statement to handle file operations in Reading Files in Python: A Guide to File I/O and Context Managers. While that taught us how to consume existing tools, today we’ll take a step further: we are going to build our own context managers from scratch.
Understanding Resource Management
In backend development, resource management is the practice of ensuring that every resource you open—a database connection, a network socket, or even a temporary file—is properly closed after use. If you forget to close these, your application will eventually leak resources, leading to crashes or "too many open files" errors.
A context manager is a Python object that defines the runtime context to be established when executing a with statement. It encapsulates the "setup" phase (acquiring the resource) and the "teardown" phase (releasing the resource), guaranteeing that the teardown happens even if an error occurs.
The Anatomy of a Context Manager
To create a custom context manager, you define a class with two special magic methods: __enter__ and __exit__.
__enter__(self): This method is called when thewithblock begins. It should perform the setup logic and return the resource you want to use.__exit__(self, exc_type, exc_value, traceback): This method is called when thewithblock ends, whether it finished successfully or raised an exception. It performs the cleanup logic.
A Worked Example: A Simple Timer
Let's build a Timer class that logs how long a specific block of code takes to execute. This is a classic example of a context manager that doesn't just handle file I/O, but handles time as a resource.
PYTHONimport time class Timer: def __enter__(self): self.start = time.perf_counter() return self def __exit__(self, exc_type, exc_val, exc_tb): self.end = time.perf_counter() self.duration = self.end - self.start print(f"Code block took {self.duration:.4f} seconds to run.") # Using our custom context manager with Timer(): # Simulate some work time.sleep(1.5)
In this example, when with Timer() is called, Python executes __enter__. When the indentation level returns to the original scope, Python automatically triggers __exit__, calculating the elapsed time.
Why Use Custom Context Managers?
Beyond simple timers, you will use these for:
- Database Connections: Opening a connection and ensuring it closes even if a query fails.
- Locking: Acquiring a thread lock and ensuring it releases.
- API Client Sessions: Managing headers or temporary authentication tokens.
Hands-on Exercise: A Database Simulator
Create a class called DatabaseConnection.
- In
__enter__, print "Connecting to database..." and return the string "Connected". - In
__exit__, print "Closing connection...". - Write a
withstatement that assigns the result ofDatabaseConnection()to a variable nameddband prints it.
PYTHON# Exercise Template class DatabaseConnection: def __enter__(self): # Your code here pass def __exit__(self, exc_type, exc_value, traceback): # Your code here pass # Test your implementation with DatabaseConnection() as db: print(f"Doing work with: {db}")
Common Pitfalls
- Forgetting to return a value: If you want to use the resource inside the
withblock (e.g.,with MyManager() as resource:), your__enter__method must return that resource. - Ignoring Exceptions: Your
__exit__method receives exception details. If you want to suppress an exception (prevent it from bubbling up), you must returnTruefrom__exit__. By default, it returnsNone, which means the exception will continue to propagate. - Over-engineering: Don't build a class if a simple
try...finallyblock is sufficient. Context managers are for reusable logic.
FAQ
Q: Can I use functions instead of classes for context managers?
A: Yes, using the @contextlib.contextmanager decorator, you can write a generator function that yields a value. We will explore this in a more advanced lesson.
Q: Does __exit__ run if the program crashes?
A: It runs if an exception is raised within the with block. If the entire Python process is killed (e.g., kill -9 or a power failure), the __exit__ method will not run.
Recap
Context managers are the backbone of clean, safe resource management in Python. By defining __enter__ and __exit__, you ensure that your setup and cleanup logic are tightly coupled and guaranteed to execute. This is an essential pattern for building robust CLI tools and APIs, much like the Persistent CLI Tool we built earlier in this course.
Up next: We will look at Generators to learn how to handle large data sets without consuming all your computer's memory.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

Next.js Website & Landing Page Development
A blazing-fast, SEO-optimized website or landing page in Next.js — the kind that loads instantly and ranks. Design-to-code, done right.


