Let’s say you have a custom context manager. Without loss of generality, consider:

class MyGreeter(object):

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

    def __enter__(self):
        print(self.name, 'saying hello.')
        return len(self.name)

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.name, 'saying goodbye.')
        return False


with MyGreeter('Mori') as thing:
    print(thing)

# Output:
# Mori saying hello.
# 4
# Mori saying goodbye.

This is fine, but you might want to extend the functionality and depend on another context manager. Let’s say, in the above example, we wanted to print the message to a temporary file instead of to the console. Typical usage of NamedTemporaryFile would look like this:

import tempfile

with tempfile.NamedTemporaryFile() as f:
    f.write('...')

But now we’re in trouble. We’d like f to be accessible in the __enter__ and __exit__ methods. But we can’t have a reusable class defined inside of a with construct.

So the question is, how do we compose these two context managers while still being mindful of failure semantics?

Solution 1: Invoke context manager methods directly

Something like this would work:

import tempfile

class MyGreeter(object):

    def __init__(self, name):
        self.name = name
        self.tempfile_manager = tempfile.NamedTemporaryFile(
            mode='w', encoding='utf-8')

    def __enter__(self):
        self.tempfile = self.tempfile_manager.__enter__()
        self.tempfile.write('{} saying hello'.format(self.name))
        return self.tempfile  # or whatever

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.tempfile.write('{} saying goodbye'.format(self.name))
        finally:
            return self.tempfile_manager.__exit__(exc_type, exc_val, exc_tb)
            
with MyGreeter('Bob') as thing:
    pass

Recall that, according to the specification for ContextManagers, the three parameters to __exit__ identify a currently handled exception. If no exception is currently being handled, all three values are None. Furthermore, __exit__ returns True if a currently handled exception should be re-thrown, or False if it should be swallowed.

This enables ContextManagers to ease cognitive load of their users – if certain exceptions are recoverable, the author can do the recovery logic inside the CM itself.

Our simple Greeter has no special semantics for exception handling, so we defer to our inner CM tempfile_manager. If an exception is currently being handled, maybe NamedTemporaryFile will want to do something special with it.

Beware though: When composing CMs like this, you could in theory swallow exceptions that someone up the stack expected to see for their API to recover properly. So if you catch any of these exceptions, you should probably re-raise.

Solution 2: Use the contextlib decorator

If you have no exception handling semantics, you can use something much more concise.

import contextlib
import tempfile

@contextlib.contextmanager
def my_greeter(name):
    with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8') as temp:
        temp.write('{} saying hello\n'.format(name))
        try:
            yield name
        finally:
            temp.write('{} saying goodbye\n'.format(name))

The cool thing is, since generators pass exceptions through to the consumer, NamedTemporaryFile gets to see the exceptions it was counting on seeing.