Context Managers

Reviewed & published by Brayan K

Context managers are one of Python's most elegant and powerful tools — used everywhere from file handling, database transactions, network requests, locks, concurrency, and resource safety.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

What You'll Learn

If you've ever written with open("file.txt") as f: …you've already used a context manager.

This lesson teaches you:

✔ How context managers work

✔ How to create your own using classes

✔ How to create them using generator functions (contextlib.contextmanager)

✔ How real systems use them for safe resource handling

✔ How to build production-grade context managers

✔ How to combine context managers with decorators & advanced design patterns

🔥 1. What Is a Context Manager?

A context manager controls a setup phase and a cleanup phase.

PhaseWhat HappensHotel Analogy
__enter__Resource is acquired/preparedCheck in, get room key
with blockYour code runs using the resourceEnjoy your stay
__exit__Resource is cleaned up/releasedCheckout, room cleaned
with something as value:
    # do work

The moment execution enters the with block, the context manager prepares a resource. When the block exits — even if an error occurs — the resource is cleaned up.

⚙️ 2. How Python Processes the with Statement

with manager as x:
    work()
manager_obj = manager.__enter__()
try:
    x = manager_obj
    work()
finally:
    manager.__exit__(*sys.exc_info())

So a context manager must define:

✔ __exit__(self, exc_type, exc, tb)

🧠 3. Creating Your Own Context Manager (Class-Based)

Let's build a simple context manager that logs entering/exiting:

class Logger:
    def __enter__(self):
        print("Entering block...")
        return "Ready!"

    def __exit__(self, exc_type, exc, tb):
        print("Exiting block...")
        if exc:
            print("Error occurred:", exc)
        return False   # do not suppress errors
with Logger() as msg:
    print(msg)
Entering block...
Ready!
Exiting block...

🧩 4. A Real Project Example — Timing Block Execution

import time

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

    def __exit__(self, exc_type, exc, tb):
        self.end = time.time()
        print(f"⏱ Elapsed: {self.end - self.start:.4f}s")
with Timer():
    sum([x for x in range(5000000)])

🧨 5. A More Advanced Example — Database Connections

This pattern exists in ORMs like SQLAlchemy & Django:

class Transaction:
    def __enter__(self):
        print("Start transaction")
        return self

    def __exit__(self, exc_type, exc, tb):
        if exc_type:
            print("Rollback")
        else:
            print("Commit")
with Transaction():
    print("Updating user...")

🧊 6. Using contextlib.contextmanager (Generator Context Managers)

For simpler cases, Python provides a shortcut.

from contextlib import contextmanager

@contextmanager
def open_file(path):
    f = open(path)
    try:
        yield f        # __enter__()
    finally:
        f.close()      # __exit__()
with open_file("data.txt") as file:
    print(file.read())

This is extremely common in:

🔧 7. Building a Temporary Directory Context Manager

import tempfile
import shutil
from contextlib import contextmanager

@contextmanager
def tempdir():
    path = tempfile.mkdtemp()
    try:
        yield path
    finally:
        shutil.rmtree(path)
with tempdir() as path:
    print("Working in", path)

🔐 8. Context Managers for Locking & Thread Safety

Example using threading lock:

from threading import Lock

lock = Lock()

with lock:
    print("Critical section")

# ✅ Expected output:
# Critical section

Locks are native context managers. This pattern protects shared memory in concurrent programs.

🧬 9. Context Managers Used With Decorators (Advanced Pattern)

You can combine context managers with decorators to automatically wrap function execution:

from contextlib import contextmanager
from functools import wraps
import time

@contextmanager
def timer_block():
    start = time.time()
    yield
    print(f"Elapsed: {time.time() - start:.4f}s")

def timed(fn):
    @wraps(fn)
    def wrapper(*a, **k):
        with timer_block():
            return fn(*a, **k)
    return wrapper

@timed
def process():
    time.sleep(0.2)
process()

🧪 10. Suppressing Errors (But Carefully!)

If __exit__ returns True, exceptions are swallowed.

class IgnoreErrors:
    def __exit__(self, exc_type, exc, tb):
        return True
with IgnoreErrors():
    1 / 0     # error suppressed

Be careful — this is useful ONLY for:

Never suppress runtime errors silently in real systems.

📚 11. Nested Context Managers

with A() as a:
    with B() as b:
        ...
with A() as a, B() as b:
    ...

This is cleaner and avoids deep nesting.

⚡ 12. Combining Multiple Context Managers Dynamically

Useful for frameworks or plugin systems:

from contextlib import ExitStack

with ExitStack() as stack:
    files = [stack.enter_context(open(f)) for f in file_list]
    # all files now managed safely

This is extremely powerful for:

