Master Python decorators for function modification. Learn syntax, arguments, stacking, timing, caching, and real-world decorator patterns.
📌 Python decorators, @decorator syntax, function wrapper, decorator with arguments, timing decorator, memoization
A decorator is a special kind of function that takes another function as input, extends or modifies its behavior, and returns the altered function—all without changing the original source code. Decorators embody the Open/Closed Principle: code open for extension, closed for modification.
The @decorator_name syntax places a decorator directly above a function definition. This elegant syntax is equivalent to manually wrapping functions: decorated_function = my_decorator(original_function). The @ syntax makes decorator application cleaner and more readable.
Decorators working with function arguments require *args and **kwargs to accept any combination of positional and keyword parameters. This flexibility ensures decorators work with functions of any signature without breaking their calling interface.
Real-world decorators serve critical purposes: Flask and Django use decorators for URL routing, @property controls attribute access, memoization caches results to avoid recalculation, rate limiters restrict call frequency, and authorization decorators verify permissions before execution.
Multiple decorators can stack on a single function, executing bottom-to-top with the closest decorator applied first. This composability enables combining behaviors like logging, authentication, and caching in a clean, declarative style.
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
print("Hello, world!")
say_hello()
# Output:
# Before function call
# Hello, world!
# After function calldef my_decorator(func):
def wrapper(*args, **kwargs):
print("Before")
result = func(*args, **kwargs)
print("After")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
result = add(5, 3) # Works with any arguments
print(result) # 8import time
def timing_decorator(func):
"""Measure function execution time"""
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timing_decorator
def slow_function():
time.sleep(1)
return "Done"
slow_function()
# Output: slow_function took 1.0012 secondsdef repeat(times):
"""Decorator that repeats function execution"""
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# Output:
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!def bold(func):
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def get_text():
return "Hello"
print(get_text())
# Output: <b><i>Hello</i></b>
# Executes bottom-to-top: italic first, then bolddef memoize(func):
"""Cache function results"""
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Dramatically faster with caching
print(fibonacci(100)) # Instant instead of forever# How Flask uses decorators for routing
from flask import Flask
app = Flask(__name__)
@app.route('/home')
def home():
return "Welcome Home"
@app.route('/about')
def about():
return "About Us"
# The @app.route decorator maps URLs to functions