← Back to Articles
Tutorial

Python Decorators – Complete Guide with Examples

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.

Code Examples

Basic Decorator Pattern

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 call

Decorator with Function Arguments

def 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)  # 8

Timing Decorator (Real-World)

import 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 seconds

Parameterized Decorator

def 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!

Stacking Multiple Decorators

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 bold

Caching/Memoization Decorator

def 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

Flask Route Decorator (Real-World)

# 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

More Python Tutorials