🏗 13. Context Managers in Real Frameworks

async def db():
    async with engine.begin() as conn:
        yield conn

✔ PyTorch CUDA device switching

with torch.no_grad():
    ...
with transaction.atomic():
    ...
with np.printoptions(suppress=True):
    ...

Context managers are everywhere.

🧱 14. Build a Production-Ready Context Manager

Let's create a file-write-safe manager:

class SafeWriter:
    def __init__(self, path):
        self.path = path

    def __enter__(self):
        self.temp = open(self.path + ".tmp", "w")
        return self.temp

    def __exit__(self, exc_type, exc, tb):
        self.temp.close()
        if exc_type:
            # remove corrupted temp file
            import os
            os.remove(self.path + ".tmp")
            return False
        
        import os
        os.replace(self.path + ".tmp", self.path)

This is similar to how:

🎓 15. Mini Project — Build Your Own Resource Manager

Create a context manager that:

with Monitor("log.txt", suppress=False):
    run_expensive_task()
# 🎯 YOUR TURN — replace each ___ using the hint beside it.

from contextlib import contextmanager

class Section:
    def __init__(self, title):
        self.title = title

    # 1) Runs on the way in. Whatever it RETURNS is what

This gives you portfolio-ready experience.

🎉 Conclusion

You now understand context managers:

✔ How the with statement works internally

✔ How to build class-based managers

✔ How to build generator-based managers

✔ How to combine decorators and context managers

✔ How to suppress or inspect exceptions

✔ How they power major frameworks

✔ How to create production-grade resource managers

📋 Quick Reference — Context Managers

SyntaxWhat it does
with open(f) as fh:Open file safely, auto-closes on exit
__enter__(self)Called when entering the with block
__exit__(self, *args)Called on exit, even if an error occurs
@contextmanagerBuild a manager from a generator function
contextlib.suppress(E)Ignore a specific exception type

🏆 Lesson Complete!

You can now build and use context managers for any resource lifecycle — files, databases, locks, timers, and more.

Practice quiz

Which two phases does a context manager control?

  • Compile and run
  • Read and write
  • A setup phase and a cleanup phase
  • Import and export

Answer: A setup phase and a cleanup phase. A context manager manages a setup phase (on entry) and a cleanup phase (on exit).

Which two methods must a class-based context manager define?

  • __enter__ and __exit__
  • __init__ and __del__
  • __aenter__ and __aexit__
  • open and close

Answer: __enter__ and __exit__. Class-based context managers implement __enter__ (entry) and __exit__ (cleanup).

The value returned by __enter__ becomes what?

  • The exception type
  • The return value of the whole block
  • Always None
  • The variable bound by 'as' in the with statement

Answer: The variable bound by 'as' in the with statement. __enter__'s return value is assigned to the 'as' variable in the with statement.

Does __exit__ run if an exception is raised inside the with block?

  • No, exceptions skip cleanup
  • Yes, __exit__ always runs (it's like a finally)
  • Only if you catch the exception first
  • Only for KeyboardInterrupt

Answer: Yes, __exit__ always runs (it's like a finally). __exit__ runs whether the block finishes normally or raises — like a finally clause.

How does __exit__ suppress (swallow) an exception that occurred in the block?

  • By returning True
  • By raising it again
  • By returning None
  • By calling sys.exit()

Answer: By returning True. Returning True from __exit__ tells Python to suppress the exception.

In a @contextmanager generator, where does the cleanup code go?

  • Before the yield
  • In a separate function
  • After the yield
  • In __init__

Answer: After the yield. Setup runs before yield; the cleanup code runs after the yield.

How many times must a @contextmanager generator yield?

  • Zero times
  • Exactly once
  • Twice
  • As many as you like

Answer: Exactly once. It must yield exactly once; zero or multiple yields raise a RuntimeError.

What is the modern way to use two context managers in a single with statement?

  • with A() as a: with B() as b:
  • with A() and B():
  • with [A(), B()]:
  • with A() as a, B() as b:

Answer: with A() as a, B() as b:. with A() as a, B() as b: manages both on one line, avoiding deep nesting.

What does contextlib.ExitStack let you do?

  • Run code faster
  • Manage a dynamic, runtime-determined number of context managers
  • Suppress all exceptions
  • Replace try/except

Answer: Manage a dynamic, runtime-determined number of context managers. ExitStack enters a variable number of context managers and exits them all (in reverse) at block end.

Are threading locks usable directly as context managers with 'with lock:'?

  • No, you must wrap them
  • Only async locks support it
  • Yes, locks are native context managers
  • Only with ExitStack

Answer: Yes, locks are native context managers. Locks implement the context manager protocol, so 'with lock:' acquires and releases automatically.

Continue this course