Class Decorators0%

Class Decorators

Beginner12 min readUpdated: Jul 11, 2026
Study Materials

Class Decorators

While decorators are most commonly applied to functions, Python equally supports Class Decorators (decorating a class definition) and Classes as Decorators (using a callable class with __call__ to wrap functions). Introduced formally in PEP 3129, class decorators provide a cleaner, more composable alternative to metaclasses for modifying or augmenting class definitions at definition time.


1. Class Decorators: Mechanics and Architecture

A class decorator is a callable that accepts a class object as its argument and returns a class object (either the modified original class or an entirely new replacement class).

Output
Class Definition with Decorator
@register_entity
class Order: ...
Desugared Assignment
Order = register_entity(Order)
┌───────────────┴───────────────┐
▼ ▼
Mutate original class & return it Wrap or replace with proxy
(e.g., add attributes, register) (e.g., enforce singleton)

Why Class Decorators Instead of Metaclasses?

  1. 1
    No Metaclass Conflicts: If two base classes use different metaclasses, multiple inheritance produces a severe TypeError: metaclass conflict. Class decorators avoid this entirely because they operate purely on the finished class object.
  2. 2
    Composability: You can easily stack multiple class decorators: @serializable, @audit_logged, @dataclass.
  3. 3
    Simplicity: Modifying an already-constructed class dictionary is dramatically simpler and less error-prone than intercepting type.__new__.

2. Practical Pattern 1: Dynamic Class Augmentation

A class decorator can dynamically inspect, validate, and inject helper methods into a target class:

Python
import datetime
from typing import Type, TypeVar
 
T = TypeVar("T")
 
def auto_timestamped(cls: Type[T]) -> Type[T]:
"""Class decorator that injects automatic creation timestamping into any class."""
original_init = cls.__init__
 
def __init__(self, *args, **kwargs):
self._created_at = datetime.datetime.utcnow()
original_init(self, *args, **kwargs)
 
cls.__init__ = __init__
 
# Inject helper methods directly into the class namespace
def get_age_in_seconds(self) -> float:
return (datetime.datetime.utcnow() - self._created_at).total_seconds()
 
cls.get_age_in_seconds = get_age_in_seconds # type: ignore
return cls
 
@auto_timestamped
class UserSession:
def __init__(self, session_id: str, username: str) -> None:
self.session_id = session_id
self.username = username
 
session = UserSession("sess_48291", "charlie")
print("Session user:", session.username)
print("Created at timestamp:", session._created_at)
print(f"Session age: {session.get_age_in_seconds():.4f}s")

3. Practical Pattern 2: Component & Plugin Registration

Frameworks such as web routers, task runners, and serialization engines use class decorators to maintain an internal registry of available plugins:

Visual Architecture & Process Flow

How data and code flow step-by-step

Flowchart
Step 1
def register
cls, name: str
Step 2
Callable[[Type], Type]:

4. Classes as Function Decorators (Using __call__)

In addition to decorating classes, classes can serve as decorators themselves by implementing the __call__ dunder method. This is especially useful when a decorator requires stateful tracking, such as call frequency counters, rate limiters, or memoization caches.

Python
import functools
from typing import Any, Callable
 
class CallCounter:
"""A decorator implemented as a class that tracks invocation counts."""
 
def __init__(self, func: Callable) -> None:
self.func = func
self.count = 0
functools.update_wrapper(self, func)
 
def __call__(self, *args: Any, **kwargs: Any) -> Any:
self.count += 1
print(f"[TELEMETRY] '{self.func.__name__}' has been called {self.count} time(s).")
return self.func(*args, **kwargs)
 
@CallCounter
def compute_checksum(data: str) -> int:
return sum(ord(c) for c in data)
 
print("Result 1:", compute_checksum("alpha"))
print("Result 2:", compute_checksum("beta"))
print("Result 3:", compute_checksum("gamma"))
print(f"Total recorded invocations: {compute_checksum.count}")

5. Architectural Comparison: Class Decorators vs Metaclasses

FeatureClass DecoratorMetaclass
Execution PointAfter the class has been fully constructed by typeBefore and during class construction (__new__, __init__)
Multiple InheritanceZero inheritance conflicts; decorators stack easilyCan trigger TypeError: metaclass conflict
Namespace AlterationCan mutate or augment existing class attributesCan customize the namespace mapping before class body runs
Subclass PropagationDoes not automatically apply to subclasses unless manually re-appliedAutomatically inherited by all subclasses

Multiple Choice Questions

1.

At what phase of execution does a class decorator run? A. Every time an instance of the class is instantiated. B. Only when an instance method is called. C. At class definition time, immediately after the class body has executed and the class object is created. D. When the Python process exits.

Answer: C
Explanation:Class decorators run once at import/definition time right after the class object has been constructed by the interpreter (MyClass = decorator(MyClass)).

2.

What is a major advantage of using class decorators instead of metaclasses for adding class-level behaviors? A. Class decorators run faster in CPython than any other code. B. Class decorators avoid multiple-inheritance "metaclass conflicts" and compose cleanly without altering class inheritance trees. C. Class decorators can only be used on built-in types. D. Class decorators do not require any function definitions.

Answer: B
Explanation:Metaclasses can easily trigger TypeError: metaclass conflict when combining classes inheriting from different metaclasses. Class decorators operate directly on the constructed class without inheritance hierarchy constraints.

3.

Which dunder method must a class implement so that instances of that class can be used as decorators for functions? A. __init__ B. __call__ C. __enter__ D. __iter__

Answer: B
Explanation:To act as a decorator, an object must be callable. Implementing __call__(self, *args, **kwargs) allows instances of a class to be invoked directly with parentheses, intercepting decorated function calls.

4.

What is an important limitation of class decorators compared to metaclasses? A. Class decorators cannot modify the attributes of a class. B. Class decorators are not automatically inherited by subclasses of the decorated class. C. Class decorators cannot accept arguments. D. Class decorators only work in Python 2.

Answer: B
Explanation:A class decorator only wraps the specific class it decorates. If another class subclasses the decorated class, the decorator does not automatically run on the subclass, unlike a metaclass which is inherited across the entire class hierarchy.

5.

Which popular Python standard library module introduced in Python 3.7 relies fundamentally on a class decorator? A. urllib B. dataclasses (@dataclass) C. sqlite3 D. threading

Answer: B
Explanation:The @dataclass decorator in the dataclasses module inspects class type annotations and dynamically generates boilerplate methods such as __init__, __repr__, and __eq__.

Next Lesson

Context Managers with __enter__ and __exit__

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext