Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A Python function decorator is a callable (such as a function or a class implementing the __call__ method) that takes another callable as an argument, dynamically modifies or extends its execution context, and returns an object. While decorators frequently wrap the target function within a closure to return a new callable, they can also return the original, unmodified function (e.g., for registration purposes) or a non-callable object (e.g., a descriptor object like @property).

Syntactic Sugar vs. Manual Application

The @ symbol is syntactic sugar for passing a function into the decorator and reassigning the resulting object back to the original function name. The following two implementations are strictly equivalent at runtime: Using Syntactic Sugar:
def my_decorator(func):
    return func

@my_decorator
def target_function():
    pass
Manual Application:
def my_decorator(func):
    return func

def target_function():
    pass

target_function = my_decorator(target_function)

Standard Decorator Architecture

A standard function-based decorator consists of an outer function that accepts the target function, and an inner function (the wrapper) that executes the extended logic. The wrapper utilizes *args and **kwargs to maintain compatibility with any arbitrary signature the target function might possess.
def standard_decorator(func):
    def wrapper(*args, **kwargs):
        # Pre-execution logic
        
        # Execute the original function
        result = func(*args, **kwargs)
        
        # Post-execution logic
        return result
        
    return wrapper

Preserving Function Metadata

By default, returning a wrapper function replaces the original function’s metadata (such as __name__, __doc__, and __module__) with the metadata of the wrapper itself. To preserve the target function’s identity, the standard library provides functools.wraps, which is applied as a decorator to the inner wrapper function.
import functools

def metadata_preserving_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    return wrapper

@metadata_preserving_decorator
def example_function():
    """This docstring is preserved."""
    pass


# example_function.__name__ remains 'example_function'

# example_function.__doc__ remains 'This docstring is preserved.'

Decorator Stacking (Chaining)

Multiple decorators can be applied to a single function by stacking them. The critical mechanism to understand in stacking is the difference between the order of application and the order of execution. Decorators are applied bottom-up (closest to the function definition first). However, the execution of the resulting wrappers is nested. The pre-execution logic runs outside-in (top-down), while the post-execution logic runs inside-out (bottom-up) as the call stack unwinds.
def decorator_one(func):
    def wrapper(*args, **kwargs):
        print("Entering decorator_one")
        result = func(*args, **kwargs)
        print("Exiting decorator_one")
        return result
    return wrapper

def decorator_two(func):
    def wrapper(*args, **kwargs):
        print("Entering decorator_two")
        result = func(*args, **kwargs)
        print("Exiting decorator_two")
        return result
    return wrapper

@decorator_one
@decorator_two
def stacked_function():
    print("Executing target function")


# Application equivalent: 

# stacked_function = decorator_one(decorator_two(stacked_function))

stacked_function()

# Output:

# Entering decorator_one

# Entering decorator_two

# Executing target function

# Exiting decorator_two

# Exiting decorator_one

Class-Based Decorators

Decorators can also be implemented as classes, a pattern highly effective for decorators requiring complex state management. A standard class-based decorator without arguments accepts the target function in its __init__ method and implements the wrapper logic in its __call__ method. To preserve the original function’s metadata, functools.update_wrapper must be explicitly called within __init__. Furthermore, if a class-based decorator is applied to a class method, it will break by default. Because the decorator class does not inherently implement the descriptor protocol, the decorated method loses its binding to the instance, meaning self will not be passed during invocation. This is resolved by implementing the __get__ method to return a bound method.
import functools
import types

class ClassDecorator:
    def __init__(self, func):
        self.func = func
        self.call_count = 0  # State management
        # Preserve original function's metadata
        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        # Pre-execution logic
        result = self.func(*args, **kwargs)
        # Post-execution logic
        return result

    def __get__(self, instance, owner):
        if instance is None:
            return self
        # Implement descriptor protocol to bind the method to the instance
        return types.MethodType(self, instance)

@ClassDecorator
def standalone_function():
    pass

class ExampleClass:
    @ClassDecorator
    def method(self):
        pass

Decorator Factories (Decorators with Arguments)

When a decorator requires its own arguments, it must be implemented as a decorator factory. For function-based decorators, this requires a three-level nested architecture:
  1. The outermost function accepts the decorator arguments.
  2. The middle function accepts the target callable.
  3. The innermost function (the wrapper) accepts the target callable’s arguments.
import functools

def decorator_factory(decorator_arg1, decorator_arg2):
    def actual_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Logic utilizing decorator_arg1 and decorator_arg2
            
            result = func(*args, **kwargs)
            return result
        return wrapper
    return actual_decorator

@decorator_factory("value1", "value2")
def target_function(x, y):
    return x + y
In this architecture, the Python interpreter first evaluates decorator_factory("value1", "value2"), which returns actual_decorator. The interpreter then applies actual_decorator to target_function.
Master Python with Deep Grasping Methodology!Learn More