You’ve probably heard the phrase: “In Python, everything is an object.” That includes functions, classes, and yes—even types themselves. At the heart of this is a concept many avoid: metaclasses.
Metaclasses let you control the creation and behavior of classes themselves. They are what create classes, just like classes create objects.
This blog will teach you:
A metaclass is the “class of a class.”
Let’s build a mental model:
In Python, the default metaclass is type
.
object ← MyClass ← my_instance
↑
type
This means:
my_instance
is an instance of MyClass
MyClass
is an instance of type
>>> class MyClass:
... pass
>>> isinstance(MyClass, type)
True
>>> isinstance(MyClass(), MyClass)
True
Normally, when you write:
class MyClass:
pass
Python internally does:
MyClass = type("MyClass", (object,), {})
So type()
can be used not only to get a type, but also to create classes!
Feature | Class Inheritance | Metaclass |
---|---|---|
Operates on | Instances | Classes themselves |
Called during | __init__ of the object |
type.__new__ of the class |
Customization type | Behavior of instances | Behavior of class definitions |
Use case | Polymorphism, shared methods | Class validation, registration, APIs |
class Base:
def greet(self):
print("Hello from Base")
class Child(Base):
pass
child = Child()
child.greet() # Output: Hello from Base
class Meta(type):
def __new__(cls, name, bases, dct):
print(f"Defining class {name}")
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=Meta):
pass
Output: Defining class MyClass
registry = {}
class RegisterType(type):
def __init__(cls, name, bases, dct):
registry[name] = cls
super().__init__(name, bases, dct)
class PluginBase(metaclass=RegisterType):
pass
class PluginA(PluginBase):
pass
print(registry)
class EnforceMethod(type):
def __init__(cls, name, bases, dct):
if 'process' not in dct:
raise TypeError(f"{name} must define a 'process' method")
super().__init__(name, bases, dct)
Frameworks like SQLAlchemy use metaclasses to map class attributes to database columns.
type
class MyMeta(type):
def __new__(cls, name, bases, dct):
print(f"Creating class {name}")
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=MyMeta):
pass
Output: Creating class MyClass
Your class: MyClass
↓ (created by)
Metaclass: MyMeta (subclass of type)
class RequireRunMethod(type):
def __init__(cls, name, bases, dct):
if not callable(dct.get('run', None)):
raise TypeError(f"Class '{name}' must define a 'run' method")
super().__init__(name, bases, dct)
class Base(metaclass=RequireRunMethod):
pass
class Good(Base):
def run(self):
print("Running!")
# This will raise TypeError
class Bad(Base):
pass
type()
work in class creation?Unless explicitly asked, prefer class decorators or mixins—only use metaclasses when you must intervene at class creation time.
__init_subclass__()
: A modern alternative to metaclassesMetaclasses are the secret sauce behind some of Python’s most powerful tools. They let you step behind the curtain and customize how classes themselves behave.
Although they’re not common in beginner-level code, understanding them gives you deep insight into Python’s object model, and empowers you to build flexible, reusable architectures.
Use them wisely and when necessary. For simpler needs, class inheritance or decorators often do the job just fine.