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.
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 ORM —
ModelBasemetaclass collects field definitions and builds database mappings - SQLAlchemy — metaclass handles declarative model registration
- ABCMeta — Python’s own
abc.ABCuses a metaclass to track abstract methods - Enum —
EnumMetametaclass 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.