Context Managers

Advanced · 18 min read · ▶ live playground · ✦ checkpoint

Some bugs are not about the main work. They are about the thing you forgot after the work: close the file, restore the setting, print the final log. A context manager is an object that sets something up before a block and cleans it up after the block, so that "before and after" code stays attached to the work it protects.

Revisit the with statement

A resource is something your program borrows and should give back or restore, such as an open file, a temporary setting, or a connection. The with statement is Python syntax that creates a managed block around a resource.

You met with open(...) in file handling. Reuse that notebook picture: opening a file is checking out a notebook, and the with block is the time you are allowed to write in it. When the indented block ends, Python returns the notebook for you.

from pathlib import Path
 
 
path = Path("practice_notes.txt")
 
with open(path, "w", encoding="utf-8") as note:
    note.write("Mina: 20 minutes\n")
    print("inside block:", note.closed)
 
print("after block:", note.closed)
 
with open(path, "r", encoding="utf-8") as note:
    print(note.read().strip())
 
path.unlink()  # delete this temporary file

inside block: False
after block: True
Mina: 20 minutes

Inside the block, the file is open. After the block, the same file object is closed. The name note still exists, but the resource behind it has been cleaned up.

This matters more when code fails. A with block still gives the context manager a chance to clean up as the block exits. That is the habit: put the risky borrowed thing inside a managed block, not loose in the middle of a long function.

Write a context manager class

The context-manager protocol is the pair of dunder methods Python calls for a with block. __enter__ is the method that runs at the start of the block and returns the value after as. __exit__ is the method that runs when the block ends and receives exception details if something went wrong.

Think of __enter__ as checking out the notebook and __exit__ as returning it, even if your notes got messy.

class PracticeSession:
    def __init__(self, player):
        self.player = player
        self.events = []
 
    def __enter__(self):
        print(f"{self.player} starts practice")
        return self
 
    def add(self, event):
        self.events.append(event)
 
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            print("practice ended cleanly")
        else:
            print(f"practice hit {exc_type.__name__}")
 
        for event in self.events:
            print(f"- {event}")
 
        return False
 
 
with PracticeSession("Noor") as session:
    session.add("solved one puzzle")
    session.add("earned 30 points")

Noor starts practice
practice ended cleanly
- solved one puzzle
- earned 30 points

__enter__ returns self, so session inside the block is the PracticeSession object. __exit__ receives three values about an exception: its type, its value, and its traceback. When nothing failed, all three are None.

The return False line means "do not hide exceptions." Suppressing an exception means stopping it from continuing after the block. Most context managers should not suppress by default because hidden failures are hard to debug.

Use contextlib and @contextmanager

contextlib is a standard-library module with helpers for building context managers. @contextmanager is a decorator from contextlib that turns a generator function into a context manager.

This works because a generator can pause at yield. Code before yield is setup. The yielded value becomes the value after as. After the managed block finishes, Python resumes the generator at yield. If the block raises an exception, Python throws that exception back at the yield line, so must-run cleanup belongs in finally.

from contextlib import contextmanager
 
 
@contextmanager
def score_boost(player, amount):
    print(f"prepare boost for {player}")
    try:
        yield amount
    finally:
        print(f"clear boost for {player}")
 
 
with score_boost("Ravi", 5) as boost:
    score = 40 + boost
    print(f"score: {score}")

prepare boost for Ravi
score: 45
clear boost for Ravi

The finally block is the cleanup spot. It runs when the managed block exits normally, and it also runs when the managed block exits because of an exception.

Use the class form when the context manager needs several methods or a lot of state. Use @contextmanager when the shape is mostly "do setup, yield one thing, do cleanup."

Try it - manage a tiny file

Run this, then change the player line to write two players. The file is created, used, read, and removed in one run, because the browser playground does not keep files across page reloads.

python — playgroundlive
⌘/Ctrl + Enter to run

Restore state after work

Resource management is designing code so borrowed things are released or restored after use. Files are the classic example, but context managers are just as useful for temporary program state.

Here, one small context manager changes a scoreboard setting for a block, then puts the old value back.

from contextlib import contextmanager
 
 
settings = {"mood": "quiet"}
 
 
@contextmanager
def temporary_mood(mood):
    old_mood = settings["mood"]
    settings["mood"] = mood
    try:
        yield
    finally:
        settings["mood"] = old_mood
 
 
print("before:", settings["mood"])
 
with temporary_mood("focused"):
    print("inside:", settings["mood"])
 
print("after:", settings["mood"])

before: quiet
inside: focused
after: quiet

That pattern appears all over real Python code:

  • Open something, then close it.
  • Change a setting, then restore it.
  • Start a log section, then finish it.
  • Acquire something shared, then release it.

The point is not shorter code. The point is safer code: the cleanup is part of the shape, not a note you hope you remember later.

Checkpoint

Answer all three to mark this lesson complete

Context managers make setup and cleanup visible without making the main action noisy. Next, functional programming gives you another way to shape transformations: small functions, lazy tools, and clear data flow.

+50 XP on completion