🟠 Python Internals

Metaclasses & the Class Creation Protocol

📖 Lesson 46 ⏱ 60 min 🧪 5 questions 💻 3 exercises

🎯 Learning Objectives

  • Explain the type hierarchy: type is both a class and its own metaclass; every class is an instance of type
  • Trace the class creation protocol step-by-step: __prepare__, __new__, __init__
  • Write a custom metaclass that enforces structural rules (abstract methods, naming conventions, required attributes)
  • Use __init_subclass__ as a lightweight alternative to metaclasses for subclass hooks
  • Use __class_getitem__ to implement generic-style syntax (MyClass[T])
  • Build a plugin/registry system using metaclass hooks
  • Understand the metaclass conflict problem and resolution with __mro_entries__

1 · Everything is an Instance of type

In Python, classes are first-class objects — instances of type. This is the foundation of the entire metaclass system. Every class you define (or that ships with the standard library) is literally an object whose type is type.

# Every class is an instance of type
print(type(int))      # <class 'type'>
print(type(str))      # <class 'type'>
print(type(list))     # <class 'type'>

class MyClass:
    pass

print(type(MyClass))  # <class 'type'>
print(isinstance(MyClass, type))  # True

# type is its own metaclass
print(type(type))     # <class 'type'>

# The type hierarchy
print(issubclass(type, object))   # True
print(issubclass(object, type))   # False
print(isinstance(type, object))   # True — type is an instance of object
type_hierarchy.py

The relationship between type and object forms a curious loop: type is a subclass of object, but object is an instance of type. This bootstrap cycle is hard-wired into the CPython interpreter at the C level.

┌─────────────────────────────────────────────┐
│  instance-of (type)        subclass-of (is-a)│
├─────────────────────────────────────────────┤
│                                              │
│  int  ──instance-of──▶  type                │
│  str  ──instance-of──▶  type                │
│  MyClass ─instance-of──▶ type               │
│  type ──instance-of──▶  type  (self-ref!)   │
│                                              │
│  int  ──subclass-of──▶  object              │
│  str  ──subclass-of──▶  object              │
│  type ──subclass-of──▶  object              │
│  object ──instance-of──▶ type               │
└─────────────────────────────────────────────┘

Because type is itself callable, you can use it directly to create classes dynamically — no class statement needed:

# type(name, bases, namespace) creates a new class
MyDynamic = type("MyDynamic", (object,), {
    "x": 42,
    "greet": lambda self: f"Hello from {type(self).__name__}",
})

obj = MyDynamic()
print(obj.greet())   # Hello from MyDynamic
print(obj.x)         # 42
print(type(MyDynamic))   # <class 'type'>

# This is EXACTLY what the `class` statement does internally
dynamic_class.py
Key insight: The class statement is syntactic sugar for a type() call (or a custom metaclass call). When Python sees class Foo(Bar):, it: (1) collects the class body namespace, (2) determines the metaclass, (3) calls metaclass(name, bases, namespace). The default metaclass is type.

2 · The Class Creation Protocol

When Python executes a class statement, the following sequence runs internally. Understanding this protocol is essential for writing correct metaclasses:

class Foo(Base, metaclass=Meta):
    body...

Internally:
  1. Determine metaclass:
       meta = Meta (explicit) or type(Base) or type

  2. Prepare namespace:
       namespace = meta.__prepare__("Foo", (Base,), **kwargs)
       # Returns a dict (or custom mapping) used to execute the class body

  3. Execute class body:
       exec(body, globals, namespace)
       # All assignments in the body land in `namespace`

  4. Create the class object:
       Foo = meta.__new__(meta, "Foo", (Base,), namespace)

  5. Initialise the class object:
       meta.__init__(Foo, "Foo", (Base,), namespace)

  6. Call __set_name__ on all descriptors in namespace
  7. Call Base.__init_subclass__(cls=Foo, **kwargs) on each base

We can observe every step by writing a tracing metaclass:

class TracingMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        print(f"  __prepare__({name!r}, {[b.__name__ for b in bases]})")
        return super().__prepare__(name, bases, **kwargs)   # returns a dict

    def __new__(mcs, name, bases, namespace, **kwargs):
        print(f"  __new__({name!r}, attrs={list(namespace.keys())})")
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace, **kwargs):
        print(f"  __init__({name!r})")
        super().__init__(name, bases, namespace)

