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
A def func wrapped by @decorator into a wrapper function, followed by a call sequence: caller calls func (really wrapper), wrapper calls the original func, then control returns from func to wrapper and from wrapper back to the caller

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 — property and descriptors, the other main way Python customizes attribute access without a metaclass.