Python Metaclasses

Classes are objects. Metaclasses are the factories that build them. Understand type(), __init_subclass__, and when a custom metaclass is actually the right tool.

Advanced

type() as a Metaclass

In Python, everything is an object - including classes. The type() built-in is the default metaclass that creates all classes:

Python
class Dog:
    species = 'Canis lupus'
    def bark(self):
        return 'Woof'

# Dog is an instance of type
print(type(Dog))         # 
print(type(Dog) is type) # True
print(isinstance(Dog, type))  # True

# type() with 3 args creates a class dynamically
# type(name, bases, namespace)
Cat = type('Cat', (object,), {
    'species': 'Felis catus',
    'meow': lambda self: 'Meow',
})
c = Cat()
print(c.meow())          # Meow
print(type(Cat))         # 

The chain: int is an instance of type; type is an instance of itself; both int and type are subclasses of object:

Python
print(type(int))          # 
print(type(type))         # 
print(issubclass(int, object))   # True
print(issubclass(type, object))  # True
print(isinstance(type, type))    # True  (type is its own metaclass)

# The class hierarchy
# object ← int, str, list, dict, ...
# type   ← (metaclass of all above)

How Classes are Created

When Python encounters a class statement, it follows these steps:

Python
# 1. Python reads the class body into a namespace dict
# 2. Determines the metaclass (explicit metaclass= or inherited, default: type)
# 3. Calls: metaclass.__prepare__(name, bases, **kwargs) -> namespace dict
# 4. Executes the class body inside that namespace
# 5. Calls: metaclass(name, bases, namespace) -> class object

# Equivalent to:
class MyClass(Base, metaclass=Meta):
    x = 1

# Python roughly does:
namespace = Meta.__prepare__('MyClass', (Base,))
# exec class body in namespace
MyClass = Meta('MyClass', (Base,), namespace)

You can watch this happen with a minimal tracing metaclass:

Python
class TracingMeta(type):
    def __new__(mcs, name, bases, namespace):
        print(f'__new__: creating class {name!r}')
        print(f'  bases: {bases}')
        print(f'  namespace keys: {list(namespace.keys())}')
        cls = super().__new__(mcs, name, bases, namespace)
        return cls

    def __init__(cls, name, bases, namespace):
        print(f'__init__: initialising class {name!r}')
        super().__init__(name, bases, namespace)

class Animal(metaclass=TracingMeta):
    kingdom = 'Animalia'
    def breathe(self): pass

class Dog(Animal):
    species = 'Canis lupus'
Output
__new__: creating class 'Animal'
  bases: ()
  namespace keys: ['__module__', '__qualname__', 'kingdom', 'breathe']
__init__: initialising class 'Animal'
__new__: creating class 'Dog'
  bases: (,)
  namespace keys: ['__module__', '__qualname__', 'species']
__init__: initialising class 'Dog'

Custom Metaclasses

A custom metaclass inherits from type and overrides __new__ or __init__ to modify the class being created:

Enforcing naming conventions

Python
class UpperCaseAttrMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Convert all non-dunder attribute names to uppercase
        upper_namespace = {}
        for key, val in namespace.items():
            if key.startswith('__'):
                upper_namespace[key] = val
            else:
                upper_namespace[key.upper()] = val
        return super().__new__(mcs, name, bases, upper_namespace)

class Config(metaclass=UpperCaseAttrMeta):
    debug = True
    max_retries = 3

print(Config.DEBUG)        # True
print(Config.MAX_RETRIES)  # 3

Auto-registering subclasses

Python
class PluginMeta(type):
    registry = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if bases:  # skip the base class itself
            plugin_name = namespace.get('name', name.lower())
            mcs.registry[plugin_name] = cls
            print(f'registered plugin: {plugin_name!r}')
        return cls

class Plugin(metaclass=PluginMeta):
    pass   # base class - not registered

class CSVPlugin(Plugin):
    name = 'csv'
    def process(self, data): pass

class JSONPlugin(Plugin):
    name = 'json'
    def process(self, data): pass

print(PluginMeta.registry)
# {'csv': , 'json': }

# Lookup by name
plugin_cls = PluginMeta.registry['csv']
plugin = plugin_cls()
plugin.process([])

Singleton via metaclass

Python
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, host):
        self.host = host

db1 = Database('localhost')
db2 = Database('remotehost')   # ignored - returns existing instance
print(db1 is db2)   # True
print(db1.host)     # localhost

__init_subclass__

Added in Python 3.6, __init_subclass__ lets a parent class hook into subclass creation without a custom metaclass - it is simpler and covers most real-world use cases:

Python
class Base:
    subclasses = []

    def __init_subclass__(cls, required_method=None, **kwargs):
        super().__init_subclass__(**kwargs)
        Base.subclasses.append(cls)

        # Enforce contract: subclasses must define 'process'
        if not hasattr(cls, 'process'):
            raise TypeError(f'{cls.__name__} must implement process()')

        # Accept keyword arguments from class line
        if required_method and not hasattr(cls, required_method):
            raise TypeError(f'{cls.__name__} must implement {required_method}()')

