Skip to main content
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.
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:

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.

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.
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.
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.
This desugars to a chained call:

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.
Tired of Poor Python Skills? Fix That With Deep Grasping!Learn More