Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›Context Managers (class-based)

Advanced Python

Context Managers (class-based)

Class-based context managers provide a structured way to handle resource allocation and deallocation using the with statement. By implementing specific magic methods, they guarantee that setup and teardown logic executes predictably, even if errors occur. You use them whenever you need to manage persistent resources like database connections, file handles, or network locks reliably.

The Protocol: __enter__ and __exit__

To build a custom context manager using a class, you must implement the __enter__ and __exit__ magic methods. When the Python interpreter encounters a with statement, it first initializes the class instance and invokes the __enter__ method. Whatever value the __enter__ method returns is then assigned to the variable defined after the 'as' keyword. The primary purpose of __enter__ is to perform the 'setup' phase: acquiring a lock, opening a file, or establishing a connection. Once the code block within the 'with' statement finishes execution, Python automatically invokes the __exit__ method. This method serves as the 'teardown' phase, where you must perform cleanup, such as closing handles or releasing acquired resources. Understanding this flow is critical because it ensures that resources are never leaked, regardless of whether the inner block executes successfully or terminates abruptly due to an unhandled exception.

class ResourceHandler:
    def __enter__(self):
        print("Allocating resource")
        return "Resource"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Releasing resource")

with ResourceHandler() as r:
    print(f"Using {r}")

Handling Exceptions within __exit__

One of the most powerful features of class-based context managers is their ability to intercept and process exceptions that occur inside the with block. The __exit__ method accepts three specific arguments: exc_type, exc_val, and exc_tb, which represent the type, value, and traceback of any exception raised during the execution of the block. If no exception occurred, these arguments are all None. If an error does occur, the __exit__ method is still guaranteed to run before the exception propagates further. This allows you to handle the cleanup process while knowing exactly what went wrong. If you want to suppress the exception entirely, you can return a 'truthy' value from the __exit__ method; if you return 'falsy' or nothing (None), the exception will continue to propagate up the call stack after your cleanup code completes. This mechanism allows you to build robust error-handling wrappers that hide complexity from the caller.

class SafeExecutor:
    def __enter__(self): return self
    def __exit__(self, exc_t, exc_v, exc_tb):
        if exc_t:
            print(f"Caught error: {exc_v}")
        return True # Suppresses the exception

with SafeExecutor():
    raise ValueError("Something went wrong")

Stateful Management for complex logic

While functions or decorators can sometimes serve as context managers, class-based implementations are superior when you need to maintain state throughout the context's lifecycle. Because an instance of the class is created, you can store data in 'self' attributes during the 'with' block, which can then be inspected or processed during the 'exit' phase. This is particularly useful for measuring performance, logging state changes, or accumulating data before finalizing a transaction. By keeping internal state encapsulated within the object, you ensure that your resource management remains thread-safe and isolated from other instances of the same context manager. This object-oriented approach scales well because you can add extra methods to the class to provide specialized functionality or configuration parameters, making your context manager highly reusable and flexible. You are no longer limited to simple procedural setups; you gain the ability to build sophisticated, state-aware resource controllers.

import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self
    def __exit__(self, *args):
        self.end = time.time()
        print(f"Elapsed: {self.end - self.start:.4f}s")

with Timer():
    sum(range(1000000))

Ensuring Atomicity in Transactions

Class-based context managers are ideal for operations requiring atomicity, where a series of tasks must either all succeed or all fail together. By managing the transaction lifecycle within the class, you can implement a 'commit or rollback' pattern. Inside the __enter__ method, you might open a transaction buffer or a temporary workspace. As the code executes inside the 'with' block, you perform your operations. If an exception triggers the __exit__ method, you can perform a rollback to revert any partial changes, ensuring the system remains in a consistent state. If the block completes without incident, you can explicitly trigger a commit process inside the __exit__ method or a separate 'commit' call. This design pattern removes the burden from the caller to remember to clean up or finalize transactions, as the context manager acts as a guard that enforces strict integrity rules over the resource being manipulated throughout the block's lifespan.

class Transaction:
    def __enter__(self):
        print("Start transaction")
        return self
    def __exit__(self, exc_t, *args):
        if not exc_t:
            print("Commit changes")
        else:
            print("Rollback changes")

