Python class decorators come in two kinds: a function placed above a class statement that receives the class and returns it (often changed), and a class whose instances wrap functions through __call__. Here’s the first kind in four lines:
def register(cls):
REGISTRY[cls.__name__] = cls
return cls
I tested each decorator here on Python 3.12.5, and the screenshots are straight from Command Prompt.
I’ll cover registering classes, adding attributes and methods, a singleton, the built-in @dataclass and @total_ordering, classes used as decorators, and the order stacked decorators run in.
What is a class decorator in Python?
@register above class Csv is shorthand for Csv = register(Csv). It runs once, right after the class is created, so it’s a natural place to record every class in a plugin registry:
EXPORTERS = {}
def register(cls):
EXPORTERS[cls.__name__.lower()] = cls
return cls # hand the same class back
@register
class Csv:
def export(self, total):
return f"csv,{total:.2f}"
@register
class Json:
def export(self, total):
return f'{{"total": {total:.2f}}}'
print(list(EXPORTERS))
for name, cls in EXPORTERS.items():
print(name, "->", cls().export(1249.5))
Output:
['csv', 'json']
csv -> csv,1249.50
json -> {"total": 1249.50}

Before Python 3, jobs like this needed a metaclass. A decorator is simpler, though it doesn’t reach subclasses; my Python metaclasses tutorial explains when you still need one.
Add attributes and methods with a class decorator
A decorator can also change the class it gets. To pass settings, wrap it in one more function, so @tag("sales") first returns the real decorator:
def tag(team, region="US"):
def decorate(cls):
cls.team = team
cls.region = region
cls.__repr__ = lambda self: f"{cls.__name__}({vars(self)})"
return cls
return decorate
@tag("sales", region="TX")
class Order:
def __init__(self, customer, amount):
self.customer = customer
self.amount = amount
o = Order("Emily", 89.99)
print(o)
print(Order.team, Order.region)
Output:
Order({'customer': 'Emily', 'amount': 89.99})
sales TX
The decorator set two class attributes and replaced __repr__. Doing this by name at runtime is covered in dynamic attributes with setattr() and getattr().
Build a singleton with a class decorator
A decorator doesn’t have to return a class. This one returns a function that always hands back the first instance, which is a common way to share one settings object:
def singleton(cls):
instances = {}
def get(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get
@singleton
class Settings:
def __init__(self, currency="USD"):
self.currency = currency
a = Settings()
b = Settings(currency="EUR") # ignored: the first object is reused
print(a is b, b.currency)
print(type(Settings).__name__)
try:
isinstance(a, Settings)
except TypeError as err:
print("TypeError:", err)
Output:
True USD
function
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union

Settings is now a function, so isinstance() fails.That TypeError is the catch: the name no longer points to a class. If you need isinstance() to work, put the logic in __new__ instead; see __new__ vs __init__ in Python.
Built-in class decorators: @dataclass and @total_ordering
The standard library ships class decorators you probably already use. @dataclass writes __init__, __repr__ and __eq__ for you, and @total_ordering fills in the missing comparison methods:
from dataclasses import dataclass
from functools import total_ordering
@dataclass
class Invoice:
number: str
total: float
print(Invoice("INV-1001", 250.0))
print(Invoice("INV-1001", 250.0) == Invoice("INV-1001", 250.0))
@total_ordering
class Version:
def __init__(self, major, minor):
self.key = (major, minor)
def __eq__(self, other):
return self.key == other.key
def __lt__(self, other):
return self.key < other.key
print(Version(3, 12) >= Version(3, 9), Version(2, 7) > Version(3, 0))
Output:
Invoice(number='INV-1001', total=250.0)
True
True False

Version supports >= and > though it only defines two methods.For defaults, ordering and frozen objects, my Python dataclass guide goes further.
How do you use a class as a decorator?
Flip it around and a class can decorate a function. __init__ receives the function and __call__ runs on every call, so the instance can keep state such as a call count:
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func) # copy __name__, __doc__
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
print(f"call {self.calls} of {self.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def sales_tax(amount, rate=0.0825):
"""Texas sales tax."""
return round(amount * rate, 2)
print(sales_tax(100))
print(sales_tax(59.99))
print(sales_tax.__name__, "|", sales_tax.__doc__, "|", sales_tax.calls)
Output:
call 1 of sales_tax
8.25
call 2 of sales_tax
4.95
sales_tax | Texas sales tax. | 2

functools.update_wrapper() keeps the original name and docstring.Without update_wrapper(), sales_tax.__name__ raises AttributeError, because instances don’t have one. Objects with __call__ count as callable; see the callable() function in Python.
In what order do stacked class decorators run?
Decorators apply from the bottom up: the one closest to class runs first, and its result goes to the one above:
def mark(label):
def decorate(cls):
print("applying", label)
cls.labels = getattr(cls, "labels", []) + [label]
return cls
return decorate
@mark("outer")
@mark("inner")
class Report:
pass
print(Report.labels)
Output:
applying inner
applying outer
['inner', 'outer']
So @mark("outer") @mark("inner") means mark("outer")(mark("inner")(Report)). Method decorators like the @property decorator follow the same rule.
You might like these class guides next:
- The classmethod() function in Python
- The __dict__ attribute of a Python class
- isinstance() and issubclass() examples
- Get the name of a function in Python
Frequently asked questions
What is a class decorator in Python?
A function that takes a class and returns a class (or a replacement), applied with @name above the class statement.
Do class decorators affect subclasses?
No. The decorator runs once on the decorated class; subclasses inherit what it added but aren’t decorated again.
How do I pass arguments to a class decorator?
Write a function that takes the arguments and returns the real decorator, like @tag("sales").
Why does isinstance() fail after a singleton decorator?
The decorator returned a function, so the name isn’t a class anymore. Use __new__ for the singleton instead.
What does functools.wraps do in a class-based decorator?
Use functools.update_wrapper(self, func) in __init__ to copy the name and docstring onto the instance.
Where are update_wrapper and total_ordering documented?
See the functools module in the Python docs.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn