🎓 A1Learner
🔍

Python Custom Context Managers

A custom context manager is a class that defines __enter__ and __exit__.

The two protocol methods

A context manager is any object with these two methods. Together they form the protocol.

class Hello:
    def __enter__(self):
        print("Entering")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Leaving")

with Hello():
    print("Inside")

What __enter__ returns

The return value of __enter__ becomes the as target. Here the block receives a plain string.

class Gift:
    def __enter__(self):
        return "a book"

    def __exit__(self, exc_type, exc_value, traceback):
        print("Gift closed")

with Gift() as present:
    print(present)

What __exit__ receives on an error

When the block raises, the three arguments describe that error. Returning False lets it spread.

class Watcher:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Type:", exc_type.__name__)
        print("Value:", exc_value)
        return False

try:
    with Watcher():
        raise ValueError("bad number")
except ValueError:
    print("It still escaped.")

Returning True swallows the error

To swallow an error means to stop it and carry on. Only __exit__ can make that choice.

class Calm:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Swallowed", exc_type.__name__)
        return True

with Calm():
    raise ValueError("bad number")
print("The program carries on.")

A timing manager shape

A timer records a start moment on entry. On exit it stores the elapsed time and prints only a label.

import time

class Timer:
    def __enter__(self):
        self.start = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.elapsed = time.perf_counter() - self.start
        print("Timer stopped.")

with Timer() as timer:
    total = sum(range(100))
print(total)

Example

class Tag:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(f"<{self.name}>")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print(f"</{self.name}>")
        return False

with Tag("section"):
    print("Hello inside")

try:
    with Tag("warning"):
        raise ValueError("oops")
except ValueError:
    print("Caught outside")

Key points

  • Protocol: a set of method names an object must provide to work a certain way
  • __enter__: the method that runs when the block starts
  • __exit__: the method that runs when the block ends, for any reason
  • Traceback: the record Python keeps of where an error happened
  • On an error, __exit__ receives the error type, the error value, and a traceback
  • Use return True in __exit__ to stop an error from spreading
  • A with block can then use objects from the class.

In this example, Tag stores the label name at creation. The __enter__ method prints the opening label. The first block prints its line inside the labels. The __exit__ method prints the closing label. The second block raises a ValueError. The closing label still prints during the exit. The method returns False, so the error spreads. The except block then catches it outside.

Note

Most __exit__ methods return None or False.

That lets errors spread as usual after cleanup runs.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Python Playground

Exercise

A block raises ValueError inside your custom manager. What does __exit__ receive?