Metaclasses

advanced metaclass type __new__ __init_subclass__ class-creation

Here’s the mind-bending part of Python: everything is an object, including classes themselves. And if classes are objects, something must create them. That “something” is a metaclass. In simple language, a metaclass is a class whose instances are classes.

Classes Are Objects

When we write class Dog: pass, Python creates an object called Dog. We can assign it to variables, pass it to functions, inspect it — just like any other object.

class Dog:
    pass

print(type(Dog))    # <class 'type'> — Dog is an instance of type
print(type(42))     # <class 'int'> — 42 is an instance of int
print(type(int))    # <class 'type'> — even int is an instance of type

type is the default metaclass. It’s the thing that creates all classes. So: type creates classes, and classes create instances.

The Meta Chain
type (metaclass)
creates classes
↓ creates
Dog (class)
creates instances
↓ creates
buddy (instance)
an actual dog object
type(buddy) = Dogtype(Dog) = typetype(type) = type

type() as a Class Factory

type isn’t just for checking types. We can call it with three arguments to create a class dynamically:

# type(name, bases, dict)
Dog = type('Dog', (), {'speak': lambda self: 'Woof!'})

d = Dog()
print(d.speak())   # Woof!
print(type(d))     # <class '__main__.Dog'>

This is exactly what Python does behind the scenes when it encounters a class statement. The class keyword is just syntactic sugar for calling the metaclass.

__new__ vs __init__ in Regular Classes

Before we get to metaclasses, let’s clarify these two in normal classes:

  • __new__creates the object (allocates memory, returns the new instance)
  • __init__initializes the object (sets up attributes on the already-created instance)
class Foo:
    def __new__(cls, *args):
        print("Creating instance")
        instance = super().__new__(cls)  # actually create the object
        return instance

    def __init__(self, x):
        print("Initializing instance")
        self.x = x

f = Foo(10)  # Creating instance → Initializing instance

We rarely override __new__ in regular classes. But in metaclasses, it’s essential.

Writing a Custom Metaclass

A metaclass is a class that inherits from type. We override __new__ or __init__ to customize how classes are created.

class UpperAttrMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Uppercase all non-dunder attributes
        upper_attrs = {}
        for key, val in namespace.items():
            if key.startswith('__'):
                upper_attrs[key] = val
            else:
                upper_attrs[key.upper()] = val
        return super().__new__(mcs, name, bases, upper_attrs)

class MyClass(metaclass=UpperAttrMeta):
    greeting = "hello"

print(hasattr(MyClass, 'greeting'))   # False
print(hasattr(MyClass, 'GREETING'))   # True
print(MyClass.GREETING)               # hello

Notice: __new__ in a metaclass receives the class namespace (all the attributes and methods defined in the class body) and can modify them before the class is actually created.

__new__ and __init__ in Metaclasses

In a metaclass, both methods work on classes (not instances):

  • __new__(mcs, name, bases, namespace) — called before the class object exists. We can modify the namespace or even return a different class.
  • __init__(cls, name, bases, namespace) — called after the class object is created. Good for registration or validation.
class RegistryMeta(type):
    registry = {}

    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        if name != 'Base':  # don't register the base class itself
            RegistryMeta.registry[name] = cls

class Base(metaclass=RegistryMeta):
    pass

class UserModel(Base):
    pass

class ProductModel(Base):
    pass

print(RegistryMeta.registry)  # {'UserModel': <class ...>, 'ProductModel': <class ...>}

This automatic registration pattern is how many ORMs (like Django’s models) work under the hood.

__init_subclass__: The Simpler Alternative

Python 3.6 added __init_subclass__ as a way to hook into subclass creation without writing a metaclass. It covers most use cases.

class Base:
    _registry = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Base._registry[cls.__name__] = cls

class UserModel(Base):
    pass

class ProductModel(Base):
    pass

print(Base._registry)  # {'UserModel': <class ...>, 'ProductModel': <class ...>}

Same result, zero metaclass complexity. For most registration and validation needs, __init_subclass__ is the way to go.

Class Decorators vs Metaclasses

Class decorators can also modify classes after creation. They’re simpler than metaclasses and cover many use cases.

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

@add_repr
class User:
    def __init__(self, name):
        self.name = name

print(User("Manish"))  # User(name='Manish')

When to use which:

  • Class decorator — simple modifications to one class at a time
  • __init_subclass__ — when we need to hook into subclass creation
  • Metaclass — when we need to control class creation itself (modifying namespace, enforcing constraints on ALL subclasses, or interacting with __new__)

Real Use Cases

Metaclasses aren’t something we write every day. But they power some of the most-used libraries:

  • Django ORMModelBase metaclass collects field definitions and builds database mappings
  • SQLAlchemy — metaclass handles declarative model registration
  • ABCMeta — Python’s own abc.ABC uses a metaclass to track abstract methods
  • EnumEnumMeta metaclass enforces unique values and prevents instantiation

In simple language, metaclasses are classes that create other classes. type is the default metaclass, and we can write custom ones to control how classes are built. But most of the time, __init_subclass__ or a class decorator gets the job done without the complexity.