class Base(metaclass=TracingMeta):
    x = 1

# Output:
#   __prepare__('Base', [])
#   __new__('Base', attrs=['__module__', '__qualname__', 'x'])
#   __init__('Base')

class Child(Base):
    y = 2

# Output:
#   __prepare__('Child', ['Base'])
#   __new__('Child', attrs=['__module__', '__qualname__', 'y'])
#   __init__('Child')
tracing_meta.py
Why __prepare__ matters: It is called before the class body executes and returns the namespace dict that will be populated. This is how OrderedDict-based metaclasses preserve field declaration order — and how the enum module tracks insertion order of members.

3 · Writing a Metaclass

A practical metaclass enforces structural rules at class-creation time — before any instance is ever created. This moves many categories of bugs from runtime to definition time:

import re

class InterfaceMeta(type):
    """
    Enforces that every concrete (non-abstract) subclass:
    1. Implements all methods listed in `__required_methods__`
    2. Uses snake_case names for public methods
    3. Has a docstring
    """
    _SNAKE = re.compile(r'^[a-z_][a-z0-9_]*$')

    def __new__(mcs, name, bases, namespace, **kwargs):
        cls = super().__new__(mcs, name, bases, namespace)

        # Skip the base class itself
        if not bases:
            return cls

        required = set()
        for base in bases:
            required.update(getattr(base, '__required_methods__', []))

        # Check all required methods are implemented
        for method_name in required:
            if not callable(getattr(cls, method_name, None)):
                raise TypeError(
                    f"Class '{name}' must implement '{method_name}()'"
                )

        # Enforce snake_case on public methods
        for attr, val in namespace.items():
            if callable(val) and not attr.startswith('_'):
                if not mcs._SNAKE.match(attr):
                    raise NameError(
                        f"Method '{attr}' in '{name}' must be snake_case"
                    )

        # Enforce docstring
        if not cls.__doc__:
            raise TypeError(f"Class '{name}' must have a docstring")

        return cls


class Storage(metaclass=InterfaceMeta):
    """Abstract base for storage backends."""
    __required_methods__ = ["read", "write", "delete"]


class FileStorage(Storage):
    """File-based storage backend."""

    def read(self, key: str) -> bytes:
        """Read bytes from file."""
        return open(key, "rb").read()

    def write(self, key: str, data: bytes) -> None:
        """Write bytes to file."""
        open(key, "wb").write(data)

    def delete(self, key: str) -> None:
        """Delete a file."""
        import os; os.unlink(key)


# This will raise TypeError — missing `delete`
try:
    class BadStorage(Storage):
        """Missing delete method."""
        def read(self, key): pass
        def write(self, key, data): pass
except TypeError as e:
    print(e)  # Class 'BadStorage' must implement 'delete()'
interface_meta.py
Tip: Keep metaclass logic in __new__ (not __init__) when you need to modify or reject the class before it fully exists. Use __init__ for post-creation setup that doesn't need to alter the class object itself.

4 · __init_subclass__: The Lightweight Alternative

Python 3.6+ provides __init_subclass__ — called on the parent class each time it is subclassed. It handles 90% of metaclass use cases without the complexity of writing a full metaclass.

class PluginBase:
    """
    Subclasses automatically register themselves in _registry
    via __init_subclass__.
    """
    _registry: dict[str, type] = {}

    def __init_subclass__(cls, plugin_name: str = None, **kwargs):
        super().__init_subclass__(**kwargs)   # always call super()
        name = plugin_name or cls.__name__.lower()
        if name in PluginBase._registry:
            raise ValueError(f"Plugin '{name}' already registered")
        PluginBase._registry[name] = cls
        print(f"  Registered plugin: '{name}' → {cls.__name__}")

class JSONPlugin(PluginBase, plugin_name="json"):
    def process(self, data): return f"JSON: {data}"

class XMLPlugin(PluginBase, plugin_name="xml"):
    def process(self, data): return f"XML: {data}"

