Decorators and Metaclasses
|
This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A decorator is a callable that wraps another callable and returns a replacement for it — most of what looks like special syntax below is sugar for that one idea. Metaclasses are a separate, much rarer tool for controlling how classes themselves are built.
Function Decorators
What a decorator is: @decorator syntax
\@decorator above a def is shorthand for calling decorator on the function and rebinding the name
to its result. The glossary defines the term precisely under
decorator:
def decorator(func):
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result!r}")
return result
return wrapper
@decorator
def add(a, b):
return a + b
# equivalent, written out by hand:
# add = decorator(add)
print(add(2, 3))
# calling add
# add returned 5
# 5
Preserving metadata with functools.wraps
After decoration, add.name and add.doc belong to wrapper, not to the original function — confusing for introspection, help(), and tools that read those attributes. Decorating wrapper itself
with functools.wraps(func) copies them back. See
functools.wraps:
import functools
def logged(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logged
def add(a, b):
"""Return the sum of a and b."""
return a + b
print(add.__name__) # add (without @wraps this would be 'wrapper')
print(add.__doc__) # Return the sum of a and b.
Decorator Factories, Stacking, and Class Decorators
Decorators with arguments: a decorator factory
A decorator that itself takes arguments is a function returning a decorator — three levels deep: the factory, the decorator it returns, and the wrapper that decorator returns:
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def say_hi():
print("hi")
say_hi()
# hi
# hi
# hi
Stacking multiple decorators
Decorators stack bottom-up: the one closest to def wraps the function first, and each one above wraps
the result of the one below it. @a then @b above def f is f = a(b(f)):
def shout(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
def exclaim(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs) + "!"
return wrapper
@shout
@exclaim
def greet(name):
return f"hello, {name}"
print(greet("Ada")) # HELLO, ADA!
# order: exclaim runs first (adds "!"), then shout uppercases the whole result
Class decorators
A decorator can also wrap a class: it receives the class object and returns a class (often the same one, mutated, or a subclass). This is frequently the simpler alternative to a metaclass, covered next:
def add_repr(cls):
def __repr__(self):
return f"{cls.__name__}({self.__dict__})"
cls.__repr__ = __repr__
return cls
@add_repr
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
print(Point(1, 2)) # Point({'x': 1, 'y': 2})
Metaclasses
A class statement does not just define a template for instances — it also creates a class object,
and that creation is itself governed by a class: the class object’s own type, its metaclass. By
default that metaclass is the built-in type. This is documented, in depth, under
Customizing class creation:
class Point:
pass
print(type(Point)) # <class 'type'>
print(isinstance(Point, type)) # True
A custom metaclass subclasses type and overrides how classes are built, typically in new or
init; a class opts into it with the metaclass= keyword:
class Meta(type):
def __new__(mcs, name, bases, namespace):
namespace.setdefault("created_by", "Meta")
return super().__new__(mcs, name, bases, namespace)
class Widget(metaclass=Meta):
pass
print(Widget.created_by) # Meta
| You rarely need this. Metaclasses affect every subclass and interact poorly with other metaclasses, multiple inheritance, and most readers' intuitions. A class decorator (above) runs at the same point — right after the class body executes — with a plain function instead of a second class hierarchy, and covers the overwhelming majority of "customize class creation" use cases. Reach for a metaclass only when you must intercept inheritance itself (e.g. auto-registering every subclass), not merely to post-process one class. |
See Also
-
Functions —
def, parameters, and scope, which every decorator and wrapper builds on. -
Managed Attributes —
propertyand descriptors, the other main way Python customizes attribute access without a metaclass.