Skip to content

Decorators Guide in Python

What Is a Decorator?

A decorator is a function that modifies another function.

Decorators allow adding extra behavior: - logging - timing - validation - retry systems - permissions - caching

Without changing the original function.


Basic Decorator Structure

def decorator(func):

    def wrapper():
        print("Before function")

        result = func()

        print("After function")

        return result

    return wrapper

Applying the Decorator

@decorator
def greet():
    print("Hello")

Equivalent to:

greet = decorator(greet)

Output

Before function
Hello
After function

Why Are Decorators Useful?

They separate concerns: - business logic stays clean - reusable behavior stays outside functions


Decorator With Arguments

Most decorators need *args and **kwargs.


Example

def logger(func):

    def wrapper(*args, **kwargs):
        print("Function called")

        return func(*args, **kwargs)

    return wrapper

Timing Decorator

import time


def timer(func):

    def wrapper(*args, **kwargs):
        start = time.time()

        result = func(*args, **kwargs)

        end = time.time()

        print(f"Executed in {end - start:.3f}s")

        return result

    return wrapper

Example Usage

@timer
def slow_spell():
    time.sleep(1)

Validation Decorator

def validate_power(min_power):

    def decorator(func):

        def wrapper(power):
            if power < min_power:
                return "Insufficient power"

            return func(power)

        return wrapper

    return decorator

Example Usage

@validate_power(10)
def cast_spell(power):
    return f"Spell cast with {power}"

Decorator Factories

Decorators with parameters are called:

→ Decorator Factories

Because they create decorators dynamically.


Retry Decorator

def retry(max_attempts):

    def decorator(func):

        def wrapper(*args, **kwargs):

            for attempt in range(max_attempts):

                try:
                    return func(*args, **kwargs)

                except Exception:
                    print("Retrying...")

            return "Failed"

        return wrapper

    return decorator

Example Usage

@retry(3)
def unstable_spell():
    raise ValueError("Boom")

Stacking Decorators

Multiple decorators can be combined.


Example

@timer
@logger
def fireball():
    print("Fireball!")

Equivalent to:

fireball = timer(logger(fireball))

Execution Order

Decorators execute from bottom to top.


Why Use functools.wraps?

Without wraps, decorators replace: - function name - docstring - metadata


Problem Example

def decorator(func):

    def wrapper():
        return func()

    return wrapper
print(greet.__name__)

Output

wrapper

The original name is lost.


Correct Version With wraps

from functools import wraps


def decorator(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

Now metadata is preserved.


Decorators on Methods

Decorators also work on class methods.


Example

class Mage:

    @staticmethod
    def validate(name):
        return len(name) > 2

Common Mistakes

Forgetting to Return the Wrapper

Wrong:

return func

Correct:

return wrapper

Forgetting *args and **kwargs

Wrong:

def wrapper():

Correct:

def wrapper(*args, **kwargs):

Real Use Cases

Decorators are heavily used in: - Flask - FastAPI - Django - testing frameworks - logging systems - caching systems - authentication - retry systems


Key Takeaways

  • Decorators modify function behavior
  • They help separate concerns
  • @decorator is syntactic sugar
  • wraps preserves metadata
  • Decorators can accept parameters
  • Multiple decorators can be stacked
  • Decorators are extremely common in modern Python