Metaclasses, Class Creation & init_subclass

In Python, Classes are Instances of Metaclasses. Just as an object instance is created by its class, a class object is created by its metaclass (type by default). Metaclasses control class instantiation, attribute validation, and registration at module load time.

This chapter details the 4-step CPython class creation pipeline, type.__new__ vs type.__init__, custom metaclass implementation, and the modern PEP 487 __init_subclass__ hook.


1. The CPython 4-Step Class Creation Pipeline

When Python executes a class MyClass(Base): block:

CPython Class Creation Pipeline:

[ 1. Evaluate Class Body Code ]
     (Executes top-level statements inside a new dictionary scope)
            |
            v
[ 2. Determine Metaclass (metaclass=Meta) ]
     (Defaults to 'type' if no explicit metaclass is specified)
            |
            v
[ 3. Invoke Meta.__new__(meta, name, bases, namespace) ]
     (Allocates the new PyTypeObject struct in C memory)
            |
            v
[ 4. Invoke Meta.__init__(cls, name, bases, namespace) ]
     (Initializes the class object namespace dictionary)

2. Metaclass Implementation Mechanics (type)

To write a custom metaclass, inherit from type and override __new__():

class ModelMetaclass(type):
    def __new__(cls, name, bases, namespace):
        # Intercept class creation: Ensure every ORM class defines a primary key!
        if name != "BaseModel":
            if "id" not in namespace:
                raise TypeError(f"Class '{name}' must define a primary key 'id'")

            # Transform namespace: Auto-inject table name if absent
            if "__tablename__" not in namespace:
                namespace["__tablename__"] = name.lower() + "s"

        # Delegate allocation to type.__new__()
        return super().__new__(cls, name, bases, namespace)

class BaseModel(metaclass=ModelMetaclass):
    pass

class User(BaseModel):
    id: int  # VALID: Passes primary key check; auto-injects __tablename__ = 'users'

3. Modern Lightweight Alternative: __init_subclass__ (PEP 487)

Writing full metaclasses adds complexity and risks metaclass conflict errors when inheriting across framework boundaries.

Introduced in PEP 487 (Python 3.6+), __init_subclass__ provides a simple hook inside standard classes to intercept and customize subclass creation without custom metaclasses:

class RegistryBase:
    _registry = {}

    # Called automatically whenever a subclass is defined!
    def __init_subclass__(cls, plugin_name: str | None = None, **kwargs):
        super().__init_subclass__(**kwargs)

        if plugin_name:
            cls._registry[plugin_name] = cls

# Subclasses register themselves cleanly via class parameters!
class JSONExporter(RegistryBase, plugin_name="json"):
    pass

class CSVExporter(RegistryBase, plugin_name="csv"):
    pass

print(RegistryBase._registry)
# {'json': <class '__main__.JSONExporter'>, 'csv': <class '__main__.CSVExporter'>}

4. Metaclass Conflicts & Rule of Thumb

  • Metaclass Conflict: Occurs when a class inherits from multiple parent classes that use different, non-derived metaclasses (TypeError: metaclass conflict).
  • Rule of Thumb (Tim Peters): “Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t.” Prefer __init_subclass__ or Class Decorators for 95% of class registration and attribute validation needs.
Display Options
Appearance
Text Size
100%