In Python, everything is an object — including classes themselves. A metaclass is the “class of a class.” It’s what creates and configures class objects, the same way a class creates and configures instances.
In simple language, if a class is a blueprint for objects, then a metaclass is a blueprint for classes.
type() Is the Default Metaclass
Every class we write is secretly created by type. We can even see this:
class Dog:
pass
print(type(Dog)) # <class 'type'>
print(type(Dog())) # <class 'Dog'>
So Dog is an instance of type, and Dog() is an instance of Dog.
Creating Classes Dynamically with type()
We can create classes on the fly using type(name, bases, dict):
# These two are equivalent
class Dog:
sound = "woof"
Dog = type("Dog", (), {"sound": "woof"})
This is what Python does under the hood every time we write a class statement.
Writing a Custom Metaclass
A custom metaclass inherits from type and overrides __new__ or __init__ to customize class creation.
class ValidatedMeta(type):
def __new__(mcs, name, bases, namespace):
# Require all classes to have a 'version' attribute
if "version" not in namespace:
raise TypeError(f"{name} must define a 'version' attribute")
return super().__new__(mcs, name, bases, namespace)
class MyPlugin(metaclass=ValidatedMeta):
version = "1.0" # this works
class BadPlugin(metaclass=ValidatedMeta):
pass # TypeError: BadPlugin must define a 'version' attribute
When Python sees metaclass=ValidatedMeta, it calls ValidatedMeta.__new__() instead of type.__new__() to create the class.
Practical Uses
Metaclasses are used in some well-known libraries:
- Django ORM —
Modelclasses use metaclasses to turn field definitions into database schema - Abstract Base Classes —
ABCMetaenforces that subclasses implement required methods - Plugin registration — auto-register every subclass in a registry
Here’s a registration example:
class PluginMeta(type):
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if bases: # don't register the base class itself
mcs.registry[name] = cls
return cls
class Plugin(metaclass=PluginMeta):
pass
class AuthPlugin(Plugin):
pass
class CachePlugin(Plugin):
pass
print(PluginMeta.registry)
# {'AuthPlugin': <class 'AuthPlugin'>, 'CachePlugin': <class 'CachePlugin'>}
The Simpler Alternative: init_subclass
Since Python 3.6, we have __init_subclass__() which handles most use cases without needing a metaclass.
class Plugin:
registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin.registry[cls.__name__] = cls
class AuthPlugin(Plugin):
pass
print(Plugin.registry) # {'AuthPlugin': <class 'AuthPlugin'>}
Much simpler. Same result.
When NOT to Use Metaclasses
Metaclasses are powerful but rarely needed. Before reaching for one, consider:
__init_subclass__— for subclass hooks (Python 3.6+)- Class decorators — for modifying a class after creation
- Descriptors — for custom attribute behavior
The famous quote: “Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t.” — Tim Peters
In simple language, metaclasses let us control how classes are built. They’re the factory that produces factories. Incredibly powerful, but for most of us, __init_subclass__ and class decorators will do the job.