Python Decorators · Examples
Core Python

Python Decorators Examples

Short, runnable programs that show python decorators in action. Read the explanation, press Run, then change the code and try your own ideas. New to the topic? Start with the Python Decorators lesson.

1Validate arguments before the function runs

The wrapper checks every positional argument first and raises a clear ValueError before the real function is called. The function itself stays free of checking code.

Validate arguments before the function runs
from functools import wraps

def positive_args(func):
    @wraps(func)
    def wrapper(*args):
        for value in args:
            if value <= 0:
                raise ValueError(f"{func.__name__}: {value} must be positive")
        return func(*args)
    return wrapper

@positive_args
def rectangle_area(width, height):
    return width * height

print(rectangle_area(4, 5))
try:
    rectangle_area(4, -1)
except ValueError as error:
    print("Rejected:", error)
Expected output20
Rejected: rectangle_area: -1 must be positive

2Memoise with functools.cache

@cache stores each answer by its arguments. cache_info() reports how often a stored answer was reused (hits) and how often it had to be computed (misses).

Memoise with functools.cache
from functools import cache

@cache
def shipping_cost(weight_kg):
    print(f"computing for {weight_kg} kg")
    return 4.5 + weight_kg * 1.25

print(shipping_cost(2))
print(shipping_cost(2))
print(shipping_cost(10))
print(shipping_cost(2))
print(shipping_cost.cache_info())
Expected outputcomputing for 2 kg
7.0
7.0
computing for 10 kg
17.0
7.0
CacheInfo(hits=2, misses=2, maxsize=None, currsize=2)

3A decorator with an argument: deprecation warnings

deprecated("...") is called first and returns the real decorator. The message is remembered by the wrapper, which prints it on every call and then runs the old function.

A decorator with an argument: deprecation warnings
from functools import wraps

def deprecated(replacement):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"Warning: {func.__name__} is deprecated, use {replacement}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@deprecated("total_price")
def old_total(prices):
    return sum(prices)

def total_price(prices):
    return round(sum(prices), 2)

print(old_total([4.5, 3.25]))
print(total_price([4.5, 3.25]))
Expected outputWarning: old_total is deprecated, use total_price
7.75
7.75

4Stacking order: when does each layer run?

Decorators are applied bottom-up (inner first), but the wrappers run top-down (outer first). The printed lines show both orders.

Stacking order: when does each layer run?
def tag(name):
    def decorator(func):
        print(f"decorating with {name}")
        def wrapper(*args, **kwargs):
            print(f"enter {name}")
            result = func(*args, **kwargs)
            print(f"exit {name}")
            return result
        return wrapper
    return decorator

@tag("outer")
@tag("inner")
def work():
    print("working")

print("--- calling ---")
work()
Expected outputdecorating with inner
decorating with outer
--- calling ---
enter outer
enter inner
working
exit inner
exit outer

5A decorator that registers functions

A decorator does not have to wrap anything. This one records each function in a dictionary and returns it unchanged, a pattern used by web frameworks to register routes and commands.

A decorator that registers functions
commands = {}

def command(name):
    def decorator(func):
        commands[name] = func
        return func
    return decorator

@command("hello")
def say_hello():
    return "Hello there!"

@command("bye")
def say_bye():
    return "See you soon!"

for name in sorted(commands):
    print(name, "->", commands[name]())
print(say_hello())
Expected outputbye -> See you soon!
hello -> Hello there!
Hello there!

6Format the result of many functions

One decorator changes the output of any function that returns a number. The functions keep calculating plain numbers, and the formatting lives in one place.

Format the result of many functions
from functools import wraps

def as_currency(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        amount = func(*args, **kwargs)
        return f"${amount:,.2f}"
    return wrapper

@as_currency
def order_total(prices, tax_rate=0.0):
    return sum(prices) * (1 + tax_rate)

@as_currency
def shipping(weight_kg):
    return 4.5 + weight_kg * 1.25

print(order_total([1200, 349.5], tax_rate=0.1))
print(order_total([19.99, 5]))
print(shipping(2))
Expected output$1,704.45
$24.99
$7.00

Practise next

Python Decorators exercises → Back to the lesson

Python *args and **kwargs examplesPython Lambda Functions examplesPython Closures examplesPython Class Methods and Static Methods examples