class CSVPlugin(PluginBase):          # uses class name as key: 'csvplugin'
    def process(self, data): return f"CSV: {data}"

print(PluginBase._registry)
# {'json': JSONPlugin, 'xml': XMLPlugin, 'csvplugin': CSVPlugin}

plugin = PluginBase._registry["json"]()
print(plugin.process({"key": "value"}))
plugin_registry.py

Notice how __init_subclass__ receives keyword arguments directly from the class statement — no metaclass machinery required:

class Validator(PluginBase, plugin_name="validate"):
    """Receives plugin_name='validate' from the class statement."""
    def process(self, data): return f"VALID: {data}"
plugin_kwargs.py
Why prefer __init_subclass__? It is simpler than metaclasses because: it lives on the class itself (no separate metaclass hierarchy), it receives keyword arguments from the class statement, it always calls super().__init_subclass__(**kwargs) to propagate to other classes in the MRO, and it avoids metaclass conflict issues entirely.

5 · __class_getitem__ for Generic Syntax

__class_getitem__ enables MyClass[T] syntax without importing anything from typing. This is the mechanism that powers list[int] and dict[str, int] in Python 3.9+:

from __future__ import annotations
from typing import TypeVar, Generic

T = TypeVar("T")

# ── Standard library Generic uses __class_getitem__ ──
# list[int], dict[str, int] work because list.__class_getitem__ returns a GenericAlias

print(list[int])         # list[int]   — a types.GenericAlias
print(dict[str, int])    # dict[str, int]

# ── Custom generic class ──
class Stack:
    """A typed stack using __class_getitem__."""

    def __class_getitem__(cls, item):
        """Called when Stack[int] is written."""
        # Return a GenericAlias — allows Stack[int] as a type hint
        import types
        return types.GenericAlias(cls, (item,))

    def __init__(self):
        self._data: list = []

    def push(self, item) -> None:
        self._data.append(item)

    def pop(self):
        return self._data.pop()

    def __repr__(self):
        return f"Stack({self._data!r})"

# Now Stack[int] is valid in type hints
def process(stack: Stack[int]) -> int:
    return stack.pop()

s: Stack[int] = Stack()
s.push(1); s.push(2)
print(process(s))        # 2
print(Stack[int])        # stack.Stack[int]
class_getitem_basic.py

For more advanced use cases, __class_getitem__ can return a dynamically-created class that carries metadata about the type parameter:

# ── Metadata-carrying generic alias ──
class TypedCollection:
    _item_type: type = object

    def __class_getitem__(cls, item_type):
        new_cls = type(
            f"{cls.__name__}[{item_type.__name__}]",
            (cls,),
            {"_item_type": item_type},
        )
        return new_cls

    def append(self, item):
        if not isinstance(item, type(self)._item_type):
            raise TypeError(
                f"Expected {type(self)._item_type.__name__}, "
                f"got {type(item).__name__}"
            )
        self._items = getattr(self, "_items", [])
        self._items.append(item)

IntCollection = TypedCollection[int]
c = IntCollection()
c.append(42)
try:
    c.append("hello")
except TypeError as e:
    print(e)  # Expected int, got str
typed_collection.py

6 · __prepare__ and Ordered Namespaces

__prepare__ lets you substitute the default dict namespace with a custom mapping before the class body executes. This is how enum.EnumType tracks member insertion order:

from collections import OrderedDict

class OrderedMeta(type):
    """Metaclass that preserves declaration order of all attributes."""

    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        return OrderedDict()   # class body written into an OrderedDict

    def __new__(mcs, name, bases, namespace, **kwargs):
        # namespace is the OrderedDict we returned from __prepare__
        cls = super().__new__(mcs, name, bases, dict(namespace))
        cls._field_order = [
            k for k in namespace
            if not k.startswith("__") and not callable(namespace[k])
        ]
        return cls

class Schema(metaclass=OrderedMeta):
    """Preserves field declaration order."""
    first_name: str = ""
    last_name:  str = ""
    age:        int = 0
    email:      str = ""

    def full_name(self):
        return f"{self.first_name} {self.last_name}"

print(Schema._field_order)
# ['first_name', 'last_name', 'age', 'email'] — guaranteed order
ordered_meta.py

