Decorators

A decorator is a callable that takes a function and returns a new function, letting you wrap behaviour around code without editing it.

Advanced50mInterview Production

Learning objectives

  • Explain what @decorator desugars to
  • Write decorators with and without arguments
  • Preserve metadata with functools.wraps
  • Build production decorators: timing, retry, cache, auth

Prerequisites

FunctionsClosuresFirst-class functions

Simple explanation

What it is. A decorator is a function that wraps another function (or class) to add behaviour before, after, or around the original call. `@log` above a function is syntactic sugar for `func = log(func)`.

Why it exists. Cross-cutting concerns — logging, timing, caching, auth, retries — would otherwise be copy-pasted into every function. Decorators let you write that logic once and apply it declaratively.

The problem it solves. They keep the core function focused on its job while behaviour like 'retry three times on failure' or 'cache the result' is layered on top, readably and reusably.

How it works. Because functions are first-class objects, a decorator receives the function, defines an inner wrapper (a closure) that calls it, and returns the wrapper. Python rebinds the original name to the wrapper. `@wraps` copies the original's __name__, __doc__ and signature onto the wrapper.

Analogy: Like gift-wrapping: the present (your function) is unchanged inside, but the wrapping adds something on the outside — and you can stack several layers of wrapping.

Common misconceptions

  • "Decorators run every time the function is called." — The decorator itself runs once at definition; only the wrapper runs per call.
  • "Stacked decorators apply top-to-bottom at call time." — They are applied bottom-up at definition; the top one wraps outermost.
  • "Decorators change the original function." — They replace the name with a new function; the original is still callable inside the wrapper.

Common alternatives

  • Context managers for scoped setup/teardown
  • Middleware (in web frameworks) for request-level concerns
  • Explicit higher-order function calls when one-off

Interactive visualization

Step 1 / 6
1def log_calls(func):
2 def wrapper(*args):
3 print("->", func.__name__)
4 return func(*args)
5 return wrapper
6
7@log_calls
8def add(a, b):
9 return a + b
10
11add(2, 3)

Call stack

<module>

log_calls

func = → original add

Heap / objects

origfunction

add (original)

refcount: 1

@log_calls runs at definition

`@log_calls` above add means `add = log_calls(add)`. It runs once, now — not on every call.

Code examples

BeginnerBasic decorator
python
Loading editor…

Behind the scenes

  • `@dec` is applied at function-definition time, immediately after the `def` executes.
  • The wrapper is a closure capturing `func`; each decorated function gets its own wrapper.
  • `functools.wraps` copies __module__, __name__, __qualname__, __doc__ and __dict__, and sets __wrapped__ so the original is still reachable.
  • Class-based decorators implement __call__; parameterised decorators add an extra function layer.

Interview insights

Key definitions

  • · Decorator: a callable taking a function/class and returning a replacement, usually adding behaviour.
What does @decorator actually do?

Concise answer: It rebinds the name: `func = decorator(func)` right after the def runs.

Detailed answer: The decorator receives the just-defined function, typically returns a wrapper closure, and Python binds the original name to that return value. Parameterised decorators add one more layer so the outer call captures configuration and returns the real decorator.

Common mistake: Believing the decorator body runs on every call rather than once.

Follow-ups:

  • · Why use functools.wraps?
  • · How do stacked decorators compose?

Knowledge check

What's the output?Question 1 of 2

With @a @b def f(): ..., which decorator wraps outermost?

Multiple choiceQuestion 2 of 2

What does functools.wraps preserve?

Score: 0/2