Skip to main content

Metaclasses - The Class of Classes

Reading time: ~35 minutes | Level: Advanced

The Prediction Puzzle

Before reading further, predict the output of this code. Write your answer down. Then continue.

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

def __init__(cls, name, bases, namespace):
print(f"Meta.__init__({name})")
super().__init__(name, bases, namespace)

def __call__(cls, *args, **kwargs):
print(f"Meta.__call__({cls.__name__})")
return super().__call__(*args, **kwargs)


class Base(metaclass=Meta):
def __new__(cls):
print(f"Base.__new__({cls.__name__})")
return super().__new__(cls)

def __init__(self):
print(f"Base.__init__({type(self).__name__})")


class Child(Base):
pass


print("--- Creating instance ---")
obj = Child()
print(f"type(obj) = {type(obj).__name__}")
print(f"type(Child) = {type(Child).__name__}")

Here is the actual output:

Meta.__new__(Base)
Meta.__init__(Base)
Meta.__new__(Child)
Meta.__init__(Child)
--- Creating instance ---
Meta.__call__(Child)
Base.__new__(Child)
Base.__init__(Child)
type(obj) = Child
type(Child) = Meta

Notice three things. First, Meta.__new__ and Meta.__init__ fire when the class statement executes -- not when you create an instance. The class statement class Base(metaclass=Meta): itself is what triggers the metaclass. Second, Child also triggers the metaclass even though it does not declare metaclass=Meta directly -- it inherits the metaclass from Base. Third, when you call Child() to create an instance, Python calls Meta.__call__ first, which then orchestrates Base.__new__ and Base.__init__.

If any of these surprised you, this lesson will make every step clear.

What You Will Learn

  • How type functions as both a callable and the metaclass of every class in Python
  • The complete class creation pipeline: __prepare__, body execution, __new__, __init__
  • How to write custom metaclasses that intercept and modify class creation
  • How __call__ on a metaclass controls instance creation, and how to use it for the singleton pattern
  • How __prepare__ lets you control the class namespace with custom mappings
  • Metaclass inheritance rules and how to resolve metaclass conflicts
  • Real-world metaclass patterns from Django, SQLAlchemy, and the abc module
  • When to use metaclasses and when simpler alternatives are the right choice

