Python

Python Decorators Deep Dive: From Basics to Custom Implementations

📅 December 05, 2025 ⏱️ 1 min read 👁️ 2 views 🏷️ Python

Decorators are a powerful metaprogramming tool in Python that allow you to modify or enhance functions and classes without changing their source code. They follow the decorator pattern from design patterns.

Basic Function Decorator


def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function call")
        result = func(*args, **kwargs)
        print("After function call")
        return result
    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {name}!")
    return f"Greeted {name}"

# Equivalent to: greet = my_decorator(greet)
result = greet("Alice")

Decorators with Arguments


def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat(times=3)
def say_hello(name):
    return f"Hello, {name}!"

# Calls the function 3 times
messages = say_hello("Bob")

Preserving Function Metadata


from functools import wraps

def timer(func):
    @wraps(func)  # Preserves original function metadata
    def wrapper(*args, **kwargs):
        import time
        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():
    """This is a slow function"""
    import time
    time.sleep(1)
    return "Done"

print(slow_function.__name__)  # 'slow_function'
print(slow_function.__doc__)   # 'This is a slow function'

Class Decorators


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

@singleton
class Database:
    def __init__(self):
        self.connection = "Connected"

db1 = Database()
db2 = Database()
print(db1 is db2)  # True - same instance

Real-World Use Cases


# Authentication decorator
def require_auth(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not is_authenticated():
            raise PermissionError("Not authenticated")
        return func(*args, **kwargs)
    return wrapper

# Caching/Memoization
from functools import lru_cache

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

# Logging
def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

# Rate limiting
import time
from functools import wraps

def rate_limit(max_per_second):
    min_interval = 1.0 / max_per_second
    def decorator(func):
        last_called = [0.0]
        @wraps(func)
        def wrapper(*args, **kwargs):
            elapsed = time.time() - last_called[0]
            if elapsed < min_interval:
                time.sleep(min_interval - elapsed)
            last_called[0] = time.time()
            return func(*args, **kwargs)
        return wrapper
    return decorator

Stacking Multiple Decorators


@timer
@log_calls
@require_auth
def process_payment(amount):
    return f"Processed ${amount}"

# Decorators are applied from bottom to top
# Equivalent to: timer(log_calls(require_auth(process_payment)))

🏷️ Tags:
python decorators advanced python metaprogramming design patterns

📚 Related Articles