A more powerful pattern: intercept every attribute assignment during class body execution using a custom namespace that overrides __setitem__:

# ── Counting class: use a custom namespace to auto-number fields ──
class NumberedNamespace(dict):
    def __init__(self):
        super().__init__()
        self._counter = 0

    def __setitem__(self, key, value):
        if not key.startswith("_") and isinstance(value, str) and value == "auto":
            value = self._counter
            self._counter += 1
        super().__setitem__(key, value)

class NumberedMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        return NumberedNamespace()

class Fields(metaclass=NumberedMeta):
    first = "auto"
    second = "auto"
    third = "auto"

print(Fields.first, Fields.second, Fields.third)   # 0 1 2
numbered_namespace.py
Note on Python 3.7+: Regular class namespaces already preserve insertion order (since dict became ordered). __prepare__ is now mainly needed when you want a custom mapping type — e.g. to intercept attribute assignments during class body execution (as shown in the NumberedNamespace example above).

7 · Metaclass Conflict & __mro_entries__

When inheriting from two classes with different metaclasses, Python raises a TypeError. This is the infamous "metaclass conflict" — the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases:

class MetaA(type):
    def __new__(mcs, name, bases, ns):
        print(f"MetaA creating {name}")
        return super().__new__(mcs, name, bases, ns)

class MetaB(type):
    def __new__(mcs, name, bases, ns):
        print(f"MetaB creating {name}")
        return super().__new__(mcs, name, bases, ns)

class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass

# This raises: TypeError: metaclass conflict: the metaclass of a derived class
# must be a (non-strict) subclass of the metaclasses of all its bases
try:
    class C(A, B): pass
except TypeError as e:
    print(e)

# Fix: create a combined metaclass
class MetaAB(MetaA, MetaB): pass   # inherits from both

class C(A, B, metaclass=MetaAB): pass   # now works
metaclass_conflict.py

__mro_entries__ is a lesser-known protocol that lets non-class objects appear in base class lists. When Python encounters an object in the bases tuple that is not a class, it calls obj.__mro_entries__(bases) to get the actual classes to insert into the MRO:

class GenericBase:
    """Supports MyClass[T] in inheritance: class Foo(GenericBase[int])."""

    def __class_getitem__(cls, params):
        class _GenericAlias:
            def __mro_entries__(self, bases):
                # Replace this alias with the actual class in the MRO
                return (cls,)
            def __repr__(self):
                return f"{cls.__name__}[{params!r}]"
        return _GenericAlias()

class IntList(GenericBase[int]):
    """MRO resolves to: IntList → GenericBase → object."""
    pass

print(IntList.__bases__)    # (<class 'GenericBase'>,)
print(IntList.__mro__)      # (IntList, GenericBase, object)
mro_entries.py
When to use metaclasses: Metaclasses are powerful but add significant cognitive overhead. Python core developers (including Guido) recommend reaching for them only when __init_subclass__, class decorators, or __set_name__ cannot solve the problem. The bar is: "Does this need to run at class creation time and affect the class object itself?" If the answer is yes, and simpler hooks won't do, then a metaclass is justified.

Singleton & Borg Patterns via Metaclass

Metaclasses are the cleanest way to implement the Singleton pattern in Python — no module-level tricks or __new__ hacks needed.

class SingletonMeta(type):
    """Metaclass that ensures only one instance per class."""
    _instances: dict = {}

    def __call__(cls, *args, **kwargs):
        # __call__ on the metaclass intercepts instance creation
        if cls not in cls._instances:
            # super().__call__ runs cls.__new__ then cls.__init__
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]


class Config(metaclass=SingletonMeta):
    def __init__(self, debug: bool = False):
        self.debug = debug

c1 = Config(debug=True)
c2 = Config()
print(c1 is c2)    # True — same object
print(c2.debug)    # True — init only ran once

# Reset for testing
del SingletonMeta._instances[Config]
c3 = Config(debug=False)
print(c3 is c1)    # False — fresh instance


class BorgMeta(type):
    """
    Borg pattern: every instance is distinct but shares __dict__.
    All instances share state without being the same object.
    """
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        cls._shared_state: dict = {}
        return cls

    def __call__(cls, *args, **kwargs):
        instance = cls.__new__(cls, *args, **kwargs)
        instance.__dict__ = cls._shared_state   # share the dict
        instance.__init__(*args, **kwargs)
        return instance


