Navigation

Python

How to Create and Use Python Decorators

Create Python decorators to modify function behavior. Add logging, timing, authentication, and caching with clean decorator syntax.

Table Of Contents

Problem

You want to add functionality like logging, timing, or authentication to multiple functions without repeating code in each function.

Solution

# Basic decorator
def my_decorator(func):
    def wrapper():
        print("Before function")
        func()
        print("After function")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# Before function
# Hello!
# After function

# Decorator with arguments
def timer(func):
    import 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

@timer
def slow_function(n):
    import time
    time.sleep(1)
    return n * 2

result = slow_function(5)  # Shows timing info

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

@repeat(3)
def greet(name):
    print(f"Hello {name}")

greet("Alice")  # Prints 3 times

# Class-based decorator
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Call {self.count} of {self.func.__name__}")
        return self.func(*args, **kwargs)

@CountCalls
def add(x, y):
    return x + y

add(1, 2)  # Call 1 of add
add(3, 4)  # Call 2 of add

# Preserve function metadata
from functools import wraps

def log_calls(func):
    @wraps(func)  # Preserves original function's metadata
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_calls
def calculate(x, y):
    """Add two numbers"""
    return x + y

print(calculate.__name__)  # calculate
print(calculate.__doc__)   # Add two numbers

Explanation

Decorators are functions that take another function and extend its behavior. Use @decorator_name above function definitions for cleaner syntax.

Always use *args, **kwargs in wrapper functions to handle any arguments. Use @wraps from functools to preserve the original function's metadata like name and docstring.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Python