with Transaction():
    print("Performing work...")

Composition and Reusability

Classes allow you to compose multiple context managers into a single unified interface, providing a clean abstraction for complex systems. If you have several distinct resources that need to be managed—such as a file system lock, a network socket, and a database connection—you can instantiate them within a master context manager class. By centralizing the management logic, you avoid 'nested with' statements that can quickly clutter your codebase and make error handling difficult to maintain. Furthermore, because class-based managers are objects, you can accept configuration parameters in the __init__ method, making them dynamic. For instance, you could pass a database URL or a retry limit during initialization. This separation of configuration from execution allows you to write one robust, generic context manager class that can be deployed across different parts of your application with varying settings, effectively centralizing your infrastructure resource management policies in one location.

class MultiResource:
    def __enter__(self):
        self.f = open('data.txt', 'w')
        return self.f
    def __exit__(self, *args):
        self.f.close()
        print("File closed")

with MultiResource() as f:
    f.write("Hello World")

Key points

  • The __enter__ method handles resource initialization and returns the object used within the context.
  • The __exit__ method is responsible for cleanup tasks and is guaranteed to run even if exceptions occur.
  • Arguments in __exit__ capture exception details, allowing for custom error handling or suppression.
  • Returning a truthy value from __exit__ tells the interpreter to swallow any active exceptions.
  • Stateful context managers use instance attributes to track information during the execution of the block.
  • Class-based managers are superior to generators for complex logic requiring custom methods or state.
  • Resource atomicity is achieved by using the exit method to commit or rollback based on success status.
  • Composition allows developers to manage multiple related resources within a single consistent interface.

Common mistakes

  • Mistake: Forgetting to return a value from __enter__. Why it's wrong: If __enter__ returns None, the 'as' variable in the 'with' statement will be None. Fix: Ensure __enter__ returns 'self' or the resource to be used.
  • Mistake: Raising an exception in __exit__ that is not related to the handled error. Why it's wrong: This can suppress the original exception or cause unexpected behavior by masking the actual error. Fix: Only raise exceptions in __exit__ if you intend to re-raise or wrap the current exception chain.
  • Mistake: Assuming the __exit__ method is called if the __init__ method fails. Why it's wrong: If an error occurs during instantiation, the object is never created, so __exit__ is never reached. Fix: Use try-finally blocks outside the context manager or handle initialization errors within __init__.
  • Mistake: Forgetting to handle exceptions in __exit__. Why it's wrong: If an exception occurs inside the 'with' block, the context manager must explicitly return True to suppress it, or it will propagate. Fix: Return True in __exit__ only if you want to swallow the exception.
  • Mistake: Trying to close a resource twice. Why it's wrong: Calling close methods in both __exit__ and a custom close method can lead to errors if the resource state is modified inconsistently. Fix: Centralize cleanup logic strictly within the __exit__ method.

Interview questions

What is a class-based context manager in Python and what is its primary purpose?

A class-based context manager is a Python object that defines the runtime context for a block of code by implementing the special __enter__ and __exit__ methods. Its primary purpose is to manage resources, such as file handles, database connections, or network sockets, ensuring they are automatically and reliably acquired when entering the block and cleaned up or closed when exiting, even if an exception occurs.

What are the roles of the __enter__ and __exit__ methods in a class-based context manager?

The __enter__ method is called when execution enters the with-statement block; it performs setup tasks and returns the object that will be bound to the variable specified in the 'as' clause. The __exit__ method is called when the block finishes or an exception is raised. It is responsible for cleanup tasks, such as closing file descriptors, and it receives three arguments: exc_type, exc_val, and exc_tb, which allow it to handle or suppress exceptions.

How does a class-based context manager handle exceptions during execution?

When an exception occurs inside a 'with' block, the __exit__ method is still guaranteed to be executed. The method receives the exception details via its arguments. If the method returns False or None, the exception propagates up the stack after the cleanup code finishes. If the method returns True, it effectively suppresses the exception, allowing the program to continue execution gracefully as if no error had occurred within the block.