Prerequisites

  • Solid understanding of classes, inheritance, and MRO
  • Familiarity with type() as a one-argument function (returns an object's type)
  • Understanding of __new__ and __init__ on regular classes
  • Comfort with the CPython object model: __dict__, attribute resolution, object as universal base
  • Completed the Module Overview for Metaprogramming

Part 1 - type as Metaclass

Everything Is an Object. Classes Are Objects Too.

In Python, every value is an object. Integers, strings, functions, modules -- all objects. Classes are no exception. A class is an object whose type is type.

class Foo:
pass

print(type(42)) # <class 'int'>
print(type("hello")) # <class 'str'>
print(type(Foo)) # <class 'type'>
print(type(int)) # <class 'type'>
print(type(type)) # <class 'type'>

Every class you define has type as its type. int, str, list, dict -- all of them are instances of type. And type itself is an instance of type. That circular relationship is bootstrapped in C during CPython's initialisation and cannot be recreated in pure Python.

The Type-Object Hierarchy

The relationship between type and object is the foundation of Python's object model:

  • type is a subclass of object (so issubclass(type, object) is True)
  • object is an instance of type (so type(object) is type)
  • type is an instance of itself (so type(type) is type)

This is a chicken-and-egg relationship that is resolved in CPython's C implementation. You do not need to recreate it -- you need to understand it.

print(issubclass(type, object)) # True
print(isinstance(type, object)) # True
print(isinstance(object, type)) # True
print(type(object)) # <class 'type'>
print(type(type)) # <class 'type'>
print(object.__bases__) # ()
print(type.__bases__) # (<class 'object'>,)

class Is Syntactic Sugar for type()

When you write:

class Foo(Bar):
x = 10
def method(self):
return self.x

Python translates this into roughly:

namespace = {}
body = """
x = 10
def method(self):
return self.x
"""
exec(body, globals(), namespace)
Foo = type('Foo', (Bar,), namespace)

The three-argument form of type takes a name (string), a tuple of base classes, and a namespace dictionary. It returns a new class object.

# These two are equivalent:

# Approach 1: class statement
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return f"Point({self.x}, {self.y})"

# Approach 2: type() directly
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return f"Point({self.x}, {self.y})"

Point2 = type('Point2', (object,), {
'__init__': __init__,
'__repr__': __repr__,
})

p1 = Point(1, 2)
p2 = Point2(3, 4)
print(p1) # Point(1, 2)
print(p2) # Point2(3, 4)
print(type(Point)) # <class 'type'>
print(type(Point2)) # <class 'type'>

The class statement is more readable, but type() is what actually creates the class object. This distinction matters because a metaclass is anything that replaces type in that call.

tip

A metaclass is the class of a class. Just as an object is an instance of a class, a class is an instance of its metaclass. The default metaclass is type. When you specify metaclass=Meta, Python calls Meta('Foo', bases, namespace) instead of type('Foo', bases, namespace).

Part 2 - How Class Creation Works

The class statement triggers a multi-step pipeline. Understanding each step is essential for writing metaclasses that intervene at the right point.

The Full Pipeline

Let us walk through each step.

Step 1: Determine the Metaclass

Python resolves the metaclass using these rules, in order:

  1. If metaclass=Meta is specified in the class statement, use Meta.
  2. If any base class has a metaclass other than type, use that metaclass.
  3. If multiple bases have different metaclasses, use the most derived one (or raise TypeError if there is no single most-derived metaclass).
  4. Default to type.
class Meta(type):
pass

class A(metaclass=Meta):
pass

class B(A): # Inherits Meta as its metaclass
pass

print(type(A)) # <class '__main__.Meta'>
print(type(B)) # <class '__main__.Meta'>

Step 2: __prepare__ Creates the Namespace

Before executing the class body, Python calls metaclass.__prepare__(name, bases, **kwargs). This must return a mapping object that will serve as the class namespace.

The default type.__prepare__ returns a plain dict. Custom metaclasses can return an OrderedDict, a defaultdict, or any other mapping to control how the class body's names are stored.

class Meta(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
print(f"__prepare__({name})")
return {}

class Foo(metaclass=Meta):
x = 1
# Output: __prepare__(Foo)

Step 3: Execute the Class Body

Python executes the class body as if it were a function body, using the namespace from __prepare__ as the local namespace. Every assignment, function definition, and class definition inside the body writes to this namespace.

# When Python executes:
class Foo:
x = 10
def method(self):
pass

# The namespace dict becomes:
# {'x': 10, 'method': <function method at ...>, '__module__': '__main__', ...}

Step 4: type.__new__ Creates the Class Object

After the body executes, Python calls metaclass.__new__(mcs, name, bases, namespace). This allocates and returns the new class object. The __new__ method is where you can:

  • Inspect the namespace to see what the class body defined
  • Add, remove, or modify attributes before the class is finalised
  • Validate that required methods or attributes are present
  • Register the class in a global registry

Step 5: type.__init__ Initialises the Class

After __new__ returns the class object, Python calls metaclass.__init__(cls, name, bases, namespace). This is analogous to __init__ on regular classes -- the object already exists, and __init__ performs additional setup.

In practice, most metaclass logic goes in __new__ rather than __init__, because __new__ receives the namespace before the class is fully constructed.

Steps 6 and 7: Post-Creation Hooks

After the class is created, Python calls __set_name__ on all descriptors in the class namespace, then calls __init_subclass__ on the parent class. These hooks are covered in their own lessons.

note

The distinction between __new__ and __init__ on metaclasses is the same as on regular classes. __new__ creates the object and returns it. __init__ receives the already-created object and configures it. On metaclasses, the "object" being created is a class.

Part 3 - Writing a Custom Metaclass

A custom metaclass is a class that inherits from type. You override __new__ to intercept class creation.

A Basic Metaclass

class AutoReprMeta(type):
"""Metaclass that auto-generates __repr__ for all classes."""

def __new__(mcs, name, bases, namespace):
# Don't modify the base class itself
if '__auto_repr__' not in namespace:
namespace['__auto_repr__'] = True

cls = super().__new__(mcs, name, bases, namespace)

# Generate __repr__ if one isn't explicitly defined
if '__repr__' not in namespace:
def __repr__(self):
attrs = ', '.join(
f'{k}={v!r}'
for k, v in self.__dict__.items()
if not k.startswith('_')
)
return f"{type(self).__name__}({attrs})"
cls.__repr__ = __repr__

return cls


class Person(metaclass=AutoReprMeta):
def __init__(self, name, age):
self.name = name
self.age = age


class Employee(Person):
def __init__(self, name, age, company):
super().__init__(name, age)
self.company = company


p = Person("Alice", 30)
e = Employee("Bob", 25, "Acme")
print(p) # Person(name='Alice', age=30)
print(e) # Employee(name='Bob', age=25, company='Acme')

The metaclass inspects the namespace dict during __new__, checks whether the class already defines __repr__, and injects one if it does not. Every class created with this metaclass -- and every subclass of such a class -- gets this behavior automatically.

A Class Registry Metaclass

One of the most common metaclass patterns is automatic class registration. Every class created with the metaclass registers itself in a central dictionary.

class RegistryMeta(type):
_registry = {}

def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Don't register the base class itself
if bases:
key = namespace.get('registry_key', name.lower())
mcs._registry[key] = cls
return cls

@classmethod
def get(mcs, key):
return mcs._registry.get(key)

@classmethod
def all_registered(mcs):
return dict(mcs._registry)


class Handler(metaclass=RegistryMeta):
"""Base class for all handlers."""
pass


class JsonHandler(Handler):
registry_key = 'json'

def handle(self, data):
return f"Handling JSON: {data}"


class XmlHandler(Handler):
registry_key = 'xml'

def handle(self, data):
return f"Handling XML: {data}"


class CsvHandler(Handler):
# No explicit key -- defaults to 'csvhandler'
def handle(self, data):
return f"Handling CSV: {data}"


print(RegistryMeta.all_registered())
# {'json': <class 'JsonHandler'>, 'xml': <class 'XmlHandler'>,
# 'csvhandler': <class 'CsvHandler'>}

handler_cls = RegistryMeta.get('json')
handler = handler_cls()
print(handler.handle('{"a": 1}')) # Handling JSON: {"a": 1}

This pattern is the foundation of plugin systems, serialisation format dispatchers, and web framework route handlers.

Interface Enforcement

Metaclasses can enforce that subclasses implement required methods. This is what abc.ABCMeta does under the hood.

class InterfaceMeta(type):
"""Metaclass that enforces required method implementation."""

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

# Skip the base interface class itself
if bases:
required = set()
for base in bases:
required |= getattr(base, '_required_methods', set())

missing = required - set(namespace.keys())
if missing:
raise TypeError(
f"Class '{name}' must implement: "
f"{', '.join(sorted(missing))}"
)

return cls


class Serializer(metaclass=InterfaceMeta):
_required_methods = {'serialize', 'deserialize'}


class JsonSerializer(Serializer):
def serialize(self, data):
import json
return json.dumps(data)

def deserialize(self, raw):
import json
return json.loads(raw)

# This works fine -- both methods are implemented.

# This would raise TypeError:
# class BrokenSerializer(Serializer):
# def serialize(self, data):
# pass
#
# TypeError: Class 'BrokenSerializer' must implement: deserialize
danger

Do not reach for a metaclass when you only need to enforce abstract methods. Python's built-in abc.ABC and abc.abstractmethod already do this correctly, with proper handling of inheritance and partial implementations. Write a custom metaclass only when ABC cannot express your constraints.

Part 4 - __call__ on Metaclasses

This is the part that confuses most engineers. There are two levels of __call__ in play, and conflating them is the source of most metaclass bugs.

The Two Levels

When you write obj = MyClass(arg1, arg2), Python does not directly call MyClass.__init__. It calls type(MyClass).__call__(MyClass, arg1, arg2). Since type(MyClass) is the metaclass, this means the metaclass's __call__ method orchestrates instance creation.

The default type.__call__ does approximately this:

# Pseudocode for type.__call__
def __call__(cls, *args, **kwargs):
instance = cls.__new__(cls, *args, **kwargs)
if isinstance(instance, cls):
instance.__init__(*args, **kwargs)
return instance

So the call chain is:

The Singleton Pattern via __call__

Overriding __call__ on a metaclass lets you intercept every instance creation. The classic use case is the singleton pattern:

class SingletonMeta(type):
_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls.__class__._instances:
# Call type.__call__ to actually create the instance
instance = super().__call__(*args, **kwargs)
cls.__class__._instances[cls] = instance
return cls.__class__._instances[cls]


class Database(metaclass=SingletonMeta):
def __init__(self, url):
self.url = url
print(f"Connecting to {url}")


db1 = Database("postgres://localhost/mydb")
# Output: Connecting to postgres://localhost/mydb
db2 = Database("postgres://localhost/mydb")
# No output -- __init__ is not called again
print(db1 is db2) # True

The first call to Database(...) goes through super().__call__(), which creates the instance normally. Subsequent calls skip instance creation entirely and return the cached instance.

Why __call__ and Not __new__?

You could implement a singleton by overriding __new__ on the class itself:

class Database:
_instance = None

def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def __init__(self, url):
self.url = url # Called every time!

But this has a flaw: __init__ still runs on every call, potentially resetting state. The metaclass __call__ approach lets you skip both __new__ and __init__ when returning a cached instance.

tip

The distinction matters: __call__ on the metaclass controls whether __new__ and __init__ run at all. __new__ on the class controls how the instance object is allocated. These are different layers of the creation pipeline.

Logging Every Instance Creation

A less exotic but more practical use of metaclass __call__ is instrumentation:

import time

class InstrumentedMeta(type):
def __call__(cls, *args, **kwargs):
start = time.perf_counter()
instance = super().__call__(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"Created {cls.__name__} in {elapsed:.6f}s")
return instance


class HeavyObject(metaclass=InstrumentedMeta):
def __init__(self):
# Simulate expensive initialisation
total = sum(range(1_000_000))
self.total = total


obj = HeavyObject()
# Output: Created HeavyObject in 0.031245s (varies)

Every class using this metaclass gets automatic creation timing without modifying any class code.

Part 5 - __prepare__ and Custom Namespaces

What __prepare__ Does

__prepare__ is a classmethod on the metaclass that returns the mapping object used as the namespace when executing the class body. The default is a plain dict.

class Meta(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
print(f"__prepare__ called for {name}")
ns = super().__prepare__(name, bases, **kwargs)
print(f"Returning namespace of type: {type(ns).__name__}")
return ns

class Foo(metaclass=Meta):
x = 1
y = 2

# Output:
# __prepare__ called for Foo
# Returning namespace of type: dict

Tracking Definition Order

Before Python 3.7 (when dict became insertion-ordered by spec), __prepare__ was essential for preserving the order in which attributes were defined in the class body. The enum module used this internally.

from collections import OrderedDict

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

def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, dict(namespace))
cls._field_order = [
k for k in namespace
if not k.startswith('_')
]
return cls


class Config(metaclass=OrderedMeta):
host = "localhost"
port = 8080
debug = False
workers = 4

print(Config._field_order)
# ['host', 'port', 'debug', 'workers']

In modern Python (3.7+), dict preserves insertion order, so this specific use case no longer requires __prepare__. But __prepare__ remains valuable for other reasons.

Custom Namespace Mappings

__prepare__ can return any mapping that supports __setitem__. This lets you intercept every assignment in the class body.

class NoOverwriteDict(dict):
"""A dict that raises on duplicate key assignment."""
def __setitem__(self, key, value):
if key in self and not key.startswith('_'):
raise TypeError(
f"Duplicate definition of '{key}' in class body"
)
super().__setitem__(key, value)


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

def __new__(mcs, name, bases, namespace):
# Convert back to regular dict for class creation
return super().__new__(mcs, name, bases, dict(namespace))


class Clean(metaclass=StrictMeta):
x = 1
y = 2
# x = 3 # Would raise: TypeError: Duplicate definition of 'x'

This is a defensive pattern that prevents accidental method redefinition in large class bodies.

How Enum Uses __prepare__ Internally

The enum module's EnumMeta returns a custom _EnumDict namespace from __prepare__. This namespace tracks which names are enum members vs. regular attributes, prevents duplicate member names, and enforces the rules of enum definition.

import enum

# Simplified illustration of what EnumMeta.__prepare__ enables:
# The _EnumDict tracks member definitions separately from methods
class Color(enum.Enum):
RED = 1
GREEN = 2
BLUE = 3

# The namespace returned by __prepare__ was an _EnumDict that:
# 1. Recorded RED, GREEN, BLUE as enum members
# 2. Would reject: RED = 4 (duplicate member)
# 3. Allowed non-member definitions like methods
note

__prepare__ must be a classmethod (or staticmethod). It receives the metaclass, the class name, and the base classes. It must return a mapping. The returned mapping does not need to be a dict -- any object implementing __setitem__ and __getitem__ works -- but type.__new__ will ultimately convert it to a real dict internally.

Part 6 - Metaclass Inheritance and Conflicts

Metaclasses Propagate Through Inheritance

When a class uses a custom metaclass, all of its subclasses use that metaclass too. This is because the subclass is created by calling its metaclass, and the metaclass is inherited.

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

class Base(metaclass=Meta):
pass
# Output: Meta creating: Base

class Child(Base):
pass
# Output: Meta creating: Child

class GrandChild(Child):
pass
# Output: Meta creating: GrandChild

print(type(Base)) # <class 'Meta'>
print(type(Child)) # <class 'Meta'>
print(type(GrandChild)) # <class 'Meta'>

You do not need to repeat metaclass=Meta on subclasses. The metaclass propagates automatically.

The Metaclass Resolution Algorithm

When a class has multiple bases with different metaclasses, Python must determine which metaclass to use. The rule is:

The metaclass of the new class must be a subclass of the metaclass of every base class.

If such a metaclass exists among the candidates, Python uses the most derived one. If no such metaclass exists, Python raises TypeError.

class MetaA(type):
pass

class MetaB(type):
pass

class A(metaclass=MetaA):
pass

class B(metaclass=MetaB):
pass

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

Resolving Metaclass Conflicts

The fix is to create a metaclass that inherits from both conflicting metaclasses:

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

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

class MetaAB(MetaA, MetaB):
"""Combined metaclass that satisfies both MetaA and MetaB."""
pass

class A(metaclass=MetaA):
pass

class B(metaclass=MetaB):
pass

class C(A, B, metaclass=MetaAB):
pass
# Output:
# MetaA.__new__(A)
# MetaB.__new__(B)
# MetaA.__new__(C) (MetaAB.__new__ calls MetaA via MRO)

MetaAB is a subclass of both MetaA and MetaB, satisfying the requirement. Its MRO determines the order in which __new__ methods execute.

A Practical Conflict: ABCMeta

The most common metaclass conflict in real code involves abc.ABCMeta:

from abc import ABCMeta, abstractmethod

class RegistryMeta(type):
_registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if bases:
mcs._registry[name] = cls
return cls

# This fails if you try:
# class Base(metaclass=RegistryMeta):
# @abstractmethod
# def process(self): ...
#
# Because ABC uses ABCMeta, and RegistryMeta is not a subclass of ABCMeta.

# Solution: inherit from ABCMeta
class RegistryABCMeta(RegistryMeta, ABCMeta):
pass

class Base(metaclass=RegistryABCMeta):
@abstractmethod
def process(self):
...

class Concrete(Base):
def process(self):
return "processed"

print(RegistryABCMeta._registry)
# {'Concrete': <class 'Concrete'>}

# Attempting to instantiate Base still raises TypeError
# b = Base() # TypeError: Can't instantiate abstract class Base
danger

Metaclass conflicts are a real maintenance burden. Every library that uses a metaclass forces its users to deal with potential conflicts. This is one of the strongest arguments for preferring __init_subclass__ over metaclasses when the simpler hook suffices. __init_subclass__ cannot cause metaclass conflicts because it does not involve a metaclass at all.

Part 7 - Real-World Metaclass Examples

Django's ModelBase (Simplified)

Django's ORM uses a metaclass called ModelBase to transform a class with field declarations into a database-backed model. Here is a simplified version that illustrates the core pattern:

class Field:
def __init__(self, field_type, max_length=None, default=None):
self.field_type = field_type
self.max_length = max_length
self.default = default
self.name = None # Set by metaclass

def __repr__(self):
return f"Field({self.field_type}, name={self.name!r})"


class Options:
"""Simplified version of Django's Options (_meta)."""
def __init__(self, model_name, fields, table_name):
self.model_name = model_name
self.fields = fields
self.table_name = table_name


class ModelBase(type):
"""Simplified Django ModelBase metaclass."""

def __new__(mcs, name, bases, namespace):
# Don't process the base Model class itself
parents = [b for b in bases if isinstance(b, ModelBase)]
if not parents:
return super().__new__(mcs, name, bases, namespace)

# Collect field definitions from the namespace
fields = {}
for key, value in list(namespace.items()):
if isinstance(value, Field):
value.name = key
fields[key] = value

# Build the class
cls = super().__new__(mcs, name, bases, namespace)

# Attach metadata
table_name = namespace.get('__tablename__', name.lower())
cls._meta = Options(
model_name=name,
fields=fields,
table_name=table_name,
)

return cls


class Model(metaclass=ModelBase):
"""Base model class."""

def __init__(self, **kwargs):
for name, field in self._meta.fields.items():
value = kwargs.get(name, field.default)
setattr(self, name, value)

def __repr__(self):
attrs = ', '.join(
f'{name}={getattr(self, name)!r}'
for name in self._meta.fields
)
return f"{type(self).__name__}({attrs})"


class User(Model):
__tablename__ = 'auth_user'
username = Field('varchar', max_length=150)
email = Field('varchar', max_length=254)
is_active = Field('boolean', default=True)


u = User(username='alice', email='[email protected]')
print(u)
# User(username='alice', email='[email protected]', is_active=True)
print(u._meta.table_name)
# auth_user
print(u._meta.fields)
# {'username': Field(varchar, name='username'),
# 'email': Field(varchar, name='email'),
# 'is_active': Field(boolean, name='is_active')}

The real Django ModelBase is far more complex -- it handles inheritance, proxy models, abstract models, app registry integration, manager installation, and field contribution. But the core pattern is the same: inspect the namespace, collect fields, build metadata, and attach it to the class.

SQLAlchemy's Declarative Metaclass (Simplified)

SQLAlchemy's declarative system uses a similar pattern to map classes to database tables:

class Column:
def __init__(self, column_type, primary_key=False, nullable=True):
self.column_type = column_type
self.primary_key = primary_key
self.nullable = nullable
self.name = None


class DeclarativeMeta(type):
_tables = {}

def __new__(mcs, name, bases, namespace):
parents = [b for b in bases if isinstance(b, DeclarativeMeta)]
if not parents:
return super().__new__(mcs, name, bases, namespace)

table_name = namespace.get('__tablename__', name.lower())
columns = {}

for key, value in namespace.items():
if isinstance(value, Column):
value.name = key
columns[key] = value

cls = super().__new__(mcs, name, bases, namespace)
cls.__tablename__ = table_name
cls.__columns__ = columns

# Register the table
mcs._tables[table_name] = cls

return cls

def create_table_sql(cls):
"""Generate CREATE TABLE SQL."""
col_defs = []
for name, col in cls.__columns__.items():
parts = [name, col.column_type]
if col.primary_key:
parts.append("PRIMARY KEY")
if not col.nullable:
parts.append("NOT NULL")
col_defs.append(" ".join(parts))
return f"CREATE TABLE {cls.__tablename__} (\n " + \
",\n ".join(col_defs) + "\n);"


class Base(metaclass=DeclarativeMeta):
pass


class User(Base):
__tablename__ = 'users'
id = Column('INTEGER', primary_key=True)
name = Column('VARCHAR(100)', nullable=False)
email = Column('VARCHAR(200)', nullable=False)


print(User.create_table_sql())
# CREATE TABLE users (
# id INTEGER PRIMARY KEY,
# name VARCHAR(100) NOT NULL,
# email VARCHAR(200) NOT NULL
# );

abc.ABCMeta - The Standard Library Metaclass

The abc module provides ABCMeta, a metaclass that tracks abstract methods and prevents instantiation of classes that have not implemented all of them.

from abc import ABCMeta, abstractmethod

class Shape(metaclass=ABCMeta):
@abstractmethod
def area(self):
...

@abstractmethod
def perimeter(self):
...

class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self):
import math
return math.pi * self.radius ** 2

def perimeter(self):
import math
return 2 * math.pi * self.radius

# Works fine:
c = Circle(5)
print(f"Area: {c.area():.2f}") # Area: 78.54

# Fails -- not all abstract methods implemented:
class PartialShape(Shape):
def area(self):
return 0

# s = PartialShape()
# TypeError: Can't instantiate abstract class PartialShape
# with abstract method perimeter

Under the hood, ABCMeta.__new__ scans the namespace and base classes for methods decorated with @abstractmethod. It stores the set of unimplemented abstract methods in cls.__abstractmethods__. The object.__new__ check prevents instantiation if __abstractmethods__ is non-empty.

print(Shape.__abstractmethods__) # frozenset({'area', 'perimeter'})
print(Circle.__abstractmethods__) # frozenset()
print(PartialShape.__abstractmethods__) # frozenset({'perimeter'})

This is one of the best-designed metaclasses in Python's ecosystem. It does exactly one thing, it does it correctly, and it is invisible to users of the classes built with it.

tip

Study ABCMeta as a model for metaclass design. It follows the principle that a metaclass should be invisible to users of the classes it creates. Users of Shape and Circle never need to know that ABCMeta exists.

Part 8 - When NOT to Use Metaclasses

Metaclasses are the most powerful class customisation tool in Python. They are also the most complex, the hardest to debug, and the most likely to cause maintenance problems.

The Decision Hierarchy

When you need to customise class creation, work through this list from simplest to most complex:

Prefer __init_subclass__ for Subclass Registration

The registry pattern from Part 3 can be implemented without a metaclass:

class Handler:
_registry = {}

def __init_subclass__(cls, registry_key=None, **kwargs):
super().__init_subclass__(**kwargs)
key = registry_key or cls.__name__.lower()
Handler._registry[key] = cls


class JsonHandler(Handler, registry_key='json'):
def handle(self, data):
return f"JSON: {data}"


class XmlHandler(Handler, registry_key='xml'):
def handle(self, data):
return f"XML: {data}"


print(Handler._registry)
# {'json': <class 'JsonHandler'>, 'xml': <class 'XmlHandler'>}

This is simpler, has no metaclass conflicts, and is the recommended approach for Python 3.6+.

Prefer Class Decorators for Class Transformation

If you need to modify a class after creation -- adding methods, wrapping methods, adding attributes -- a class decorator is usually sufficient:

def add_repr(cls):
"""Class decorator that adds __repr__."""
def __repr__(self):
attrs = ', '.join(
f'{k}={v!r}'
for k, v in self.__dict__.items()
if not k.startswith('_')
)
return f"{cls.__name__}({attrs})"
cls.__repr__ = __repr__
return cls


@add_repr
class Point:
def __init__(self, x, y):
self.x = x
self.y = y


print(Point(1, 2)) # Point(x=1, y=2)

This achieves the same result as the AutoReprMeta metaclass from Part 3, without the complexity.

When Metaclasses ARE Necessary

Metaclasses remain the right tool when:

  1. You need __prepare__ to control the class namespace before the body executes. No other mechanism provides this.
  2. You need to intercept instance creation via __call__ across an entire class hierarchy. __new__ on individual classes is an alternative but does not provide the same level of control.
  3. You are building a framework where the metaclass behavior must propagate automatically through inheritance, with no action required from users. Django's ORM is the canonical example.
  4. You need to prevent class creation (raise TypeError in __new__) based on namespace inspection before the class object exists.

The "Invisible Metaclass" Principle

Tim Peters, author of the Zen of Python, wrote: "Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why)."

The best metaclasses are invisible. Users of Django models do not know or care that ModelBase exists. Users of ABCs do not interact with ABCMeta directly. If your users need to understand your metaclass to use your library, the design is wrong.

Key Takeaways

  • type is the default metaclass. Every class is an instance of its metaclass. class Foo: is sugar for type('Foo', (object,), namespace).
  • The class creation pipeline is: determine metaclass, __prepare__ creates namespace, body executes in namespace, __new__ creates the class object, __init__ initialises it, then __set_name__ and __init_subclass__ fire.
  • Custom metaclasses subclass type and override __new__ to inspect or modify the class namespace before the class is finalised.
  • __call__ on the metaclass controls instance creation. It calls cls.__new__ and cls.__init__. Override it to implement singletons, caching, or instance instrumentation.
  • __prepare__ returns the mapping used as the class namespace. Use it to intercept assignments, enforce constraints, or track definition order.
  • Metaclasses propagate through inheritance. Conflicting metaclasses from multiple bases require a combined metaclass that inherits from all of them.
  • Real frameworks use metaclasses: Django's ModelBase, SQLAlchemy's DeclarativeMeta, and abc.ABCMeta are all production metaclasses.
  • Prefer simpler tools when possible: __init_subclass__ for subclass hooks, class decorators for post-creation transformation. Use metaclasses only when you need __prepare__, __call__ interception, or automatic propagation through inheritance.

Graded Practice Challenges

Level 1 - Predict the Output

For each snippet, predict the exact output. Then check your answer.

Question 1:

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

class A(metaclass=M):
pass

class B(A):
pass

print(type(B).__name__)
Answer
new: A
new: B
M

M.__new__ fires for both A and B (metaclass inherits). type(B) is M.

Question 2:

class M(type):
def __call__(cls, *args, **kwargs):
print(f"call: {cls.__name__}")
return super().__call__(*args, **kwargs)

class Foo(metaclass=M):
def __init__(self):
print("init")

a = Foo()
b = Foo()
Answer
call: Foo
init
call: Foo
init

Each call to Foo() invokes M.__call__, which in turn calls Foo.__init__ via super().__call__().

Question 3:

class M(type):
@classmethod
def __prepare__(mcs, name, bases):
print(f"prepare: {name}")
return {}

def __new__(mcs, name, bases, ns):
print(f"new: {name}")
return super().__new__(mcs, name, bases, ns)

def __init__(cls, name, bases, ns):
print(f"init: {name}")
super().__init__(name, bases, ns)

class Foo(metaclass=M):
x = 1
Answer
prepare: Foo
new: Foo
init: Foo

The order is always: __prepare__ first (before body execution), then __new__ (creates the class), then __init__ (initialises it).

Question 4:

class M(type):
_count = 0

def __new__(mcs, name, bases, ns):
mcs._count += 1
return super().__new__(mcs, name, bases, ns)

class A(metaclass=M): pass
class B(A): pass
class C(B): pass

print(M._count)
Answer
3

M.__new__ fires for A, B, and C. Each increments _count.

Question 5:

class M(type):
def __call__(cls, *args, **kwargs):
instance = super().__call__(*args, **kwargs)
instance._created_by = 'M'
return instance

class Foo(metaclass=M):
def __init__(self):
self._created_by = 'Foo'

f = Foo()
print(f._created_by)
Answer
M

super().__call__() runs Foo.__init__ which sets _created_by = 'Foo'. Then M.__call__ overwrites it with 'M' after super().__call__() returns. The metaclass has the last word.

Level 2 - Debug Challenge

The following code is intended to create a metaclass that prevents classes from defining any attribute starting with test_ (reserving that prefix for testing frameworks). But it does not work correctly. Find and fix the bug.

class NoTestAttrMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)

