Metaclasses
Learn what metaclasses are, how type() works, and how to create custom metaclasses in Python.
Metaclasses
This is the most advanced topic in this entire guide. Most Python developers never need to write a metaclass directly — but understanding them explains a lot of "magic" you see in popular frameworks like Django, SQLAlchemy, and Pydantic.
Take your time here. Build up slowly.
Everything in Python is an object
You already know this for normal values:
x = 42
name = "Ali"
items = [1, 2, 3]But it is also true for classes:
class Dog:
def bark(self):
print("Woof!")
print(type(x)) # <class 'int'>
print(type(name)) # <class 'str'>
print(type(Dog)) # <class 'type'>Dog is an object too. Its type is type. That means type is the class of classes — it is what creates class objects.
type() — two different jobs
type() does two completely different things depending on how you call it:
One argument — check the type:
print(type(42)) # <class 'int'>
print(type("hello")) # <class 'str'>
print(type([1, 2, 3])) # <class 'list'>Three arguments — create a new class:
# type(name, bases, namespace)
Dog = type("Dog", (), {"sound": "Woof"})
print(Dog) # <class '__main__.Dog'>
print(Dog.sound) # Woof
print(type(Dog)) # <class 'type'>This is exactly what Python does when it processes a class statement. Writing:
class Dog:
sound = "Woof"Is equivalent to:
Dog = type("Dog", (), {"sound": "Woof"})The class keyword is syntactic sugar — type is doing the actual work.
Creating a class with type() fully
def bark(self):
print(f"{self.name} says: Woof!")
def __init__(self, name):
self.name = name
Dog = type(
"Dog", # class name
(object,), # base classes — tuple
{ # class namespace
"__init__": __init__,
"bark": bark,
"species": "Canis familiaris"
}
)
rex = Dog("Rex")
rex.bark() # Rex says: Woof!
print(Dog.species) # Canis familiaris
print(type(Dog)) # <class 'type'>What is a metaclass?
A metaclass is a class whose instances are classes. Just like a class defines how its instances behave, a metaclass defines how its classes behave.
normal: class → creates → instance
metaclass: metaclass → creates → classtype is the default metaclass for every class in Python. When you write class Dog:, Python uses type to build the Dog class object.
A custom metaclass lets you intercept and customize this process.
Creating a custom metaclass
Inherit from type:
class MyMeta(type):
def __new__(mcs, name, bases, namespace):
print(f"Creating class: {name}")
return super().__new__(mcs, name, bases, namespace)Use it with metaclass=:
class Dog(metaclass=MyMeta):
def bark(self):
print("Woof!")
class Cat(metaclass=MyMeta):
def meow(self):
print("Meow!")Output — this runs when the class is defined, not when you create instances:
Creating class: Dog
Creating class: Cat__new__ on a metaclass receives:
mcs— the metaclass itselfname— the name of the class being createdbases— tuple of base classesnamespace— dictionary of the class body
Real example 1 — enforcing naming conventions
A metaclass that enforces all methods in a class must be lowercase:
class EnforceLowercase(type):
def __new__(mcs, name, bases, namespace):
for key in namespace:
if key.startswith("_"):
continue # skip dunder methods
if key != key.lower():
raise TypeError(
f"Method '{key}' in class '{name}' must be lowercase. "
f"Use '{key.lower()}' instead."
)
return super().__new__(mcs, name, bases, namespace)
class GoodAPI(metaclass=EnforceLowercase):
def get_user(self): # fine — lowercase
pass
def create_order(self): # fine — lowercase
pass
class BadAPI(metaclass=EnforceLowercase):
def GetUser(self): # TypeError — not lowercase
passOutput:
TypeError: Method 'GetUser' in class 'BadAPI' must be lowercase. Use 'getuser' instead.The error happens when the class is defined — before any instance is ever created.
Real example 2 — automatic class registration
A common pattern in frameworks — automatically register every subclass so you can look them up by name later:
class PluginMeta(type):
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if bases: # skip the base class itself
mcs.registry[name] = cls
print(f"Registered plugin: {name}")
return cls
class Plugin(metaclass=PluginMeta):
"""Base class for all plugins."""
def run(self):
raise NotImplementedError
class CSVPlugin(Plugin):
def run(self):
print("Processing CSV data")
class JSONPlugin(Plugin):
def run(self):
print("Processing JSON data")
class XMLPlugin(Plugin):
def run(self):
print("Processing XML data")
print(PluginMeta.registry)
# {'CSVPlugin': <class 'CSVPlugin'>, 'JSONPlugin': <class 'JSONPlugin'>, 'XMLPlugin': <class 'XMLPlugin'>}
# look up and use a plugin by name
plugin_name = "JSONPlugin"
plugin = PluginMeta.registry[plugin_name]()
plugin.run() # Processing JSON dataOutput:
Registered plugin: CSVPlugin
Registered plugin: JSONPlugin
Registered plugin: XMLPluginEvery subclass of Plugin is automatically registered the moment it is defined. No manual registration needed — the metaclass handles it.
Real example 3 — adding methods automatically
A metaclass that automatically adds a to_dict() method to every class:
class AutoDict(type):
def __new__(mcs, name, bases, namespace):
def to_dict(self):
return {
key: value
for key, value in self.__dict__.items()
if not key.startswith("_")
}
namespace["to_dict"] = to_dict
return super().__new__(mcs, name, bases, namespace)
class User(metaclass=AutoDict):
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
class Product(metaclass=AutoDict):
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
user = User("Ali", 22, "ali@example.com")
print(user.to_dict())
# {'name': 'Ali', 'age': 22, 'email': 'ali@example.com'}
product = Product("Laptop", 999.99, 50)
print(product.to_dict())
# {'name': 'Laptop', 'price': 999.99, 'stock': 50}Both classes got to_dict() automatically — without inheriting from anything or manually defining it.
init_subclass — a simpler alternative
Before reaching for a metaclass, check if __init_subclass__ solves your problem. It is simpler and works for most common use cases — it runs every time a class inherits from your base class:
class Plugin:
registry = {}
def __init_subclass__(cls, plugin_type=None, **kwargs):
super().__init_subclass__(**kwargs)
if plugin_type:
Plugin.registry[plugin_type] = cls
print(f"Registered: {plugin_type} → {cls.__name__}")
class CSVPlugin(Plugin, plugin_type="csv"):
def run(self):
print("Processing CSV")
class JSONPlugin(Plugin, plugin_type="json"):
def run(self):
print("Processing JSON")
print(Plugin.registry)
# {'csv': <class 'CSVPlugin'>, 'json': <class 'JSONPlugin'>}
Plugin.registry["csv"]().run() # Processing CSVOutput:
Registered: csv → CSVPlugin
Registered: json → JSONPluginUse __init_subclass__ before writing a metaclass. It handles most subclass customization cases with far less complexity. Only reach for a metaclass when you need to customize the class creation process itself — modifying the namespace, changing bases, intercepting __new__ on the class level.
class decorators — another simpler alternative
Class decorators can also modify a class after it is created — simpler than a metaclass for many use cases:
def add_repr(cls):
"""Add a __repr__ to any class automatically."""
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 User:
def __init__(self, name, age):
self.name = name
self.age = age
@add_repr
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
print(User("Ali", 22)) # User(name='Ali', age=22)
print(Product("Laptop", 999)) # Product(name='Laptop', price=999)When to use each tool
| Tool | Use when |
|---|---|
| Class decorator | Modify or add to a class after it is defined |
__init_subclass__ | Customize behavior when subclassing |
| Metaclass | Intercept class creation itself — modify namespace, enforce rules at definition time |
dataclasses | Auto-generate __init__, __repr__, __eq__ — covers most data class needs |
A well-known Python saying: if you think you need a metaclass, you probably do not. They are powerful but complex. Most problems that feel like they need a metaclass can be solved more cleanly with __init_subclass__, class decorators, or dataclasses. Use metaclasses when you are building a framework and genuinely need to control the class creation process.
Summary
| Concept | Meaning |
|---|---|
type(x) | Get the type of x |
type(name, bases, namespace) | Create a class dynamically |
| Metaclass | The class that creates a class — default is type |
| Custom metaclass | Inherit from type, override __new__ |
metaclass=MyMeta | Use a custom metaclass for a class |
__init_subclass__ | Simpler alternative for subclass customization |
| Class decorator | Simpler alternative for post-creation modification |