Skillversitas

Metaclasses

A metaclass is the class of a class - the object that creates classes and controls how they are created. You met the premise in the classes chapter (type(Book) is type); this chapter makes it a tool. A metaclass intercepts class creation - the moment class Foo(Base): executes - and can inspect, modify, or reject the class being built before it exists. The uses are real but narrow: enforcing interface contracts at definition time, auto-registering subclasses, generating methods from declarations, and building framework "models" (the ORM base class is the canonical example). The rule that keeps them sane: a metaclass is a framework mechanism, not a default design choice - most of the jobs people reach for it do are better done with a base class, a decorator, or __init_subclass__.

Classes Are Made by type

class Book:
    title = "X"

# defining a class is equivalent to calling type with three args:
# type(name, bases, namespace)
Book2 = type("Book2", (), {"title": "Y", "__module__": __name__})
print(Book2.title)        # 'Y' - a class, built without the class keyword
print(type(Book2))        # <class 'type'> - made by the default metaclass
print(issubclass(Book, object))  # True - everything descends from object,
                                 # and every class is an instance of type (or a metaclass)

The class statement is syntactic sugar for a type(name, bases, namespace) call: the body executes into a namespace dict (the methods, the class attributes), and type combines the name, the bases, and that namespace into a new class object. The first argument to type in that call is the metaclass - by default type itself, or a base's metaclass if one is specified. So "what makes a class" is itself a callable you can replace, and replacing it is what a metaclass is.

Defining a Metaclass

class RegistryMeta(type):
    """Records every class created with this metaclass."""
    registry: dict[str, type] = {}

    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        if name != "RegistryMixinBase" and bases:
            mcls.registry[name] = cls
        return cls

class RegistryMixinBase(metaclass=RegistryMeta):
    """Inherit this to be auto-registered."""

class Alpha(RegistryMixinBase):
    pass

class Beta(RegistryMixinBase):
    pass

print(sorted(RegistryMeta.registry))   # ['Alpha', 'Beta'] - both registered
# no class body, no register() call - the metaclass saw the creation

A metaclass is a subclass of type, and its __new__/__call__ run at class-creation time with the name, bases, and namespace in hand. The metaclass=RegistryMeta keyword on the base tells every inheriting class to be built by RegistryMeta, so Alpha and Beta register themselves by existing. The super().__new__ call does the real construction; the metaclass wraps it to add behavior. The three hooks to know: __new__ (create and return the class object - where structural checks and modifications belong), __init__ (initialize the already-created class object), and __call__ (what runs when someone calls the class - i.e. when an instance is made; this is where instance-creation policy lives). Most metaclasses touch only __new__.

Enforcing Contracts at Definition Time

class ApiMeta(type):
    REQUIRED = ("name", "version", "endpoints")

    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        if bases:  # skip the base itself
            missing = [a for a in mcls.REQUIRED if a not in namespace]
            if missing:
                raise TypeError(
                    f"{name} must define {missing}")
        return cls

class PluginBase(metaclass=ApiMeta):
    name = "base"
    version = "0"
    endpoints = ()

class Good(PluginBase):
    name = "auth"
    version = "1"
    endpoints = ("/login",)

class Bad(PluginBase):
    name = "broken"
    # version and endpoints missing

# Good() defines; Bad() raises TypeError AT CLASS DEFINITION
try:
    class Bad2(PluginBase):
        name = "broken2"
except TypeError as e:
    print(e)   # Bad2 must define ['version', 'endpoints']

This is the metaclass's most common legitimate job: a contract that must hold for every subclass, checked the moment the subclass is defined - earlier and more precisely than an ABC's instantiation-time check. The ABC says "you cannot make an instance until you implement the methods"; the metaclass says "you cannot even define the class until you declare the attributes." The error at definition time is the better failure mode for a plugin system - the broken plugin is caught when its module is imported, not when the first request hits it. The cost is the same as any metaclass: the logic runs for every class in the hierarchy, and it is harder to read than a base-class check.

The Lighter Alternatives

# __init_subclass__: a classmethod hook, NO metaclass needed
class TrackedBase:
    _all: list[type] = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        TrackedBase._all.append(cls)      # every subclass records itself

class A(TrackedBase): pass
class B(TrackedBase): pass
print([c.__name__ for c in TrackedBase._all])   # ['A', 'B']

# a class decorator: modify the class after it is built
def freeze(cls):
    cls.__setattr__ = lambda self, k, v: (_ for _ in ()).throw(
        AttributeError(f"{cls.__name__} is frozen"))
    return cls

@freeze
class Config:
    def __init__(self):
        self.x = 1

Two of the three metaclass jobs have lighter tools. __init_subclass__ is a classmethod that runs automatically when a class is subclassed - it receives the new class and can register it, check it, or configure it, with no metaclass in the MRO. It is the right tool for auto-registration and per-subclass setup, which is most of what people actually use metaclasses for. A class decorator runs once on the built class and can modify its namespace - the right tool for adding methods or enforcing a one-time transformation. The decision ladder: need to run at subclass time? __init_subclass__. Need to transform the class once? a decorator. Need to control class creation itself, or instance creation via __call__, or compose with another metaclass? a metaclass. Climb the ladder only as far as the job requires - a metaclass where __init_subclass__ would do is complexity the reader must pay for every class in the tree.

The Canonical Example: ORM Base Classes

Object-relational mappers (SQLAlchemy, the older Django-adjacent ORMs, and Peewee's own model system) are the standard real-world metaclass application. The model base class uses a metaclass that, at each model class's definition, collects the declared field attributes, turns them into column descriptors, builds the table schema, and wires up the query API - all before the first instance exists. The user writes a plain class User(Model): name = StringField() and the metaclass does the machinery. The reason it must be a metaclass (not __init_subclass__) is that the transformation is deep: it rewrites the class's namespace, creates descriptor objects, and sometimes needs to control how the class is called. When your design reaches that depth - "declaring a class should build a machine" - the metaclass is the right tool, and the ORM base is the template for reading one.

Summary

A metaclass is the class that creates classes: type(name, bases, namespace) is what the class statement compiles to, and subclassing type lets you intercept that creation in __new__ to inspect, modify, or reject the class. The legitimate uses are narrow - enforcing a contract at definition time (earlier than an ABC's instantiation check), auto-registering subclasses, and the deep namespace rewrites of an ORM base class. Before reaching for one, climb the ladder: __init_subclass__ for subclass-time hooks, a class decorator for a one-time transformation, and a metaclass only when you need to control creation itself or instance construction via __call__. It is a framework mechanism, not a default - used well it is how a declarative API is built, used by habit it is a tax on every class in the hierarchy.