Advertisement
🚀 Advanced Python

Python Context Managers – with Statement and __enter__/__exit__

Context managers handle setup and teardown of resources automatically — files, database connections, locks, network sockets. The with statement guarantees that cleanup code always runs, even if an exception occurs. This eliminates a whole class of resource-leak bugs.

⏱️ 20 min read🎯 Advanced📅 Updated 2026

The with Statement

with ensures the resource is properly closed/released regardless of what happens.

Python
# Without context manager (bad — file may not close on error)
f = open("data.txt", "w")
f.write("Hello")  # If this crashes, f.close() never runs!
f.close()

# With context manager (correct)
with open("data.txt", "w") as f:
    f.write("Hello")  # __exit__ calls f.close() automatically
# File is closed here — always
💡
Tip

Always use with for file operations. It is safer and more readable than manual open/close.

The Context Manager Protocol

Any class with __enter__ and __exit__ works as a context manager.

Python
class Timer:
    import time as _time

    def __enter__(self):
        import time
        self.start = time.time()
        return self   # Bound to "as" variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        elapsed = time.time() - self.start
        print(f"Elapsed: {elapsed:.4f}s")
        return False  # Don't suppress exceptions

with Timer() as t:
    total = sum(range(1_000_000))
▶ Output
Elapsed: 0.0412s

contextlib.contextmanager – Generator Approach

Use @contextmanager to write context managers as generator functions — less boilerplate.

Python
from contextlib import contextmanager

@contextmanager
def managed_resource(name):
    print(f"Opening {name}")
    try:
        yield name      # Everything before yield = __enter__
    finally:
        print(f"Closing {name}")  # Always runs = __exit__

with managed_resource("database") as r:
    print(f"Working with {r}")
▶ Output
Opening database Working with database Closing database
Advertisement

Multiple Context Managers

Combine multiple managers in one with statement.

Python
# Open two files simultaneously
with open("input.txt", "r") as src, open("output.txt", "w") as dst:
    for line in src:
        dst.write(line.upper())

# Both files auto-closed after block

contextlib.suppress – Ignore Specific Exceptions

Cleanly suppress known safe exceptions without try/except.

Python
from contextlib import suppress
import os

# Instead of:
try:
    os.remove("temp.txt")
except FileNotFoundError:
    pass

# Use suppress:
with suppress(FileNotFoundError):
    os.remove("temp.txt")

Why with Guarantees Cleanup — Even When Code Crashes

A context manager runs setup on entry and teardown on exit no matter what — normal finish, early return, or an exception all trigger the cleanup. That's why with open(...) is safer than a bare open(): the file closes even if the code inside blows up.

# Without: a crash before close() leaks the file handle
f = open("data.txt")
process(f)          # if this raises, f is never closed
f.close()

# With: __exit__ runs on the way out, exception or not
with open("data.txt") as f:
    process(f)      # file guaranteed closed afterward

Build your own two ways

# 1) class with __enter__ / __exit__
class Timer:
    def __enter__(self): self.t = time.time(); return self
    def __exit__(self, *exc): print(time.time() - self.t)

# 2) the shortcut: contextlib
from contextlib import contextmanager
@contextmanager
def timer():
    t = time.time()
    yield                 # everything before = enter, after = exit
    print(time.time() - t)

__exit__ receives the exception info; return True from it to swallow the error, or None/False to let it propagate. Stack multiple: with A() as a, B() as b: closes them in reverse order.

🏋️ Practical Exercise

Work with the with statement:

  1. Open a file using with open(...) and confirm it closes automatically.
  2. Write a class implementing __enter__ and __exit__ that prints on entry and exit.
  3. Create the same context manager using @contextlib.contextmanager and yield.
  4. Use contextlib.suppress to ignore a specific exception.

🔥 Challenge Exercise

Build a Timer context manager that records how long the code inside its with block takes and prints the elapsed time on exit — implement it both as a class (__enter__/__exit__) and as a @contextmanager generator. Make sure the timer still reports correctly even if the block raises an exception, then use multiple context managers in one with line.

📋 Summary

  • A context manager guarantees setup and cleanup around a block, even if an exception occurs.
  • The with statement calls __enter__ on entry and __exit__ on exit.
  • Implement the protocol with __enter__ and __exit__ methods on a class.
  • The @contextlib.contextmanager decorator turns a generator (with one yield) into a context manager.
  • Returning True from __exit__ suppresses the exception; returning falsy lets it propagate.
  • You can manage several resources in one with by separating them with commas.

Interview Questions on Context Managers

  • What is a context manager and what problem does it solve?
  • How does the with statement work under the hood?
  • What methods make up the context manager protocol?
  • How do you create a context manager using contextlib.contextmanager?
  • What does the return value of __exit__ control?
  • How do you manage multiple resources in one with block?
  • What does contextlib.suppress do?

FAQ

What is the main benefit of a context manager? +

It guarantees cleanup. Resources like files, database connections, and locks are released automatically when the with block ends, even if an exception is raised — eliminating the need for manual try/finally.

What is the easiest way to write a context manager? +

Use the @contextlib.contextmanager decorator on a generator function: put setup code before a single yield and cleanup code after it. This is much shorter than writing a class with __enter__/__exit__.

What does returning True from __exit__ do? +

It tells Python the exception raised inside the block has been handled and should be suppressed. Returning a falsy value (the default) lets the exception propagate normally.

Can I open multiple files in one with? +

Yes: with open("a") as a, open("b") as b: manages both, closing each when the block ends. For a dynamic number of resources, use contextlib.ExitStack.