Fundamentals 8 min read

Boost Your Python Code: 5 Powerful Custom Decorators You Must Use

This article explores how Python decorators can eliminate repetitive code, improve performance, enforce type safety, simplify debugging, and implement rate limiting, offering five custom decorator examples with clear explanations and ready-to-use implementations.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Boost Your Python Code: 5 Powerful Custom Decorators You Must Use

In programming, efficiency often depends on how elegantly we can reuse and extend code. Python decorators are a powerful tool that let developers modify or extend the behavior of functions and methods, yet many developers do not fully utilize or understand them.

“A language that does not change the way you think about programming is not worth learning.” – Alan Perlis

When you truly understand decorators, they can change the way you think about code structure. This article delves into five custom Python decorators that can improve project quality and simplify the programming experience.

Problem: Repeated Patterns in Code

During development, repetitive patterns such as logging, error handling, and performance monitoring often appear in multiple functions, increasing the risk of bugs and making maintenance harder.

Solution: Python Decorators

Decorators provide a concise and elegant solution. By wrapping additional functionality around a function, decorators reduce redundancy, promote code reuse, and improve readability.

1. Measure Execution Time

Tracking execution time for functions, especially during performance testing, can become tedious if done repeatedly.

Decorator:

time_logger
import time

def time_logger(func):
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"Function '{func.__name__}' took {end_time - start_time:.4f} seconds.")
        return result
    return wrapper

Usage:

@time_logger
def process_data(n):
    time.sleep(n)
    return f"Processed {n} seconds of data."

process_data(2)

2. Retry Error‑Prone Functions

For functions that may raise transient errors (e.g., API calls), a retry decorator can encapsulate the retry logic.

Decorator:

retry
import time

def retry(retries=3, delay=1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempt + 1} failed: {e}")
                    time.sleep(delay)
            raise Exception(f"Function '{func.__name__}' failed after {retries} retries.")
        return wrapper
    return decorator

Usage:

@retry(retries=5, delay=2)
def fetch_data():
    import random
    if random.random() < 0.8:
        raise ValueError("Transient error!")
    return "Data fetched successfully."

print(fetch_data())

3. Enforce Type Checking

Python’s type hints are not enforced at runtime. This decorator ensures that arguments match expected types.

Decorator:

type_check
def type_check(*expected_types):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for arg, expected in zip(args, expected_types):
                if not isinstance(arg, expected):
                    raise TypeError(f"Expected {expected}, got {type(arg)}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

Usage:

@type_check(int, int)
def add(a, b):
    return a + b

print(add(2, 3))  # works
# print(add(2, "3"))  # raises TypeError

4. Debug Function Calls

A debugging decorator can log input arguments and return values, making troubleshooting easier.

Decorator:

debug
def debug(func):
    def wrapper(*args, **kwargs):
        print(f"Calling '{func.__name__}' with args: {args} kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"'{func.__name__}' returned: {result}")
        return result
    return wrapper

Usage:

@debug
def multiply(a, b):
    return a * b

5. Rate Limiting

When a function (e.g., an API request) should only run a limited number of times within a time window, a rate‑limiting decorator is valuable.

Decorator:

rate_limiter
import time

def rate_limiter(calls, period):
    def decorator(func):
        last_calls = []
        def wrapper(*args, **kwargs):
            nonlocal last_calls
            now = time.time()
            last_calls = [t for t in last_calls if now - t < period]
            if len(last_calls) >= calls:
                raise RuntimeError("Rate limit exceeded.")
            last_calls.append(now)
            return func(*args, **kwargs)
        return wrapper
    return decorator

Usage:

@rate_limiter(calls=2, period=5)
def fetch_data():
    print("Fetching data...")

fetch_data()
time.sleep(2)
fetch_data()
# fetch_data()  # would raise RuntimeError

Conclusion

Python decorators offer an elegant way to enhance and extend code functionality. They reduce redundancy, make codebases more modular and readable, and embody the principle that “programs are meant to be read by humans, with computers executing them occasionally.”

“Programs are meant to be read by humans, with computers executing them occasionally.” – Donald Knuth

Decorators embody this philosophy, making code cleaner and easier to maintain.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performancecode-reusedecorators
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

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.