class ValidChild(Base):
    def process(self): return 'ok'

# This would raise TypeError:
# class BadChild(Base):
#     pass  # missing process()

print(Base.subclasses)   # []

Keyword arguments on the class line are passed to __init_subclass__:

Python
class Shape:
    def __init_subclass__(cls, color='black', **kwargs):
        super().__init_subclass__(**kwargs)
        cls.default_color = color
        print(f'New shape: {cls.__name__}, color={color}')

class Circle(Shape, color='red'):
    pass

class Square(Shape, color='blue'):
    pass

class Triangle(Shape):   # uses default color='black'
    pass

print(Circle.default_color)    # red
print(Triangle.default_color)  # black
Prefer __init_subclass__ over metaclasses

For the common cases of registering subclasses, validating subclass structure, or adding default attributes, __init_subclass__ is simpler and avoids metaclass conflict errors (which arise when two metaclasses in the hierarchy are not related by inheritance). Use a metaclass only when you need to modify the class namespace before it is created, or when you need to intercept instance creation via __call__.

Class Decorators

Class decorators apply to the fully-constructed class and return a modified or replaced class. They are simpler than metaclasses for post-creation modifications:

Python
def add_repr(cls):
    def __repr__(self):
        attrs = ', '.join(f'{k}={v!r}' for k, v in self.__dict__.items())
        return f'{cls.__name__}({attrs})'
    cls.__repr__ = __repr__
    return cls

def validate_types(**types):
    def decorator(cls):
        original_init = cls.__init__
        def new_init(self, **kwargs):
            for name, expected_type in types.items():
                if name in kwargs and not isinstance(kwargs[name], expected_type):
                    raise TypeError(f'{name} must be {expected_type.__name__}')
            original_init(self, **kwargs)
        cls.__init__ = new_init
        return cls
    return decorator

@add_repr
@validate_types(name=str, age=int)
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person(name='Alice', age=30)
print(p)   # Person(name='Alice', age=30)

Real-World Use Cases

Metaclasses appear in well-known Python frameworks. Understanding them helps you use these tools effectively:

Framework/LibraryWhat the Metaclass Does
Django ORMScans class attributes for Field instances, builds SQL schema, creates query managers on Model subclasses
SQLAlchemyMaps Python class attributes to database columns, manages ORM session lifecycle
ABCMetaTracks abstract methods, prevents instantiation of classes that don't implement all abstract methods
EnumConverts class body assignments to enum members, enforces uniqueness, provides __members__ dict
dataclassesUses a class decorator (not metaclass) - generates __init__, __repr__, __eq__ from field annotations

A minimal ORM-style field mapping using a metaclass:

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

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

class ModelMeta(type):
    def __new__(mcs, name, bases, namespace):
        fields = {k: v for k, v in namespace.items() if isinstance(v, Field)}
        namespace['_fields'] = fields
        cls = super().__new__(mcs, name, bases, namespace)
        return cls

    def create_table_sql(cls):
        cols = ', '.join(f'{name} {f.col_type}' for name, f in cls._fields.items())
        return f'CREATE TABLE {cls.__name__.lower()} ({cols})'

class Model(metaclass=ModelMeta):
    pass

class User(Model):
    id    = Field('INTEGER PRIMARY KEY')
    name  = Field('TEXT NOT NULL')
    email = Field('TEXT UNIQUE')

print(User.create_table_sql())
# CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)
Metaclass conflict

If a class inherits from two classes with different metaclasses, Python raises TypeError: metaclass conflict. The fix is to create a new metaclass that inherits from both conflicting metaclasses: class CombinedMeta(MetaA, MetaB): pass. This is another reason to prefer __init_subclass__ and class decorators - they do not introduce metaclass conflicts.

Frequently Asked Questions

A metaclass is the class of a class. Just as an instance is created by its class, a class is created by its metaclass. The default metaclass is type. When Python processes a class statement, it calls the metaclass to build the class object. Custom metaclasses intercept this creation to add, modify, or validate class attributes and methods.

Rarely. Metaclasses are powerful but hard to reason about. Before using a metaclass, consider: (1) a class decorator - simpler and covers most use cases; (2) __init_subclass__ - for customising subclass creation without a custom metaclass. Use a metaclass only when you need to intercept the class creation process itself - e.g., ORMs, plugin systems, API frameworks, or enforcing interface contracts across an entire hierarchy.

In a metaclass, __new__(mcs, name, bases, namespace) creates and returns the class object - it is called first. __init__(cls, name, bases, namespace) initialises the already-created class object. Most metaclass logic goes in __new__ since you can modify namespace (the class dict) before the class is built. Both receive the same 3 arguments: class name, tuple of base classes, and namespace dict.

__init_subclass__(cls, **kwargs) is called on the parent class whenever it is subclassed. It receives the new subclass as cls and any keyword arguments passed in the class line. It was added in Python 3.6 as a simpler alternative to metaclasses for the common case of registering or validating subclasses. It is inherited, so every level of the hierarchy can participate.