for attr_name in namespace:
if attr_name.startswith('test_'):
raise TypeError(
f"Class '{name}' cannot define '{attr_name}': "
f"'test_' prefix is reserved"
)

return cls


class Service(metaclass=NoTestAttrMeta):
def test_connection(self):
"""Check if the service can connect."""
return True
Hint

Look at the order of operations in __new__. When does the validation run relative to the class creation?

Solution

The bug is that super().__new__() is called before the validation check. The class object is created first, then the check runs. If the check fails, the class has already been partially created (it exists as a dangling object). More importantly, if any post-creation hooks (__set_name__, __init_subclass__) have side effects, those will have already fired.

The fix is to validate before calling super().__new__():

class NoTestAttrMeta(type):
def __new__(mcs, name, bases, namespace):
# Validate BEFORE creating the class
for attr_name in namespace:
if attr_name.startswith('test_'):
raise TypeError(
f"Class '{name}' cannot define '{attr_name}': "
f"'test_' prefix is reserved"
)

return super().__new__(mcs, name, bases, namespace)

Always perform validation before super().__new__() when you want to prevent the class from being created at all.

Level 3 - Design Challenge

Build a SchemaEnforcerMeta metaclass that enforces a schema on class definitions. The metaclass should:

  1. Read a class-level attribute called __schema__, which is a dictionary mapping attribute names to types (e.g., {'name': str, 'age': int}).
  2. In __new__, verify that every key in __schema__ has a corresponding Field descriptor defined in the class namespace. Raise TypeError if any are missing.
  3. Override __call__ so that when an instance is created, it validates that all keyword arguments match the types specified in __schema__. Raise TypeError for type mismatches.
  4. Work correctly with inheritance -- a child class should inherit its parent's schema and be able to extend it with additional fields.