class AppState(metaclass=BorgMeta):
    def __init__(self, **kw):
        self.__dict__.update(kw)

s1 = AppState(x=1)
s2 = AppState(y=2)
print(s1 is s2)     # False — distinct objects
print(s1.y)         # 2 — shared state
print(s2.x)         # 1 — shared state
singleton_borg.py
In practice, the simplest singleton in Python is a module — Python imports each module only once and caches it in sys.modules, so module-level state is effectively a singleton. Reach for SingletonMeta only when you need multiple distinct singleton classes or testability via _instances reset.

Abstract Base Classes Without abc

The abc module is itself built on metaclasses. Here is how to build the same mechanism from first principles — and then how to use abc.ABCMeta properly.

class AbstractMeta(type):
    """
    Marks methods decorated with @abstract as requiring implementation.
    Raises TypeError when an abstract class is instantiated directly.
    """
    def __new__(mcs, name, bases, namespace):
        abstract_methods = set()

        # Collect from bases
        for base in bases:
            abstract_methods.update(getattr(base, "_abstract_methods", set()))

        # Add newly marked abstracts, remove ones that are implemented
        for k, v in namespace.items():
            if getattr(v, "_abstract", False):
                abstract_methods.add(k)
            elif k in abstract_methods:
                abstract_methods.discard(k)

        cls = super().__new__(mcs, name, bases, namespace)
        cls._abstract_methods = frozenset(abstract_methods)
        return cls

    def __call__(cls, *args, **kwargs):
        if cls._abstract_methods:
            raise TypeError(
                f"Cannot instantiate abstract class '{cls.__name__}' — "
                f"unimplemented: {sorted(cls._abstract_methods)}"
            )
        return super().__call__(*args, **kwargs)


def abstract(fn):
    """Decorator to mark a method as abstract."""
    fn._abstract = True
    return fn


class Shape(metaclass=AbstractMeta):
    @abstract
    def area(self) -> float: ...

    @abstract
    def perimeter(self) -> float: ...

    def describe(self) -> str:
        return f"{type(self).__name__}: area={self.area():.2f}"


class Circle(Shape):
    def __init__(self, r: float): self.r = r
    def area(self)      -> float: import math; return math.pi * self.r ** 2
    def perimeter(self) -> float: import math; return 2 * math.pi * self.r


c = Circle(5)
print(c.describe())   # Circle: area=78.54

try:
    Shape()
except TypeError as e:
    print(e)   # Cannot instantiate abstract class 'Shape' — unimplemented: ['area', 'perimeter']

# ── Real ABCMeta (stdlib) ──
from abc import ABCMeta, abstractmethod

class Repository(metaclass=ABCMeta):
    @abstractmethod
    def find(self, id: int): ...

    @abstractmethod
    def save(self, entity) -> None: ...

    def find_all(self) -> list:   # concrete method
        return []
abstract_meta.py

Real-World Example: Django-Style Model Metaclass

Django's ModelBase metaclass is one of the most studied metaclasses in Python. Here is a stripped-down version showing the core techniques:

class FieldDescriptor:
    """Data descriptor for a model field."""
    def __set_name__(self, owner, name):
        self.name = name
        self.private = f"_{name}"

    def __init__(self, field_type: type, **opts):
        self.field_type = field_type
        self.opts = opts

    def __get__(self, obj, objtype=None):
        if obj is None: return self
        return getattr(obj, self.private, None)

    def __set__(self, obj, value):
        if value is not None and not isinstance(value, self.field_type):
            raise TypeError(f"Field '{self.name}': expected {self.field_type.__name__}")
        setattr(obj, self.private, value)


