logo
35

Decorators

⏱️ 35 min

Decorators: Enhance Functions Without Touching Their Source

What might confuse you right now

"Why not just edit the function body directly? Why wrap it?"

When you need to add the same capability (logging, auth, timing) to many functions, decorators eliminate duplicate code and enforce consistency.

One-line definition

A decorator is a higher-order function that takes a function and returns an enhanced version.

Real-life analogy

Like putting a case and screen protector on your phone: the phone itself doesn't change, but it gains new protection.

Minimal runnable example

from functools import wraps

def logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] calling: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logger
def greet(name):
    return f"Hello, {name}"

print(greet("JR"))

Key concepts

  • @logger is equivalent to greet = logger(greet)
  • wrapper handles "enhanced behavior + calling the original function"
  • @wraps preserves the original function's metadata (name, docstring)

Quick quiz (5 min)

  1. Write a decorator that prints the function name.
  2. Modify it to also print the arguments.
  3. Implement @timer that outputs execution time in ms.

Quiz answer guidelines & grading criteria

  • Answer direction: working code that covers core conditions and edge inputs from the prompt.
  • Criterion 1 (Correctness): Main flow produces correct results, key branches execute.
  • Criterion 2 (Readability): Clear variable names, no excessive nesting.
  • Criterion 3 (Robustness): Basic protection against null values, type errors, or unexpected input.

Transfer task (homework)

Take two functions that both have duplicate logging code and refactor them into "one decorator + two pure business functions."

Acceptance criteria

You can independently:

  • Write a basic decorator
  • Properly handle *args, **kwargs
  • Explain how decorators replace the original function call chain

Common errors & debugging steps (beginner edition)

  • Can't understand the error: read the last line for the error type (e.g., TypeError, NameError), then trace back to the relevant code line.
  • Not sure about a variable's value: temporarily add print(variable, type(variable)) at key points to verify data matches expectations.
  • Code changes aren't taking effect: confirm the file is saved, you're running the right file, and your terminal environment (venv) is correct.

Common misconceptions

  • Misconception: Forgetting return wrapper.

  • Reality: Without the return, the replacement never happens.

  • Misconception: Not writing *args, **kwargs, which drops arguments.

  • Reality: A generic decorator must pass through all arguments.