CodeOath
← All posts
Python70 min total · 18 parts

Python Fundamentals for Interviews: Data Structures, Comprehensions, and Gotchas

Contents — Part 11 of 18: Context Managers and with
Part 11 of 18 · ~1 min

Context Managers and with

with open("data.txt") as f:
    contents = f.read()
# file is guaranteed to be closed here, even if read() raised an exception

with guarantees cleanup runs — this is the same problem try/finally solves, but for the very common case of "acquire a resource, use it, release it no matter what." Writing a custom one only needs two dunder methods:

class Timer:
    def __enter__(self):
        self.start = time.perf_counter()
        return self  # becomes the "as x" value

    def __exit__(self, exc_type, exc_val, exc_tb):
        elapsed = time.perf_counter() - self.start
        print(f"Elapsed: {elapsed:.4f}s")
        return False  # False (or None) means: don't suppress the exception, if any

with Timer():
    time.sleep(0.2)
# "Elapsed: 0.2001s"

__exit__ receives the exception info (if the with block raised one) as its three arguments — returning True from __exit__ swallows that exception silently, which is occasionally intentional but is also a common source of bugs when done by accident. The equivalent, shorter way to write the same thing uses contextlib.contextmanager with a single yield:

from contextlib import contextmanager

@contextmanager
def timer():
    start = time.perf_counter()
    yield                              # code inside "with timer():" runs here
    print(f"Elapsed: {time.perf_counter() - start:.4f}s")

Everything before yield is __enter__; everything after is __exit__.