Here is the expected usage:

class Field:
def __init__(self):
self.name = None

def __set_name__(self, owner, name):
self.name = name

def __set__(self, obj, value):
obj.__dict__[self.name] = value

def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)


class User(metaclass=SchemaEnforcerMeta):
__schema__ = {'name': str, 'email': str}
name = Field()
email = Field()


class Admin(User):
__schema__ = {'role': str} # Extends User's schema
role = Field()


u = User(name="Alice", email="[email protected]") # OK
a = Admin(name="Bob", email="[email protected]", role="superuser") # OK
# User(name=123, email="[email protected]") # TypeError: 'name' must be str, got int
# class Bad(metaclass=SchemaEnforcerMeta): # TypeError: missing Field for 'x'
# __schema__ = {'x': int}
Solution
class SchemaEnforcerMeta(type):
def __new__(mcs, name, bases, namespace):
# Collect inherited schema from bases
inherited_schema = {}
for base in reversed(bases):
inherited_schema.update(getattr(base, '__full_schema__', {}))

# Merge with this class's schema
local_schema = namespace.get('__schema__', {})
full_schema = {**inherited_schema, **local_schema}

# Collect all Field instances (including inherited)
all_fields = set()
for base in bases:
for attr_name, attr_value in vars(base).items():
if isinstance(attr_value, Field):
all_fields.add(attr_name)
for attr_name, attr_value in namespace.items():
if isinstance(attr_value, Field):
all_fields.add(attr_name)