class ModelMeta(type):
    """
    Metaclass that:
    1. Collects all FieldDescriptor declarations into _meta.fields
    2. Injects a primary key if none is declared
    3. Generates __repr__ automatically
    4. Provides a classmethod .objects.all() stub
    """

    def __new__(mcs, name, bases, namespace, **kwargs):
        fields = {}

        # Inherit fields from base classes
        for base in bases:
            fields.update(getattr(base, "_fields", {}))

        # Collect FieldDescriptor instances from current namespace
        for k, v in list(namespace.items()):
            if isinstance(v, FieldDescriptor):
                fields[k] = v

        # Auto-inject 'id' primary key if not declared
        if "id" not in fields and name != "Model":
            id_field = FieldDescriptor(int, primary_key=True)
            id_field.name    = "id"
            id_field.private = "_id"
            namespace["id"]  = id_field
            fields["id"]     = id_field

        namespace["_fields"] = fields
        cls = super().__new__(mcs, name, bases, namespace)

        # Inject __repr__
        def _repr(self):
            parts = ", ".join(
                f"{k}={getattr(self, k)!r}"
                for k in self._fields
                if getattr(self, k) is not None
            )
            return f"{type(self).__name__}({parts})"
        cls.__repr__ = _repr

        return cls


class Model(metaclass=ModelMeta):
    """Base model class."""
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)


class User(Model):
    username = FieldDescriptor(str)
    email    = FieldDescriptor(str)
    age      = FieldDescriptor(int)


class Post(Model):
    title   = FieldDescriptor(str)
    body    = FieldDescriptor(str)
    user_id = FieldDescriptor(int)


u = User(username="alice", email="alice@example.com", age=30)
print(u)           # User(id=None, username='alice', email='alice@example.com', age=30)

p = Post(title="Hello", body="World", user_id=1)
print(p)           # Post(id=None, title='Hello', body='World', user_id=1)

print(User._fields.keys())   # dict_keys(['id', 'username', 'email', 'age'])
django_style.py
This is essentially how Django's ModelBase works: it iterates the namespace collecting Field objects, builds _meta.fields, auto-injects pk, and contributes each field to the class. SQLAlchemy's DeclarativeMeta uses the same pattern to collect Column objects and map them to database columns.

Best Practices

  • Prefer __init_subclass__ over metaclasses for most subclass hooks — it is simpler, avoids metaclass conflicts, and is easier to test. Only use a metaclass when you need to intercept __prepare__, modify the class object itself in __new__, or hook into __call__ (instance creation).
  • Always call super() in every metaclass hooksuper().__prepare__, super().__new__, super().__init__ — to stay cooperative with other metaclasses in the chain.
  • Always call super().__init_subclass__(**kwargs) and forward all unused kwargs — this enables cooperative multiple inheritance with other __init_subclass__ hooks higher in the MRO.
  • Use __set_name__ instead of metaclass field-collection when descriptors just need their own name — __set_name__ is called automatically and needs no metaclass at all.
  • Resolve metaclass conflicts early — create a combined metaclass (class CombinedMeta(MetaA, MetaB): pass) and pass it explicitly rather than letting Python raise TypeError at class creation time.
  • Document metaclass behaviour prominently — metaclasses work at class-creation time, which is non-obvious. A clear docstring explaining what the metaclass injects or enforces is essential for maintainability.
  • Use type.__new__ carefully — do all structural modifications (adding methods, registering fields) in __new__, not __init__, because __new__ receives the namespace before descriptors run __set_name__.
  • Test metaclass behaviour with minimal classes — write unit tests that create tiny classes using the metaclass and assert on their structure; do not rely on integration tests to catch metaclass bugs.

Exercises

Exercise 1 — Registry Metaclass

Build a HandlerMeta metaclass that automatically registers subclasses in a central registry, keyed by a command class attribute:

  • The metaclass maintains a class-level _registry: dict[str, type].
  • When any subclass of BaseHandler is created, if it has a command attribute, register it under that key.
  • Provide a get_handler(command: str) -> BaseHandler classmethod that looks up and instantiates the handler.
  • Raise KeyError with a helpful message if the command is not registered.
  • Test: define three handler subclasses; verify all three are in the registry; verify get_handler("list")() returns the right instance.
💡 Hint
class HandlerMeta(type):
    _registry: dict[str, type] = {}

    def __new__(mcs, name, bases, namespace, **kwargs):
        cls = super().__new__(mcs, name, bases, namespace)
        if "command" in namespace:
            cmd = namespace["command"]
            if cmd in mcs._registry:
                raise ValueError(f"Command '{cmd}' already registered")
            mcs._registry[cmd] = cls
        return cls

