Skip to content

Metaclasses

Metaclasses are often oversold. The real skill is not "knowing metaclasses" but knowing where class decorators, descriptors, and `__init_subclass__` stop being enough.

Quick takeaway: use a metaclass only when you need to change class creation itself. For registration, validation, or light post-processing, simpler hooks usually work better.

Start With the Creation Timeline

A class statement looks declarative, but Python actually executes the class body, builds a namespace, and then constructs a class object.

Why It Matters

  • Frameworks that feel "magical" usually hook into class creation.
  • You need the right tool for the job: decorator, descriptor, __init_subclass__, or metaclass.
  • Once you see the timeline, declarative ORM or validation APIs stop feeling mysterious.

Pick the Smallest Tool

ToolUsual purposeConsider before a metaclass?
Class decoratorPost-process a classYes
__init_subclass__Register or validate subclassesYes
DescriptorControl field access and bindingYes
MetaclassChange how class objects are createdLast resort

Example: Declarative Registration

py
class PluginRegistry(type):
    registry: dict[str, type] = {}

    def __new__(
        mcls,
        name: str,
        bases: tuple[type, ...],
        namespace: dict[str, object],
    ) -> type:
        cls = super().__new__(mcls, name, bases, namespace)
        plugin_name = namespace.get("plugin_name")
        if isinstance(plugin_name, str):
            mcls.registry[plugin_name] = cls
        return cls


class PluginBase(metaclass=PluginRegistry):
    plugin_name: str


class JsonPlugin(PluginBase):
    plugin_name = "json"


class CsvPlugin(PluginBase):
    plugin_name = "csv"


print(PluginRegistry.registry)

This works well for declarative registration. But if registration is the only goal, `__init_subclass__()` is often simpler and easier for library users to compose.

When It Is Worth It

Good fit

You need to enforce class-creation policy or interpret class declarations as a DSL.

Bad fit

You only need light validation, registration, or attribute rewrites.

Tradeoff

Metaclasses affect inheritance and composition, so they raise the cost for downstream users.

Read Before This

Built with VitePress for a Python 3.14 handbook.