Compare the class-based approach to the generator-based approach using @contextlib.contextmanager.

The class-based approach is often preferred when the context manager requires complex internal state management, multiple methods for auxiliary operations, or if it needs to be inherited. Conversely, the generator-based approach using @contextlib.contextmanager is more concise for simple resource management. It uses a yield statement to split the code into setup and teardown parts, requiring significantly less boilerplate code compared to explicitly defining a class with __enter__ and __exit__.

How would you implement a custom class-based context manager that times the execution of a block of code?

You would define a class with an __enter__ method that records the current time using time.perf_counter(). The __exit__ method would then record the time again, calculate the difference, and print the elapsed duration. By using a class, you maintain the state of the start time cleanly between method calls, ensuring that even if the code block fails, you can still report the total duration before the exception propagates.

Explain the importance of the __exit__ method's return value in suppressing exceptions.

The return value of __exit__ is a boolean that dictates whether the context manager swallows an exception. If __exit__ returns True, the Python interpreter considers the exception handled and resumes execution after the 'with' block. This is critical for building custom control structures or testing frameworks where you might expect specific errors to occur. If it returns False, the original exception continues to bubble up, which is essential for ensuring that real errors are not silently ignored by the caller.

All Python interview questions →

Check yourself

1. When does the __enter__ method of a class-based context manager execute?

  • A.Immediately after the class instance is created in the __init__ method.
  • B.When the execution flow enters the block defined by the 'with' statement.
  • C.Only if an exception is raised within the code block.
  • D.After the 'with' block has finished executing.
Show answer

B. When the execution flow enters the block defined by the 'with' statement.
__enter__ runs exactly when the 'with' block starts. Option 0 is wrong because __init__ is for setup, not context entering. Option 2 is wrong because __enter__ always runs regardless of exceptions. Option 3 describes the purpose of __exit__.

2. What is the result of returning False from the __exit__ method when an exception occurs inside the 'with' block?

  • A.The exception is caught and the program continues after the 'with' block.
  • B.The exception is silenced and no error is reported.
  • C.The exception propagates out of the 'with' block normally.
  • D.The exception is converted into a warning.
Show answer

C. The exception propagates out of the 'with' block normally.
Returning a falsy value (or None) from __exit__ tells Python to re-raise the exception. Option 0 and 1 are incorrect because they describe suppressing the exception (True). Option 3 is incorrect as warnings are unrelated to flow control.

3. If you want to use an object in a 'with' statement like 'with MyClass() as obj:', what must __enter__ return?

  • A.The object itself (self).
  • B.The class instance's __init__ method.
  • C.The value True.
  • D.A list of all objects managed by the class.
Show answer

A. The object itself (self).
The value assigned to the variable after 'as' is the return value of __enter__. Returning 'self' is standard practice. Option 1 is invalid syntax, option 2 is incorrect logic, and option 3 would make 'obj' equal to True.

4. Why is it important to implement __exit__ even if you don't intend to handle exceptions?

  • A.To define how the object is serialized.
  • B.To ensure cleanup tasks like closing files or sockets happen automatically.
  • C.To prevent the garbage collector from deleting the object.
  • D.To allow multiple 'with' statements to share the same instance.
Show answer

B. To ensure cleanup tasks like closing files or sockets happen automatically.
The main purpose of a context manager is resource management (RAII). Option 0 is for pickling, option 2 is unrelated to manual memory management, and option 3 is not the responsibility of the context manager.

5. What happens if __enter__ fails (raises an exception) before the 'with' block is entered?

  • A.The __exit__ method is called with exception details.
  • B.The __exit__ method is called with None arguments.
  • C.The __exit__ method is never called.
  • D.The code block inside the 'with' statement still executes.
Show answer

C. The __exit__ method is never called.
If __enter__ fails, the code block never runs and the context was never established, so __exit__ is not called. Option 0 and 1 are incorrect because the manager is not fully initialized. Option 3 is impossible if the block never executes.

Take the full Python quiz →

← PreviousIterators and the Iterator ProtocolNext →functools — partial, reduce, lru_cache

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app