Lesson 10 / 11

Decorators and Context Managers

Decorators

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

@timer
def slow_task():
    time.sleep(1)

slow_task()

A decorator wraps a function to add behavior around it — logging, timing, permission checks, caching — without changing the function’s own code. The @timer syntax above is shorthand for slow_task = timer(slow_task); the decorator receives the original function, and returns a new function (wrapper) that does extra work before and after calling it.

*args and **kwargs

Notice wrapper(*args, **kwargs) in the example above — this lets the decorator work on any function, regardless of how many positional or keyword arguments it takes. *args collects any number of positional arguments into a tuple; **kwargs collects any number of keyword arguments into a dictionary.

Decorators from the standard library

from functools import lru_cache

@lru_cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

@lru_cache is a real, widely-used decorator from Python’s standard library — it automatically remembers previous results, so calling fibonacci(30) a second time returns instantly instead of recalculating from scratch. This is the exact same mechanism as the custom @timer decorator above, just already written for you.

Writing your own context manager

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

    def __exit__(self, *args):
        print(f"Elapsed: {time.time() - self.start:.4f}s")

with Timer():
    time.sleep(1)

__enter__ and __exit__ are what make the with statement from the File Handling lesson work — you’re now able to build your own resource-managing objects, not just use built-in ones like open(). __exit__ runs even if an exception is raised inside the with block, which is exactly why with open(...) reliably closes a file even when something goes wrong while reading it.