Decorators

RoadmapsPython Backend

Overview

A decorator is a function that takes another function and extends its behavior without explicitly modifying it. The `@decorator` syntax is syntactic sugar for `func = decorator(func)`. Under the hood, a decorator returns a wrapper function (a closure, which is a function that retains access to its enclosing scope) that replaces the original function. It acts like gift wrapping a box: the original box is still inside, but it now has a shiny exterior.

Decorators allow you to cleanly extract reusable cross-cutting concerns—like logging, authentication, or timing—away from the core business logic of your functions.

Where used: FastAPI route definitions (@app.get), Flask routes (@app.route), Built-in class properties (@property)

Why learn this

Code walkthrough

def uppercase(func):
  def wrapper():
    original_result = func()
    return original_result.upper()
  return wrapper

@uppercase
def greet():
  return 'hello'

print(greet())

Focus: return original_result.upper()

Aha moment

def no_op(func):
  pass

@no_op
def greet():
  return 'hello'

print(greet())

Prediction: What does this code print?

Common guess: It prints 'hello', assuming the decorator does nothing.

It throws a TypeError. Since `no_op` doesn't explicitly return anything, it returns `None`. The decorator syntax does `greet = no_op(greet)`, so `greet` is now `None`.

Common mistakes

Glossary

syntactic sugar
Syntax in a programming language that makes things easier to read or to express, but doesn't add any new functionality. For example, `@my_decorator` is syntactic sugar for `func = my_decorator(func)`. Example: `@timer`
cross-cutting concerns
Aspects of a program that affect many different parts of the system, like logging or security, rather than just one specific feature. Example: `@log_calls`

Recall questions

Understanding checks

What will this code print?

<b>Hello</b>

The decorator `make_bold` wraps `get_text`. When `get_text()` is called, it actually calls `wrapper()`, which evaluates `func()` (returning 'Hello'), wraps it in `<b>` tags, and returns the result.

This decorator is meant to log the execution of a function, but calling `add(2, 3)` returns `None`. Why?

The `wrapper` function forgot to return the result of `func(*args, **kwargs)`.

In a decorator, the `wrapper` replaces the original function. If the original function calculates a value but the `wrapper` doesn't explicitly return it, the caller will implicitly receive `None`.

Practice tasks

Challenge

Write a timing decorator that accepts arguments

Create a decorator `timer` that measures the execution time of a function. It must accept any number of positional and keyword arguments (`*args`, `**kwargs`), run the original function, print the time taken in seconds (you can mock the time or use `import time`), and return the original function's result.

Continue learning

Previous: Closures & LEGB scope

Next: functools.wraps

Return to Python Backend Roadmap