Decorators Explained¶
Try This First: Before reading, define a simple function:
def greet(): return "hello". Now try adding@propertyto a class method. What changes? Decorators modify how functions behave.
Learn Your Way¶
| Read | Build | Watch | Test | Review | Visualize |
|---|---|---|---|---|---|
| You are here | Projects | Videos | Quiz | Flashcards | Diagrams |
A decorator is a function that wraps another function to add extra behavior. The @ symbol is shorthand for applying a decorator.
Visualize It¶
See how a decorator wraps a function and changes its behavior: Open in Python Tutor
What decorators look like¶
The @app.get("/") is a decorator. It takes the home function and registers it as a web endpoint. You see decorators everywhere in FastAPI, Flask, Click, and pytest.
How decorators work¶
A decorator is just a function that takes a function and returns a new function:
def shout(func):
def wrapper():
result = func()
return result.upper()
return wrapper
@shout
def greet():
return "hello, world"
print(greet()) # "HELLO, WORLD"
The @shout line is equivalent to:
Decorators with arguments¶
If the original function takes arguments, the wrapper must pass them through:
def log_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
add(3, 5)
# Calling add with args=(3, 5), kwargs={}
# add returned 8
Real-world examples¶
Flask / FastAPI — route registration:
pytest — parametrized tests:
@pytest.mark.parametrize("input,expected", [(1, 1), (2, 4), (3, 9)])
def test_square(input, expected):
assert input ** 2 == expected
Click — CLI commands:
@click.command()
@click.option("--name", default="World")
def hello(name):
click.echo(f"Hello, {name}!")
Timing a function:
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} took {elapsed:.2f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "done"
Stacking decorators¶
You can apply multiple decorators. They apply bottom-up:
@decorator_a
@decorator_b
def my_function():
pass
# Equivalent to:
my_function = decorator_a(decorator_b(my_function))
Common mistakes¶
Forgetting functools.wraps:
from functools import wraps
def my_decorator(func):
@wraps(func) # Preserves the original function's name and docstring
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Without @wraps, the decorated function loses its original name, which makes debugging harder.
Calling instead of decorating:
@my_decorator() # Note the () — this CALLS the decorator
def func(): pass
@my_decorator # No () — this APPLIES the decorator
def func(): pass
Whether you need () depends on how the decorator was written. Some take arguments (like @app.get("/")), some don't (like @timer).
Practice¶
- Module 02 CLI Tools
- Module 04 FastAPI
- Module 08 Advanced Testing
- Module: Elite Track / 01 Algorithms Complexity Lab
- Module: Elite Track / 02 Concurrent Job System
- Module: Elite Track / 03 Distributed Cache Simulator
- Module: Elite Track / 04 Secure Auth Gateway
- Module: Elite Track / 05 Performance Profiler Workbench
- Module: Elite Track / 06 Event Driven Architecture Lab
- Module: Elite Track / 07 Observability Slo Platform
- Module: Elite Track / 08 Policy Compliance Engine
- Module: Elite Track / 09 Open Source Maintainer Simulator
- Module: Elite Track / 10 Staff Engineer Capstone
Quick check: Take the quiz
Review: Flashcard decks Practice reps: Coding challenges
| ← Prev | Home | Next → |
|---|---|---|