Lesson 22 of 25

Decorators

Understanding Decorators

A decorator is a function that takes another function as input, adds some functionality, and returns a modified function. Decorators let you modify or extend the behavior of functions without changing their code.

The @decorator syntax is syntactic sugar for wrapping a function. Writing @my_decorator above a function definition is the same as calling my_function = my_decorator(my_function).

Example
# A simple decorator
def uppercase_result(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_result
def greet(name):
    return f"hello, {name}"

print(greet("alice"))  # HELLO, ALICE

# Decorator with timing
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:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "Done!"

result = slow_function()
# slow_function took 1.0012 seconds
print(result)  # Done!
  • A decorator is a function that wraps another function
  • @decorator — apply the decorator using the @ syntax
  • The wrapper function calls the original and can add logic before/after
  • *args, **kwargs in the wrapper make it work with any function signature
  • Decorators are widely used in Flask, Django, and other frameworks
Try Decorators
JavaScript
def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}({args}, {kwargs})")
        result = func(*args, **kwargs)
        print(f"  -> returned {result}")
        return result
    return wrapper

@log_call
def add(a, b):
    return a + b

@log_call
def greet(name):
    return f"Hello, {name}!"

add(3, 5)
greet("Alice")
Notes
  • Use functools.wraps in your decorators to preserve the original function's name, docstring, and other metadata.

Practical Decorators and Chaining

Decorators have many practical applications: logging, authentication, caching, rate limiting, and input validation. You can also stack multiple decorators on a single function — they are applied bottom-up.

Decorators with arguments require an extra layer of nesting — a decorator factory that returns the actual decorator.

Example
from functools import wraps

# Decorator with arguments (decorator factory)
def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("Alice")
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!

# Chaining decorators
def bold(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

@bold
@italic
def format_text(text):
    return text

print(format_text("Hello"))  # <b><i>Hello</i></b>

# Caching with built-in decorator
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # 12586269025 (instant with caching!)
Try Chaining Decorators
JavaScript
from functools import wraps

def bold(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

@bold
@italic
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))
print(greet("Bob"))
Notes
  • When chaining decorators, the order matters. @bold @italic means bold(italic(func)). The bottommost decorator is applied first, then the one above it wraps the result.