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:
__prepare__— creates the namespace dict for the class body.- Executes the class body.
- Calls
metaclass(name, bases, namespace)— by defaulttype(...).
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
typeis the metaclass of all classes;type(MyClass)istype.- Metaclasses intercept class creation, not instance creation (
__init__handles that). - Common use cases: enforce interfaces, singletons, auto-registration, ORM field descriptors.
- Prefer
__init_subclass__or class decorators over custom metaclasses — they are simpler. abc.ABC/@abstractmethodis the standard way to define interfaces in Python.- Metaclass conflicts arise when two base classes have different metaclasses — resolved by creating a metaclass that inherits from both.