Skip to content
Function Decorators
step 1/7

Reading — step 1 of 7

Learn

~3 min readDecorators

You've built a web app with fifty endpoint functions, and your boss says: every endpoint needs to log who called it AND how long it took. Adding 5 lines to each of 50 functions = 250 lines of repeated code, plus a guarantee that the next person writing endpoint #51 forgets to add it.

Decorators solve this. A decorator is a function that takes another function, wraps it with additional behavior, and returns the enhanced version — without touching the original function's own code. Built on closures (function returning a function) plus a tiny bit of syntax sugar.

A decorator from scratch

python

What the @shout line does:

python

The @ is just syntactic sugar for "replace greet with shout(greet)." After that line, the name greet points at wrapper (which calls the original inside).

Anatomy of a typical decorator

python

*args, **kwargs make the wrapper work for any function signature.

A useful real example: timing

python

functools.wraps — preserve metadata

Without @wraps(func), the wrapped function loses its name, docstring, and module:

python

Adding @wraps(func) to your wrapper definition copies the metadata from the original to the wrapper. Now greet.__name__ is "greet" and the docstring is preserved. Always use functools.wraps.

Stacking decorators

python

Reads BOTTOM-UP: greet = timer(shout(greet)). The closest decorator to the function is applied first.

Decorators with arguments

For decorators that take arguments themselves, you need ONE more level of nesting:

python

The call repeat(3) returns the actual decorator, which then wraps hi.

Built-in decorators you've seen

  • @staticmethod, @classmethod (in classes)
  • @property — turns a method into a read-only attribute
  • @functools.lru_cache — automatic memoization (caching) of return values
  • @dataclasses.dataclass — auto-generates __init__, __repr__, __eq__

Common mistakes

  • Forgetting return result — the wrapped function silently returns None.
  • Forgetting @wraps(func) — debugging tools and help() show the wrong info.
  • Decorator argument confusion@timer (no args) needs ONE level of nesting; @retry(3) (with args) needs TWO levels.
  • Mutating shared state in the decorator without locks — fine for single-threaded code, dangerous in async/threaded.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…