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 class decorator is a higher-order callable (such as a function or a class) that takes a class object as its single argument and returns a replacement object. While it typically returns a modified class or a proxy class, a class decorator can return any Python object, including a function or an instance of another class. It provides a metaprogramming mechanism to modify class behavior, attributes, or methods at the time of class definition, operating at the class level rather than the instance level.

Syntax and Desugaring

The @ symbol is syntactic sugar applied directly above the class keyword.
@decorator
class TargetClass:
    pass
At runtime, the Python interpreter desugars this syntax into a standard function call, reassigning the class name in the local namespace to the return value of the decorator:
class TargetClass:
    pass

TargetClass = decorator(TargetClass)

Implementation Patterns

Class decorators generally follow one of three architectural patterns depending on whether they mutate the original class, wrap it in a proxy, or use a callable instance to replace the class.

1. Mutation (Returning the Original Class)

The decorator modifies the target class’s namespace (e.g., injecting attributes or overriding methods) and returns the original class object. This preserves the class identity, meaning isinstance() and issubclass() checks remain unaffected.
def mutate_class(cls):
    # Inject a new method into the class dictionary
    def new_method(self):
        return "Injected"
    
    setattr(cls, 'injected_method', new_method)
    return cls

@mutate_class
class BaseClass:
    pass

2. Wrapping (Returning a Proxy Class)

The decorator returns a completely new class that intercepts instantiation or attribute access. This pattern replaces the original class in the local namespace.
def wrap_class(cls):
    class Wrapper:
        def __init__(self, *args, **kwargs):
            # Intercept instantiation
            self._wrapped_instance = cls(*args, **kwargs)

        def __getattr__(self, name):
            # Delegate attribute access to the original instance
            return getattr(self._wrapped_instance, name)
            
    return Wrapper

@wrap_class
class BaseClass:
    pass
Note on Type Checking: Because BaseClass is now a reference to Wrapper, standard type checking (type(obj) is BaseClass) will evaluate against Wrapper, not the original class. Note on Magic Methods: A major limitation of this proxy wrapping pattern is that __getattr__ will not delegate special/magic methods (e.g., __str__, __len__, __iter__). Python’s data model dictates that special methods are looked up directly on the class (Wrapper), bypassing __getattr__. Consequently, the wrapped instance’s magic methods will not be accessible unless explicitly redefined on the Wrapper class.

3. Class-Based Class Decorators

A class can act as a decorator for another class. To achieve this, the decorator class must implement __init__ to accept the target class, and __call__ to handle the instantiation of the decorated class.
class DecoratorClass:
    def __init__(self, cls):
        self._cls = cls

    def __call__(self, *args, **kwargs):
        # Execute logic prior to target class instantiation
        instance = self._cls(*args, **kwargs)
        # Execute logic post-instantiation
        return instance

@DecoratorClass
class TargetClass:
    pass
Critical Limitation: In this pattern, TargetClass is reassigned to an instance of DecoratorClass, meaning it is no longer a type. Consequently, using it in standard type checks like isinstance(obj, TargetClass) or issubclass(cls, TargetClass) will raise a TypeError: isinstance() arg 2 must be a type. This is a fundamental breakage of class semantics that must be accounted for when using instances as class decorators.

Parameterized Class Decorators (Decorator Factories)

To pass arguments to a class decorator, you must create a decorator factory—a function that accepts the parameters and returns the actual decorator callable.
def decorator_factory(param1, param2):
    def actual_decorator(cls):
        setattr(cls, 'meta_data', (param1, param2))
        return cls
    return actual_decorator

@decorator_factory("value1", "value2")
class TargetClass:
    pass
This desugars to a chained call:
TargetClass = decorator_factory("value1", "value2")(TargetClass)

Execution Timing

Class decorators are evaluated and executed exactly once: at module import time (or when the script is parsed and executed). The decorator logic runs immediately after the class body is evaluated and the class object is constructed by its metaclass, but before any instances of the class are created.
Master Python with Deep Grasping Methodology!Learn More