๐ŸŽจFunction Decorators & @ SyntaxLESSON

Function Decorators & @ Syntax

A decorator is a function that takes another function and returns a modified version of it. Decorators let you add behavior to existing functions without modifying their source code โ€” a key principle of clean, composable design.

What Is a Decorator?

At the core, decorators are just functions that wrap other functions:

The @ Syntax (Syntactic Sugar)

The @decorator_name syntax is just a cleaner way to write the pattern above. These two are identical:

Python sees @my_decorator and immediately applies it: greet = my_decorator(greet).

The Wrapper Pattern

The standard pattern for a decorator that works with any function:

Using *args and **kwargs makes the wrapper transparent โ€” it passes through any arguments the original function accepts.

Practical Example: Timing

Practical Example: Logging

Preserving Metadata with functools.wraps

Without @wraps, decorators destroy the original function's identity:

@wraps(func) copies __name__, __qualname__, __doc__, __annotations__, and __module__ from the original to the wrapper. Always use it.

Stacking Decorators

Multiple decorators can be applied to the same function. They are applied bottom-up (innermost first):

The innermost decorator (@italic) is applied first, then @bold wraps the result.

Decorators with Parameters

To create a decorator that accepts arguments, you need an extra level of nesting โ€” a function that returns a decorator:

The repeat(3) call returns a decorator, and that decorator is applied to say_hello. This is called a decorator factory.

Class Decorators

A class can also act as a decorator if it defines __call__:

Class decorators are useful when you need to store state between calls.

Built-in Decorators

Python provides several built-in decorators for class methods:

@staticmethod

A method that doesn't receive self or cls. It's just a regular function namespaced inside a class.

@classmethod

Receives the class itself as the first argument (cls). Used for alternative constructors.

@property

Turns a method into a computed attribute (covered in depth in the Properties lesson):

Decorator Use Cases in the Real World

Decorators are everywhere in Python:

  • Flask/FastAPI routes: @app.get("/users") registers URL handlers
  • pytest fixtures: @pytest.fixture marks setup functions
  • dataclasses: @dataclass auto-generates __init__, __repr__, etc.
  • Django views: @login_required enforces authentication
  • Caching: @lru_cache and @cache memoize expensive computations
  • Retrying: @retry(times=3) auto-retries on failure

The decorator pattern is one of Python's most powerful and widely-used features.

Knowledge Check

When you stack decorators like `@A` above `@B` on a function, in what order are they applied?

What is the main purpose of using `@functools.wraps(func)` inside a decorator?

What does `@repeat(3)` represent if `repeat` is a decorator factory?