Fundamentals 9 min read

Python Decorators: Ten Practical Custom Decorators with Code Examples

This article explains Python decorators, a powerful feature for modifying functions or classes, and provides ten practical custom decorators—including @timer, @memoize, @validate_input, @log_results, @suppress_errors, @validate_output, @retry, @visualize_results, @debug, and @deprecated—each with clear explanations and complete code samples.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Python Decorators: Ten Practical Custom Decorators with Code Examples

Decorators in Python are powerful functions that modify or enhance other functions or classes without altering their original source code, enabling reusable and clean extensions.

@timer: Measure execution time

The @timer decorator records the start and end times of a function, prints the elapsed time, and returns the original result.

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time:.2f} seconds to execute.")
        return result
    return wrapper

@timer
def my_data_processing_function():
    # Your data processing code here
    pass

@memoize: Cache results

The @memoize decorator stores function results in a dictionary keyed by arguments, avoiding repeated expensive calculations.

def memoize(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@memoize
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

@validate_input: Data validation

This decorator checks input data before the wrapped function runs, raising an error if validation fails.

def validate_input(func):
    def wrapper(*args, **kwargs):
        # Your data validation logic here
        if valid_data:
            return func(*args, **kwargs)
        else:
            raise ValueError("Invalid data. Please check your inputs.")
    return wrapper

@validate_input
def analyze_data(data):
    # Your data analysis code here
    pass

@log_results: Log output

The @log_results decorator writes the function name and its result to a log file for later inspection.

def log_results(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        with open("results.log", "a") as log_file:
            log_file.write(f"{func.__name__} - Result: {result}\n")
        return result
    return wrapper

@log_results
def calculate_metrics(data):
    # Your metric calculation code here
    pass

@suppress_errors: Graceful error handling

This decorator catches exceptions, prints a friendly message, and returns None so the program can continue.

def suppress_errors(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"Error in {func.__name__}: {e}")
            return None
    return wrapper

@suppress_errors
def preprocess_data(data):
    # Your data preprocessing code here
    pass

@validate_output: Output validation

After a function runs, this decorator checks the result against custom criteria and raises an error if the output is invalid.

def validate_output(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        if valid_output(result):
            return result
        else:
            raise ValueError("Invalid output. Please check your function logic.")
    return wrapper

@validate_output
def clean_data(data):
    # Your data cleaning code here
    pass

@retry: Automatic retries

The @retry decorator attempts to call a function multiple times with a delay between attempts, useful for flaky operations like network requests.

import time

def retry(max_attempts, delay):
    def decorator(func):
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempts+1} failed. Retrying in {delay} seconds.")
                    attempts += 1
                    time.sleep(delay)
            raise Exception("Max retry attempts exceeded.")
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def fetch_data_from_api(api_url):
    # Your API data fetching code here
    pass

@visualize_results: Automatic visualization

This decorator runs the original function, then creates a Matplotlib figure for custom visual output.

import matplotlib.pyplot as plt

def visualize_results(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        plt.figure()
        # Your visualization code here
        plt.show()
        return result
    return wrapper

@visualize_results
def analyze_and_visualize(data):
    # Your combined analysis and visualization code here
    pass

@debug: Debugging helper

The @debug decorator prints the function name together with its positional and keyword arguments before execution.

def debug(func):
    def wrapper(*args, **kwargs):
        print(f"Debugging {func.__name__} - args: {args}, kwargs: {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@debug
def complex_data_processing(data, threshold=0.5):
    # Your complex data processing code here
    pass

@deprecated: Mark obsolete functions

When a function is outdated, @deprecated issues a DeprecationWarning each time the function is called.

import warnings

def deprecated(func):
    def wrapper(*args, **kwargs):
        warnings.warn(f"{func.__name__} is deprecated and will be removed in future versions.", DeprecationWarning)
        return func(*args, **kwargs)
    return wrapper

@deprecated
def old_data_processing(data):
    # Your old data processing code here
    pass

Overall, Python decorators provide a flexible way to add functionality such as timing, caching, validation, logging, error handling, retries, visualization, debugging, and deprecation warnings, making code more modular and maintainable.

Performancecachingvalidationloggingerror handlingDecorators
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.