stackademic

The leading education platform for anyone with an interest in software development.

Python Metaclasses

Control class creation itself using type, __new__, and metaclass hooks

Overview

A metaclass is the "class of a class"—it controls how a class object is constructed, just as a class controls how instances are constructed. The default metaclass is type; customizing it lets you inject attributes, validate definitions, or register subclasses at creation time. Metaclasses are powerful but rarely needed; prefer decorators or __init_subclass__ unless you truly must intercept class creation.

Syntax / Usage

Subclass type and override __new__ or __init__, then attach it with metaclass=.

class Meta(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        # namespace holds the class body (methods, attributes)
        namespace["created_by"] = "Meta"
        cls = super().__new__(mcs, name, bases, namespace)
        return cls

class Service(metaclass=Meta):
    pass

print(Service.created_by)  # 'Meta'

type(name, bases, namespace) can also build a class dynamically without the class keyword.

Examples

Auto-register every subclass in a central registry—useful for plugin systems:

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

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:  # skip the base class itself
            PluginMeta.registry[name.lower()] = cls
        return cls

class Plugin(metaclass=PluginMeta):
    pass

class ExportPlugin(Plugin):
    pass

print(PluginMeta.registry)  # {'exportplugin': <class 'ExportPlugin'>}

Enforce an interface at definition time, failing fast before any instance exists:

class RequiresRun(type):
    def __new__(mcs, name, bases, namespace):
        if bases and "run" not in namespace:
            raise TypeError(f"{name} must define run()")
        return super().__new__(mcs, name, bases, namespace)

class Task(metaclass=RequiresRun):
    pass

class Job(Task):
    def run(self) -> None:
        print("working")

Common Mistakes

  • Reaching for a metaclass when __init_subclass__ or a class decorator would be simpler and clearer
  • Mixing incompatible metaclasses via multiple inheritance, triggering a metaclass conflict TypeError
  • Confusing __new__ (creates the class object) with __init__ (initializes it)
  • Forgetting to call super().__new__, so the class is never actually built
  • Storing mutable shared state on the metaclass without realizing it is shared across all classes

See Also

python-classes python-decorators python-type-hints