pvdocs /Python/Metaclasses

Metaclasses

A metaclass is the class of a class. Just as a class defines how instances behave, a metaclass defines how classes themselves are created and behave.

type — The Default Metaclass

Every class in Python is an instance of type.

type(int)    # <class 'type'>
type(str)    # <class 'type'>
type(42)     # <class 'int'>

# type() can also dynamically create a class:
MyClass = type("MyClass", (object,), {"x": 10, "greet": lambda self: "hi"})
obj = MyClass()
obj.greet()  # 'hi'

Class Creation Flow

When Python executes class Foo(Base): ..., it calls:

  1. __prepare__ — creates the namespace dict for the class body.
  2. Executes the class body.
  3. Calls metaclass(name, bases, namespace) — by default type(...).

Custom Metaclass

class Meta(type):
    def __new__(mcs, name, bases, namespace):
        # Intercept class creation
        print(f"Creating class: {name}")
        cls = super().__new__(mcs, name, bases, namespace)
        return cls

    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)

class MyClass(metaclass=Meta):
    pass
# "Creating class: MyClass"

Practical Use Cases

1. Enforce an Interface (Abstract-like)

class InterfaceMeta(type):
    def __new__(mcs, name, bases, namespace):
        required = {"run", "stop"}
        if bases:  # skip the base class itself
            missing = required - set(namespace)
            if missing:
                raise TypeError(f"{name} must implement: {missing}")
        return super().__new__(mcs, name, bases, namespace)

class Service(metaclass=InterfaceMeta):
    pass

class GoodService(Service):
    def run(self):  ...
    def stop(self): ...

# class BadService(Service):  # raises TypeError
#     def run(self): ...

2. Singleton Pattern

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self):
        self.connection = "open"

db1 = Database()
db2 = Database()
db1 is db2  # True

3. Auto-register Subclasses (Plugin System)

class PluginMeta(type):
    registry = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:
            mcs.registry[name] = cls
        return cls

class Plugin(metaclass=PluginMeta):
    pass

class CSVPlugin(Plugin): pass
class JSONPlugin(Plugin): pass

PluginMeta.registry  # {'CSVPlugin': ..., 'JSONPlugin': ...}

__init_subclass__ — A Simpler Alternative

For many metaclass use cases, __init_subclass__ (Python 3.6+) is cleaner:

class Base:
    def __init_subclass__(cls, required=(), **kwargs):
        super().__init_subclass__(**kwargs)
        for attr in required:
            if not hasattr(cls, attr):
                raise TypeError(f"{cls.__name__} must define '{attr}'")

class Child(Base, required=("run",)):
    def run(self): ...

ABCMeta and abc.ABC

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

# Shape() raises TypeError — can't instantiate abstract class
class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self): return 3.14 * self.r ** 2

ABC uses ABCMeta as its metaclass internally.

Key Interview Points