# Validate that every schema key has a Field
# Only check local_schema keys against available fields
missing = set(local_schema.keys()) - all_fields
if missing:
raise TypeError(
f"Class '{name}' is missing Field descriptors "
f"for schema keys: {', '.join(sorted(missing))}"
)

cls = super().__new__(mcs, name, bases, namespace)
cls.__full_schema__ = full_schema
return cls

def __call__(cls, **kwargs):
schema = cls.__full_schema__

# Validate types
for attr_name, expected_type in schema.items():
if attr_name in kwargs:
value = kwargs[attr_name]
if not isinstance(value, expected_type):
raise TypeError(
f"'{attr_name}' must be {expected_type.__name__}, "
f"got {type(value).__name__}"
)

# Create instance and set attributes
instance = cls.__new__(cls)
for attr_name, value in kwargs.items():
setattr(instance, attr_name, value)
return instance

Key design decisions:

  • __full_schema__ stores the merged schema (inherited + local) so subclasses see the complete picture.
  • Validation in __new__ checks local schema keys against available fields (including inherited ones).
  • __call__ validates types before setting any attributes, providing fail-fast behavior.
  • Inheritance works because __full_schema__ is built from base class schemas plus the local __schema__.

What's Next

In the next lesson, Descriptors, you will learn the protocol that controls attribute access in Python. Descriptors are the mechanism behind property, classmethod, staticmethod, and bound methods. They are also the building blocks used inside metaclasses -- the Field objects in this lesson's Django and SQLAlchemy examples are descriptors. Understanding descriptors will complete your picture of how Python's data model works at the class level.

© 2026 EngineersOfAI. All rights reserved.