Decorators
Function and class decorators, closures, and metadata preservation.
8 questions
JuniorTheoryVery commonWhat is a decorator, and what does @dec desugar to?
What is a decorator, and what does @dec desugar to?
A decorator is a callable that takes a function and returns another (usually wrapping) function, extending behavior without editing the original. @dec above def f is just sugar for f = dec(f), so f is rebound to whatever dec returns.
Common mistakes
- ✗Thinking
@deccallsfimmediately instead of rebindingftodec(f) - ✗Assuming a decorator cannot return a brand-new function that replaces the original
- ✗Forgetting that the returned wrapper, not the original, is what later calls invoke
Follow-up questions
- →What can serve as a decorator besides a plain
deffunction? - →When exactly does the decorator run — at definition or at call time?
JuniorTheoryCommonWhat can be a decorator, and what can it be applied to?
What can be a decorator, and what can it be applied to?
Any callable can be a decorator: a function, a lambda, a class, or an instance defining __call__. And it can decorate functions, methods, or classes — anything the @ syntax sits above. The only requirement is a callable target.
Common mistakes
- ✗Believing only
deffunctions qualify, forgetting classes and__call__instances - ✗Assuming decorators apply only to functions and cannot decorate a class
- ✗Forgetting that a
lambdais itself a valid callable decorator
Follow-up questions
- →How does an instance become callable so it can act as a decorator?
- →What does a class decorator return, and what replaces the class name?
MiddleTheoryCommonWhat is the difference between @foo and @foo()?
What is the difference between @foo and @foo()?
@foo applies foo directly to the function: f = foo(f). @foo() first CALLS foo() — here foo is a decorator factory returning the actual decorator, which is then applied: f = foo()(f). The factory call lets you parametrize the decorator.
Common mistakes
- ✗Treating
@fooand@foo()as interchangeable when only one fits a given decorator - ✗Thinking
@foo()calls the decoratedfrather than calling the factoryfoo - ✗Forgetting that
@foo()needsfooto return a decorator, not a wrapper directly
Follow-up questions
- →How many nested function levels does a parametrized decorator factory need?
- →What does
foo()return so that the result is itself a valid decorator?
MiddleTheoryCommonWhy apply functools.wraps to a wrapper function?
Why apply functools.wraps to a wrapper function?
Without it the wrapper replaces the original, so __name__, __doc__, and __qualname__ become the wrapper's — breaking introspection, help, and tracebacks. @wraps(func) copies that metadata over and sets __wrapped__ to the original.
Common mistakes
- ✗Omitting
@wraps(func)and then puzzling over a wrapper-named function in tracebacks - ✗Thinking
wrapsaffects runtime speed rather than only metadata - ✗Believing
wrapsis required for the decorator to function rather than for introspection
Follow-up questions
- →What does the
__wrapped__attribute thatwrapssets let you recover? - →Which dunder attributes does
wrapscopy by default versus update?
MiddleCodeOccasionalDecorator that repeats a call N times and reports total time
Decorator that repeats a call N times and reports total time
Use three nested levels: repeat(reps) captures the count and returns a decorator; that decorator takes func and returns a @wraps(func) wrapper. The wrapper records time.perf_counter(), runs func reps times (keeping the last result), prints the elapsed total, and returns that result. The argument layer is what @repeat(3) requires; @wraps preserves the name and docstring.
Common mistakes
- ✗Using two levels and failing to accept the
repsargument cleanly - ✗Resetting the timer inside the loop instead of timing the whole run
- ✗Omitting
@wraps, losing the function's name and docstring
Follow-up questions
- →Why does taking a decorator argument require a third nesting level?
- →What breaks for callers if you omit
@wrapson the wrapper?
MiddleCodeOccasionalStacked decorators add_tag('h1') over add_div — implement and order
Stacked decorators add_tag('h1') over add_div — implement and order
add_div wraps func's result in <div>...</div>. add_tag(tag) is a factory: it takes the tag, returns a decorator that wraps the result in <tag>...</tag>. Stacked decorators apply bottom-up — @add_div runs first, then @add_tag('h1') wraps that — so the innermost markup is the div and the outer is the h1, giving <h1><div>...</div></h1>. Use @wraps on each wrapper.
Common mistakes
- ✗Reversing the application order, getting
<div><h1>...</h1></div> - ✗Writing
add_tagwithout the extra factory layer for its argument - ✗Forgetting
@wraps, losing the decorated function's metadata
Follow-up questions
- →Why does the decorator nearest the function (bottom) take effect first?
- →How could you define
add_divasadd_tag('div')to avoid duplication?
MiddleCodeOccasionalWrite a decorator that times a function
Write a decorator that times a function
Return a closure that records time.perf_counter() before and after the call, forwards *args, **kwargs, and returns the result. Apply @functools.wraps(func) to the wrapper so it keeps the original name and docstring — without wraps, the decorated function loses that metadata.
Common mistakes
- ✗Returning the elapsed time instead of the function's result
- ✗Dropping
*args/**kwargs, so the wrapper can't forward arguments - ✗Omitting
functools.wraps, losing the function's name and docstring
Follow-up questions
- →What exactly does
functools.wrapscopy onto the wrapper? - →How would the decorator change if it had to accept its own argument, like a label?
SeniorTheoryOccasionalHow do you write a decorator that takes an argument and preserves metadata?
How do you write a decorator that takes an argument and preserves metadata?
Use three nested levels: an outer factory taking the argument, returning a decorator taking func, returning a @wraps(func)-decorated inner wrapper that uses the captured argument and calls func. Apply it as @factory(arg).
Common mistakes
- ✗Using only two levels and conflating the configuration argument with
func - ✗Placing
@wraps(func)on the factory or decorator instead of the inner wrapper - ✗Trying to read the configuration argument from the wrapper's runtime
*args
Follow-up questions
- →How can one decorator support both
@factoryand@factory(arg)call forms? - →Where is the configuration argument captured — in which closure scope?