class BaseHandler(metaclass=HandlerMeta):
    @classmethod
    def get_handler(cls, command: str) -> "BaseHandler":
        try:
            return HandlerMeta._registry[command]()
        except KeyError:
            available = sorted(HandlerMeta._registry.keys())
            raise KeyError(f"Unknown command {command!r}. Available: {available}")

class ListHandler(BaseHandler):
    command = "list"
    def run(self): return "listing items"

class CreateHandler(BaseHandler):
    command = "create"
    def run(self): return "creating item"

h = BaseHandler.get_handler("list")
print(h.run())   # listing items

Exercise 2 — Declarative Schema with __prepare__

Build a SchemaMeta metaclass that uses a custom __prepare__ namespace to track field declaration order and auto-generate __init__, __repr__, and validate():

  • The custom namespace records the order that Field objects are assigned.
  • __init__ accepts keyword arguments for every declared Field.
  • validate() checks that all non-optional fields have values.
  • Test with a UserSchema having name (required) and age (optional, default=None).
💡 Hint
from collections import OrderedDict

class Field:
    def __init__(self, typ, *, required=True, default=None):
        self.typ = typ
        self.required = required
        self.default = default

class FieldOrderNamespace(OrderedDict):
    def __init__(self):
        super().__init__()
        self._fields = OrderedDict()
    def __setitem__(self, k, v):
        if isinstance(v, Field):
            self._fields[k] = v
        super().__setitem__(k, v)

class SchemaMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kw):
        return FieldOrderNamespace()

    def __new__(mcs, name, bases, ns, **kw):
        fields = dict(ns._fields)
        cls = super().__new__(mcs, name, bases, dict(ns))
        cls._schema_fields = fields

        def __init__(self, **kwargs):
            for fname, field in fields.items():
                setattr(self, fname, kwargs.get(fname, field.default))
        def __repr__(self):
            parts = ", ".join(f"{k}={getattr(self,k)!r}" for k in fields)
            return f"{type(self).__name__}({parts})"
        def validate(self):
            for fname, field in fields.items():
                if field.required and getattr(self, fname) is None:
                    raise ValueError(f"Field '{fname}' is required")
        cls.__init__ = __init__
        cls.__repr__ = __repr__
        cls.validate = validate
        return cls

class Schema(metaclass=SchemaMeta): pass

class UserSchema(Schema):
    name = Field(str, required=True)
    age  = Field(int, required=False, default=None)

u = UserSchema(name="Alice", age=30)
print(u)          # UserSchema(name='Alice', age=30)
u.validate()      # passes

u2 = UserSchema()
try:
    u2.validate()
except ValueError as e:
    print(e)      # Field 'name' is required

Exercise 3 — Metaclass vs __init_subclass__ Comparison

Implement the same interface-enforcement system two ways and compare:

  • Version A — Metaclass: InterfaceMeta that checks for required methods in __new__ and raises TypeError if any are missing.
  • Version B — __init_subclass__: Interface base class that uses __init_subclass__ to enforce the same check.
  • Both versions must enforce: all methods in __required__ = [...] are implemented in concrete subclasses.
  • Write identical test cases for both versions: valid subclass passes; missing-method subclass raises TypeError.
  • Write a brief comparison: which is simpler? Which handles multiple inheritance more cleanly?
💡 Hint — Version B
class Interface:
    __required__: list[str] = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        required = set()
        for base in cls.__mro__:
            required.update(getattr(base, "__required__", []))
        missing = [m for m in required if not callable(getattr(cls, m, None))]
        # Only enforce on concrete classes (no further required methods)
        if missing and not getattr(cls, "__abstract__", False):
            raise TypeError(f"{cls.__name__} must implement: {missing}")

class DataStore(Interface):
    __required__ = ["read", "write"]
    __abstract__ = True   # skip check for this abstract class

class FileStore(DataStore):
    def read(self, k): return b""
    def write(self, k, v): pass   # passes — both methods present

try:
    class BadStore(DataStore): pass   # raises TypeError
except TypeError as e:
    print(e)