The Secret Life of Python: Metaclasses - Classes That Make Classes
Source: Dev.to
Introduction
Classes are objects. Their type is their metaclass. And type is the ultimate metaclass—it makes classes, including itself.
Timothy had been working with Python classes for years. He understood inheritance, methods, attributes, and the MRO. One afternoon, while debugging, he typed something that broke his mental model of Python entirely.
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name} says woof!"
# Create an instance
fido = Dog("Fido")
# Check the type
print(type(fido)) #
# So far so good. But then...
print(type(Dog)) #
Timothy stared at the output. “Type of Dog is… type? What does that even mean?”
print(type(type)) #
“Type of type is type?!” Timothy’s voice rose in confusion.
Margaret, noticing his puzzlement, explained:
“You’ve discovered that classes are objects too. And like all objects, they have a type. The type that makes classes is called a metaclass.”
The conversation continued, revealing that type is its own metaclass—the class that makes classes, including itself.
Everything Is an Object
Inspecting Instances and Classes
class Dog:
species = "Canis familiaris"
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name} says woof!"
# Instance
fido = Dog("Fido")
print(type(fido)) #
print(isinstance(fido, Dog)) # True
print(fido.__class__) #
What is Dog?
# Classes are objects
print(type(Dog)) #
print(Dog.__class__) #
Because classes are first‑class objects, they can be assigned, passed around, and stored just like any other value:
# Assign to another name
MyDog = Dog
rover = MyDog("Rover")
print(rover.bark()) # Rover says woof!
# Pass as argument
def create_instance(cls, *args):
return cls(*args)
buddy = create_instance(Dog, "Buddy")
print(buddy.bark()) # Buddy says woof!
# Store in a collection
animal_classes = [Dog]
pet = animal_classes[0]("Max")
print(pet.bark()) # Max says woof!
Since classes are objects, they have a type—their metaclass.
The type Metaclass
type as the Default Metaclass
When you write a class definition, Python uses type to create the class object.
class Dog:
def bark(self):
return "Woof!"
# Roughly equivalent to:
Dog = type('Dog', (), {'bark': lambda self: "Woof!"})
# Verify
fido = Dog()
print(fido.bark()) # Woof!
print(type(Dog)) #
Using type() Directly
type() can be called in two ways:
One argument – returns the type of an object:
print(type(42)) #
print(type("hello")) #
print(type([1, 2, 3])) #
Three arguments – creates a new class:
# type(name, bases, dict)
Dog = type(
'Dog', # class name
(), # base classes (empty tuple → inherits from object)
{
'species': 'Canis familiaris',
'bark': lambda self: "Woof!"
}
)
print(Dog.species) # Canis familiaris
fido = Dog()
print(fido.bark()) # Woof!
A more elaborate example:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
return f"{self.name} (age {self.age}) says woof!"
def birthday(self):
self.age += 1
return f"{self.name} is now {self.age} years old!"
Dog = type(
'Dog',
(object,),
{
'__init__': __init__,
'bark': bark,
'birthday': birthday,
'species': 'Canis familiaris'
}
)
fido = Dog("Fido", 3)
print(fido.bark()) # Fido (age 3) says woof!
print(fido.birthday()) # Fido is now 4 years old!
print(fido.bark()) # Fido (age 4) says woof!
Thus, the class statement is syntactic sugar for a call to the metaclass (normally type).
Creating a Custom Metaclass
You can define your own metaclass by subclassing type.
class Meta(type):
"""A simple custom metaclass"""
def __new__(mcs, name, bases, namespace):
print(f"Creating class {name}")
print(f" Bases: {bases}")
print(f" Attributes: {list(namespace.keys())}")
# Actually create the class
cls = super().__new__(mcs, name, bases, namespace)
return cls
class Dog(metaclass=Meta):
def bark(self):
return "Woof!"
# During class creation the metaclass prints:
# Creating class Dog
# Bases: ()
# Attributes: ['__module__', '__qualname__', 'bark']
fido = Dog()
print(fido.bark()) # Woof!
How a Metaclass Works
__new__(mcs, name, bases, namespace)creates the class object before__init__runs.mcsstands for “metaclass” (by convention, analogous toclsfor classes andselffor instances).- Parameters:
mcs– the metaclass itselfname– class name as a stringbases– tuple of base classesnamespace– dictionary of attributes and methods
You can also implement __init__ in a metaclass to perform additional initialization after the class has been created. This flexibility lets you inject behavior, enforce constraints, or automatically register classes.