Fundamentals 7 min read

Understanding Python Decorators: Concepts, Use Cases, and Code Examples

Python decorators are special functions that wrap other functions or classes to add functionality without modifying their source code, and this guide explains their principles, common use cases such as logging, timing, caching, and permission checks, and provides ten practical code examples illustrating various decorator patterns.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Understanding Python Decorators: Concepts, Use Cases, and Code Examples

In Python, a decorator is a special type of function that can add extra behavior to other functions or classes without modifying their source code. A decorator receives a function (or class) as input and returns a new function that typically executes additional operations before or after the original function.

The syntax is simple: placing @decorator_name above a function definition tells Python to apply that decorator to the function.

Common scenarios for using decorators include logging, performance testing, permission validation, result caching, and argument validation.

Example 1: Simple decorator that prints function call information

def my_decorator(func):
    def wrapper():
        print(f"{func.__name__} is called")
        func()
    return wrapper

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

say_hello()

Example 2: Parameterized decorator for personalized messages

def message_decorator(message):
    def decorator(func):
        def wrapper():
            print(message)
            func()
        return wrapper
    return decorator

@message_decorator("Welcome!")
def greet():
    print("Nice to meet you.")

greet()

Example 3: Timing decorator that measures execution time

import time

def timer_decorator(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} seconds")
        return result
    return wrapper

@timer_decorator
def long_running_function(seconds):
    time.sleep(seconds)

long_running_function(2)

Example 4: Logging decorator that records function call details

import logging
logging.basicConfig(level=logging.INFO)

def log_decorator(func):
    def wrapper(*args, **kwargs):
        logging.info(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        logging.info(f"{func.__name__} returned {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

add(3, 5)

Example 5: Permission check decorator that simplifies access control

def admin_required(func):
    def wrapper(user):
        if user['role'] != 'admin':
            raise Exception("Permission denied.")
        return func(user)
    return wrapper

@admin_required
def delete_user(user):
    print(f"Deleting user {user['username']}")

user = {'username': 'Alice', 'role': 'admin'}
delete_user(user)

Example 6: Caching decorator using functools.lru_cache to avoid repeated calculations

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  # first computation
print(fibonacci(10))  # retrieved from cache

Example 7: Type‑checking decorator that validates argument types automatically

def type_check_decorator(*arg_types):
    def decorator(func):
        def wrapper(*args):
            if len(args) != len(arg_types):
                raise TypeError("Argument count mismatch")
            for a, t in zip(args, arg_types):
                if not isinstance(a, t):
                    raise TypeError(f"Argument {a} is not of type {t}")
            return func(*args)
        return wrapper
    return decorator

@type_check_decorator(int, int)
def add_numbers(a, b):
    return a + b

print(add_numbers(2, 3))
# print(add_numbers('2', 3))  # would raise TypeError

Example 8: Singleton decorator that ensures a class has only one instance

def singleton(cls):
    _instance = {}
    def get_instance(*args, **kwargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)
        return _instance[cls]
    return get_instance

@singleton
class MyClass:
    def __init__(self, value):
        self.value = value

obj1 = MyClass(10)
obj2 = MyClass(20)
print(obj1 is obj2)  # True

Example 9: Class decorator that modifies class attributes

class ClassDecorator:
    def __init__(self, decorated_class):
        self.decorated_class = decorated_class
    def __call__(self, *args, **kwargs):
        instance = self.decorated_class(*args, **kwargs)
        instance.name = "Decorated"
        return instance

@ClassDecorator
class MyClass:
    def __init__(self):
        self.name = "Original"

obj = MyClass()
print(obj.name)  # Decorated

Example 10: Chained decorators demonstrating multiple layers of decoration

def bold_decorator(func):
    def wrapper():
        return f"
{func()}
"
    return wrapper

def italic_decorator(func):
    def wrapper():
        return f"
{func()}
"
    return wrapper

@bold_decorator
@italic_decorator
def greet():
    return "Hello, World!"

print(greet())  #
Hello, World!

Through these examples we see the versatility and power of decorators: they can simplify code structure, improve readability, and enhance maintainability, encouraging further creative applications of Python decorators.

Programmingsoftware developmentDecoratorscode-examples
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

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.