Chapter 16: Decorators & Context Managers

Learn how to extend function behavior with decorators and manage resources elegantly with custom context managers.

Download chapter16.py

Objectives

1. Closures & Inner Functions

Functions can be passed around and defined inside others:

def make_multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

times3 = make_multiplier(3)
print(times3(10))  # 30

2. Simple Decorators

Wrap a function to extend its behavior:

def debug(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with", args, kwargs)
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned", result)
        return result
    return wrapper

@debug
def add(a, b):
    return a + b

add(2, 5)

3. Parameterized Decorators

Decorators that accept their own arguments:

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(n):
                print(f"Run {i+1}")
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print("Hello", name)

greet("Alice")

4. Context Managers: __enter__ & __exit__

Create a class that manages a resource:

class Resource:
    def __enter__(self):
        print("Acquire resource")
        return self
    def __exit__(self, exc_type, exc, tb):
        print("Release resource")

with Resource() as r:
    print("Inside with-block")

5. Generator-Based Context Managers

Use @contextmanager to simplify:

from contextlib import contextmanager

@contextmanager
def open_file(path, mode):
    f = open(path, mode)
    try:
        yield f
    finally:
        f.close()

with open_file("data.txt", "w") as f:
    f.write("Hello")

Exercises

  1. Write a decorator to time function execution and print the duration.
  2. Create a decorator that caches a function’s results (memoization).
  3. Implement a context manager that measures and prints block execution time.
  4. Rewrite a file-opening class manager using @contextmanager.