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, knew about methods and attributes, and could even explain the MRO. But 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)) # <class '__main__.Dog'>
# So far so good. But then...
print(type(Dog)) # <class 'type'>
Timothy stared at the output. "Type of Dog is... type? What does that even mean?"
He typed more:
print(type(type)) # <class 'type'>
"Type of type is type?!" Timothy's voice rose in confusion, drawing Margaret's attention from across the library.
"Ah," Margaret said, walking over with a knowing smile. "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."
"But... classes make objects! How can classes be objects?"
"Everything in Python is an object, Timothy. Even classes. And every object has a type. The type of regular objects is their class. But the type of a class... is its metaclass."
Timothy's confusion deepened. "So what makes the metaclass? What's its type?"
Margaret's smile widened. "Let me show you Python's most elegant trick: type is its own metaclass. It's the class that makes classes, including itself."
"That sounds impossible!"
"It sounds impossible," Margaret agreed, "but it's beautifully simple once you see how it works. Come, let me show you the secret life of metaclasses."
Everything is an Object
"Let's start with what you know," Margaret said, opening a notebook. "When you create a class, what happens?"
class Dog:
species = "Canis familiaris"
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name} says woof!"
# Create an instance
fido = Dog("Fido")
"I create a class Dog, then create an instance fido," Timothy replied.
"Good. Now, what is fido?"
"An object. An instance of the Dog class."
"Exactly. And you can inspect it:"
print(type(fido)) # <class '__main__.Dog'>
print(isinstance(fido, Dog)) # True
print(fido.__class__) # <class '__main__.Dog'>
"But here's the question," Margaret continued. "What is Dog?"
Timothy hesitated. "It's... a class?"
"Yes, but it's also an object. In Python, classes are first-class objects. You can assign them to variables, pass them as arguments, store them in lists, and inspect them just like any other object."
# Classes are objects
print(type(Dog)) # <class 'type'>
print(Dog.__class__) # <class 'type'>
# You can assign classes to variables
MyDog = Dog
rover = MyDog("Rover")
print(rover.bark()) # Rover says woof!
# You can pass classes as arguments
def create_instance(cls, *args):
return cls(*args)
buddy = create_instance(Dog, "Buddy")
print(buddy.bark()) # Buddy says woof!
# You can store classes in data structures
animal_classes = [Dog]
pet = animal_classes[0]("Max")
print(pet.bark()) # Max says woof!
"If classes are objects," Margaret said, "then they must have a type. And that type is called their metaclass."
The type Metaclass
"The default metaclass in Python is type," Margaret explained. "When you write a class definition, Python uses type to create the class object."
class Dog:
def bark(self):
return "Woof!"
# This is (roughly) equivalent to:
Dog = type('Dog', (), {'bark': lambda self: "Woof!"})
# Let's verify they work the same
fido = Dog()
print(fido.bark()) # Woof!
print(type(Dog)) # <class 'type'>
Timothy examined the type() call. "What are those arguments?"
"The type() function can be called in two ways," Margaret said:
One argument: returns the type of an object
print(type(42)) # <class 'int'>
print(type("hello")) # <class 'str'>
print(type([1, 2, 3])) # <class 'list'>
Three arguments: creates a new class
# type(name, bases, dict)
# - name: string, the class name
# - bases: tuple of base classes
# - dict: dictionary of class attributes and methods
Dog = type(
'Dog', # class name
(), # base classes (empty tuple = inherits from object)
{ # class dictionary
'species': 'Canis familiaris',
'bark': lambda self: "Woof!"
}
)
print(Dog.species) # Canis familiaris
fido = Dog()
print(fido.bark()) # Woof!
"Let's create a more complex class using type():"
# Define methods separately for clarity
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!"
# Create the class
Dog = type(
'Dog',
(object,), # explicitly inherit from object
{
'__init__': __init__,
'bark': bark,
'birthday': birthday,
'species': 'Canis familiaris'
}
)
# Use it
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!
"So when I write class Dog:, Python is calling type() behind the scenes?" Timothy asked.
"Exactly! The class statement is syntactic sugar. Python:
- Collects the class body into a dictionary
- Calls the metaclass (usually
type) with the name, bases, and dictionary - Binds the result to the class name
The class syntax is just a more readable way to do what type() does explicitly."
Creating a Custom Metaclass
"Now for the interesting part," Margaret said. "You can create 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())}")
# Call the parent metaclass to actually create the class
cls = super().__new__(mcs, name, bases, namespace)
return cls
class Dog(metaclass=Meta):
def bark(self):
return "Woof!"
# Output during class creation:
# Creating class Dog
# Bases: ()
# Attributes: ['__module__', '__qualname__', 'bark']
fido = Dog()
print(fido.bark()) # Woof!
"Wait," Timothy interrupted. "What's __new__ doing? And why mcs instead of self?"
"Excellent questions! In a metaclass:
-
__new__creates the class object (called before__init__) -
mcsstands for 'metaclass' (by convention, likeclsfor class,selffor instance) - The parameters are:
-
mcs: the metaclass itself (likeselffor instances) -
name: the class name as a string -
bases: tuple of base classes -
namespace: dictionary of class attributes and methods
-
Let me show you both __new__ and __init__:"
class VerboseMeta(type):
"""Metaclass that logs both __new__ and __init__"""
def __new__(mcs, name, bases, namespace):
print(f"VerboseMeta.__new__ called")
print(f" Creating class: {name}")
cls = super().__new__(mcs, name, bases, namespace)
print(f" Class created: {cls}")
return cls
def __init__(cls, name, bases, namespace):
print(f"VerboseMeta.__init__ called")
print(f" Initializing class: {cls}")
super().__init__(name, bases, namespace)
print(f" Class initialized")
class Example(metaclass=VerboseMeta):
value = 42
# Output:
# VerboseMeta.__new__ called
# Creating class: Example
# Class created: <class '__main__.Example'>
# VerboseMeta.__init__ called
# Initializing class: <class '__main__.Example'>
# Class initialized
"Notice the order: __new__ creates the class object, then __init__ initializes it. This is the same pattern as with regular classes, but here we're creating the class itself, not an instance."
Practical Metaclass: Validation
"Let's build something useful," Margaret suggested. "A metaclass that validates class attributes."
class ValidatedMeta(type):
"""Metaclass that enforces attribute naming conventions"""
def __new__(mcs, name, bases, namespace):
# Check for invalid attribute names
for attr_name, attr_value in namespace.items():
# Skip special attributes
if attr_name.startswith('_'):
continue
# Enforce: all public attributes must be lowercase
if not attr_name.islower():
raise ValueError(
f"Attribute '{attr_name}' in class '{name}' must be lowercase"
)
# Enforce: methods must have docstrings
# Note: For extra safety in production code, use:
# if callable(attr_value) and not getattr(attr_value, '__doc__', None):
# But all standard Python callables have __doc__ (even if None)
if callable(attr_value) and not attr_value.__doc__:
raise ValueError(
f"Method '{attr_name}' in class '{name}' must have a docstring"
)
return super().__new__(mcs, name, bases, namespace)
class GoodClass(metaclass=ValidatedMeta):
value = 42
def process(self):
"""Process the data"""
return self.value * 2
# This works fine
obj = GoodClass()
print(obj.process()) # 84
# But this raises an error:
try:
class BadClass(metaclass=ValidatedMeta):
InvalidName = 42 # Not lowercase!
except ValueError as e:
print(e) # Attribute 'InvalidName' in class 'BadClass' must be lowercase
# And this also raises an error:
try:
class BadClass2(metaclass=ValidatedMeta):
def process(self): # No docstring!
return 42
except ValueError as e:
print(e) # Method 'process' in class 'BadClass2' must have a docstring
"The metaclass runs at class creation time," Margaret emphasized. "So validation happens when you define the class, not when you create instances. This catches errors early."
Metaclass: Automatic Registration
"Another common pattern," Margaret said, "is automatic registration."
class PluginMeta(type):
"""Metaclass that automatically registers plugins"""
# Class attribute to store all plugins
plugins = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Don't register the base class itself
if name != 'Plugin':
# Register this plugin by name
mcs.plugins[name] = cls
print(f"Registered plugin: {name}")
return cls
class Plugin(metaclass=PluginMeta):
"""Base class for all plugins"""
def execute(self):
raise NotImplementedError
class EmailPlugin(Plugin):
def execute(self):
return "Sending email..."
class SMSPlugin(Plugin):
def execute(self):
return "Sending SMS..."
class LogPlugin(Plugin):
def execute(self):
return "Writing to log..."
# Output during class creation:
# Registered plugin: EmailPlugin
# Registered plugin: SMSPlugin
# Registered plugin: LogPlugin
# Now we can access all plugins
print("Available plugins:", list(PluginMeta.plugins.keys()))
# Available plugins: ['EmailPlugin', 'SMSPlugin', 'LogPlugin']
# Use them dynamically
for plugin_name, plugin_class in PluginMeta.plugins.items():
plugin = plugin_class()
print(f"{plugin_name}: {plugin.execute()}")
# EmailPlugin: Sending email...
# SMSPlugin: Sending SMS...
# LogPlugin: Writing to log...
Timothy's eyes lit up. "So the metaclass runs when each subclass is defined, and automatically registers it. That's how Django models work, isn't it?"
"Exactly! Django's model metaclass registers each model with the ORM. Let me show you a simplified version:"
Real-World Example: Django-Style Models
class ModelMeta(type):
"""Simplified Django-style model metaclass"""
# Registry of all models
_models = {}
def __new__(mcs, name, bases, namespace):
# Create the class
cls = super().__new__(mcs, name, bases, namespace)
# Skip the base Model class
if name == 'Model':
return cls
# Collect fields (attributes that are Field instances)
fields = {}
for attr_name, attr_value in namespace.items():
if isinstance(attr_value, Field):
fields[attr_name] = attr_value
attr_value.name = attr_name # Store field name
# Store fields on the class
cls._fields = fields
# Register the model
mcs._models[name] = cls
return cls
class Field:
"""Base class for model fields
Note: In real Django, fields are descriptors (like we covered in the
Attribute Lookup article). They implement __get__ and __set__ to
intercept attribute access. We're simplifying here to focus on the
metaclass mechanics, but the full pattern combines metaclasses with
the descriptor protocol for maximum power!
"""
def __init__(self, field_type, required=True):
self.field_type = field_type
self.required = required
self.name = None # Will be set by metaclass
# Real Django fields are descriptors - they'd have:
# def __get__(self, obj, objtype=None):
# if obj is None:
# return self
# return obj.__dict__.get(self.name)
#
# def __set__(self, obj, value):
# # Validation here
# obj.__dict__[self.name] = value
class CharField(Field):
def __init__(self, max_length=255, **kwargs):
super().__init__(str, **kwargs)
self.max_length = max_length
class IntegerField(Field):
def __init__(self, **kwargs):
super().__init__(int, **kwargs)
class Model(metaclass=ModelMeta):
"""Base class for all models"""
def __init__(self, **kwargs):
for field_name, field in self._fields.items():
value = kwargs.get(field_name)
# Validate required fields
if value is None and field.required:
raise ValueError(f"Field '{field_name}' is required")
# Validate type
if value is not None and not isinstance(value, field.field_type):
raise TypeError(
f"Field '{field_name}' must be {field.field_type.__name__}, "
f"got {type(value).__name__}"
)
setattr(self, field_name, value)
def __repr__(self):
field_strs = [f"{name}={getattr(self, name)}"
for name in self._fields.keys()]
return f"{self.__class__.__name__}({', '.join(field_strs)})"
# Define models
class User(Model):
username = CharField(max_length=50)
email = CharField(max_length=100)
age = IntegerField(required=False)
class Article(Model):
title = CharField(max_length=200)
content = CharField(max_length=5000)
views = IntegerField()
# The metaclass has collected the fields
print("User fields:", list(User._fields.keys()))
# User fields: ['username', 'email', 'age']
print("Article fields:", list(Article._fields.keys()))
# Article fields: ['title', 'content', 'views']
# Create instances with validation
user = User(username="alice", email="alice@example.com", age=30)
print(user)
# User(username=alice, email=alice@example.com, age=30)
# Validation works
try:
bad_user = User(username="bob") # Missing required 'email'
except ValueError as e:
print(e) # Field 'email' is required
try:
bad_user2 = User(username="charlie", email="charlie@example.com", age="thirty")
except TypeError as e:
print(e) # Field 'age' must be int, got str
# All models are registered
print("Registered models:", list(ModelMeta._models.keys()))
# Registered models: ['User', 'Article']
"This is brilliant!" Timothy exclaimed. "The metaclass runs when you define the model, collects all the fields, and sets everything up. Then when you create an instance, the validation is already in place."
"Exactly. The metaclass does the heavy lifting at class creation time, so instance creation can be fast and clean."
The __init_subclass__ Hook (Python 3.6+)
"Now," Margaret said, "I need to tell you about a simpler alternative for many metaclass use cases: __init_subclass__."
class Plugin:
"""Base class with automatic subclass registration"""
plugins = {}
def __init_subclass__(cls, **kwargs):
"""Called whenever a subclass is created"""
super().__init_subclass__(**kwargs)
# Register the subclass
cls.plugins[cls.__name__] = cls
print(f"Registered plugin: {cls.__name__}")
class EmailPlugin(Plugin):
def send(self):
return "Sending email..."
class SMSPlugin(Plugin):
def send(self):
return "Sending SMS..."
# Output:
# Registered plugin: EmailPlugin
# Registered plugin: SMSPlugin
print("Available plugins:", list(Plugin.plugins.keys()))
# Available plugins: ['EmailPlugin', 'SMSPlugin']
"Wait," Timothy said. "That's much simpler than a metaclass! Why would anyone use metaclasses?"
"Good question! __init_subclass__ was added in Python 3.6 specifically to handle common metaclass use cases more simply. Use it when you can:"
class ValidationBase:
"""Base class that validates subclass attributes"""
def __init_subclass__(cls, require_docstring=True, **kwargs):
super().__init_subclass__(**kwargs)
# Check for docstring
if require_docstring and not cls.__doc__:
raise ValueError(f"Class {cls.__name__} must have a docstring")
# Check method docstrings
for name, value in cls.__dict__.items():
if callable(value) and not name.startswith('_'):
if not value.__doc__:
raise ValueError(
f"Method {cls.__name__}.{name} must have a docstring"
)
class GoodClass(ValidationBase):
"""This class has a docstring"""
def process(self):
"""Process the data"""
return 42
# This works
obj = GoodClass()
# But this fails:
try:
class BadClass(ValidationBase):
# No docstring!
def process(self):
"""This method is documented"""
return 42
except ValueError as e:
print(e) # Class BadClass must have a docstring
# You can pass arguments to __init_subclass__
class LenientClass(ValidationBase, require_docstring=False):
# No docstring, but that's ok because we passed require_docstring=False
def process(self):
"""This method is documented"""
return 42
"Use __init_subclass__ when you need to customize subclass creation. Use a metaclass when you need more control over the class object itself."
When You Need a Metaclass
"So when do you actually need a full metaclass?" Timothy asked.
Margaret outlined the cases:
1. Modifying the class dictionary before class creation
class OrderedMeta(type):
"""Metaclass that uses custom namespace for tracking attribute order"""
@classmethod
def __prepare__(mcs, name, bases):
"""Called before the class body is executed
Note: Python 3.7+ dicts are ordered by default, so OrderedDict
isn't needed for simple ordering. But __prepare__ is still useful
for custom namespace behavior.
"""
print(f"Preparing namespace for {name}")
# Return a custom dict subclass that tracks access patterns
class NamespaceDict(dict):
def __init__(self):
super().__init__()
self.access_order = []
def __setitem__(self, key, value):
if key not in self and not key.startswith('_'):
self.access_order.append(key)
super().__setitem__(key, value)
return NamespaceDict()
def __new__(mcs, name, bases, namespace):
print(f"Creating {name} with attributes defined in order:")
# Access the custom attribute we added to NamespaceDict
if hasattr(namespace, 'access_order'):
for key in namespace.access_order:
print(f" {key}")
return super().__new__(mcs, name, bases, dict(namespace))
class Example(metaclass=OrderedMeta):
third = 3
first = 1
second = 2
# Output:
# Preparing namespace for Example
# Creating Example with attributes defined in order:
# third
# first
# second
2. Modifying or wrapping class methods
class TimedMeta(type):
"""Metaclass that adds timing to all methods"""
def __new__(mcs, name, bases, namespace):
import time
from functools import wraps
# Wrap all methods with timing
for attr_name, attr_value in namespace.items():
if callable(attr_value) and not attr_name.startswith('_'):
original_method = attr_value
def make_timed(method):
@wraps(method) # Preserve original function metadata
def timed_method(*args, **kwargs):
start = time.time()
result = method(*args, **kwargs)
end = time.time()
print(f"{method.__name__} took {end - start:.4f}s")
return result
return timed_method
namespace[attr_name] = make_timed(original_method)
return super().__new__(mcs, name, bases, namespace)
class Calculator(metaclass=TimedMeta):
def add(self, a, b):
import time
time.sleep(0.1) # Simulate work
return a + b
def multiply(self, a, b):
import time
time.sleep(0.2) # Simulate work
return a * b
calc = Calculator()
result = calc.add(2, 3) # add took 0.1001s
result = calc.multiply(4, 5) # multiply took 0.2002s
3. Enforcing metaclass constraints across inheritance
class SingletonMeta(type):
"""Metaclass that ensures only one instance exists"""
_instances = {}
def __call__(cls, *args, **kwargs):
"""Called when you do ClassName() to create instance
Important: When you write Database(), you're "calling" the Database
class object. Since Database is an instance of SingletonMeta, this
triggers SingletonMeta.__call__ - that's how the metaclass can
intercept instance creation!
"""
if cls not in cls._instances:
# First time - create the instance
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self, connection_string):
self.connection_string = connection_string
print(f"Connecting to {connection_string}")
# First call creates the instance
db1 = Database("mysql://localhost")
# Connecting to mysql://localhost
# Second call returns the same instance
db2 = Database("postgresql://localhost") # No new connection!
print(db1 is db2) # True - same object
print(db1.connection_string) # mysql://localhost (from first call)
Metaclass Conflicts in Multiple Inheritance
"One tricky issue," Margaret warned, "is metaclass conflicts."
class Meta1(type):
pass
class Meta2(type):
pass
class A(metaclass=Meta1):
pass
class B(metaclass=Meta2):
pass
# This fails!
try:
class C(A, B): # Can't have two different metaclasses
pass
except TypeError as e:
print(e)
# TypeError: metaclass conflict: the metaclass of a derived class
# must be a (non-strict) subclass of the metaclasses of all its bases
"The solution is to create a metaclass that inherits from both:"
class CombinedMeta(Meta1, Meta2):
"""Metaclass that combines Meta1 and Meta2"""
pass
class A(metaclass=Meta1):
pass
class B(metaclass=Meta2):
pass
class C(A, B, metaclass=CombinedMeta):
"""Now it works!"""
pass
print(type(C)) # <class '__main__.CombinedMeta'>
print(isinstance(C, Meta1)) # False (C is a class, not instance)
print(issubclass(type(C), Meta1)) # True
print(issubclass(type(C), Meta2)) # True
Real-World Pattern: Abstract Base Classes
"Now let me show you how Python's abc module uses metaclasses," Margaret said.
from abc import ABCMeta, abstractmethod
class Shape(metaclass=ABCMeta):
"""Abstract base class for shapes"""
@abstractmethod
def area(self):
"""Calculate the area"""
pass
@abstractmethod
def perimeter(self):
"""Calculate the perimeter"""
pass
# Can't instantiate abstract class
try:
shape = Shape()
except TypeError as e:
print(e)
# Can't instantiate abstract class Shape with abstract methods area, perimeter
"Note," Margaret added, "that Python provides a convenience class ABC that's just a helper wrapping ABCMeta. Most modern code uses it:"
from abc import ABC, abstractmethod
class Shape(ABC): # Equivalent to metaclass=ABCMeta
"""Abstract base class for shapes"""
@abstractmethod
def area(self):
pass
"Both forms work identically—ABC is just syntactic sugar that makes the common case cleaner."
She continued with the full example:
from abc import ABCMeta, abstractmethod
class Shape(metaclass=ABCMeta):
"""Abstract base class for shapes"""
@abstractmethod
def area(self):
"""Calculate the area"""
pass
@abstractmethod
def perimeter(self):
"""Calculate the perimeter"""
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# This works - all abstract methods implemented
rect = Rectangle(5, 3)
print(f"Area: {rect.area()}") # Area: 15
print(f"Perimeter: {rect.perimeter()}") # Perimeter: 16
# But partial implementation fails
try:
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
# Missing perimeter()!
circle = Circle(5)
except TypeError as e:
print(e)
# Can't instantiate abstract class Circle with abstract method perimeter
"The ABCMeta metaclass tracks which methods are abstract and prevents instantiation until they're all implemented. This is impossible to do without a metaclass because it needs to intercept class creation."
When NOT to Use Metaclasses
"Metaclasses are powerful," Margaret cautioned, "but they're rarely necessary. Here are simpler alternatives:"
"Also remember," she added, "metaclasses run at class definition time, not at runtime. The performance cost is paid once when the class is created (usually at import time), not when you create instances or call methods. So metaclass overhead doesn't slow down your application—but it does make class creation more complex."
class Heavy(metaclass=ComplexMeta): # Runs once at class definition
pass
# Later, in your application:
obj = Heavy() # No metaclass overhead - class already created!
Instead of metaclass for simple validation:
# Don't do this:
class ValidatedMeta(type):
def __new__(mcs, name, bases, namespace):
# Complex validation...
return super().__new__(mcs, name, bases, namespace)
# Do this:
def validate_class(cls):
"""Decorator that validates a class"""
# Validation logic here
return cls
@validate_class
class MyClass:
pass
Instead of metaclass for registration:
# Don't do this:
class RegistryMeta(type):
def __new__(mcs, name, bases, namespace):
# Register the class
return super().__new__(mcs, name, bases, namespace)
# Do this:
class Plugin:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Register the class
register(cls)
Instead of metaclass for instance modification:
# Don't do this:
class SingletonMeta(type):
def __call__(cls, *args, **kwargs):
# Singleton logic
return instance
# Do this:
def singleton(cls):
"""Decorator that makes a class a singleton"""
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
pass
"The rule," Margaret said, "is: metaclasses are the last resort. Try:
- Decorator
__init_subclass__- Regular inheritance
- Composition
- Only then: metaclass"
Debugging Metaclasses
"When working with metaclasses," Margaret advised, "you need to understand the creation order."
class DebugMeta(type):
def __prepare__(mcs, name, bases):
print(f"1. __prepare__: Creating namespace for {name}")
return super().__prepare__(name, bases)
def __new__(mcs, name, bases, namespace):
print(f"2. __new__: Creating class {name}")
cls = super().__new__(mcs, name, bases, namespace)
print(f"3. __new__: Class {name} created")
return cls
def __init__(cls, name, bases, namespace):
print(f"4. __init__: Initializing class {name}")
super().__init__(name, bases, namespace)
print(f"5. __init__: Class {name} initialized")
def __call__(cls, *args, **kwargs):
print(f"6. __call__: Creating instance of {cls.__name__}")
instance = super().__call__(*args, **kwargs)
print(f"7. __call__: Instance created")
return instance
class Example(metaclass=DebugMeta):
def __init__(self):
print(" Example.__init__: Initializing instance")
# Output during class creation:
# 1. __prepare__: Creating namespace for Example
# 2. __new__: Creating class Example
# 3. __new__: Class Example created
# 4. __init__: Initializing class Example
# 5. __init__: Class Example initialized
# Output during instance creation:
obj = Example()
# 6. __call__: Creating instance of Example
# Example.__init__: Initializing instance
# 7. __call__: Instance created
"Notice the order:
Class creation:
-
__prepare__creates the namespace - Class body executes, populating namespace
-
__new__creates the class object -
__init__initializes the class object
Instance creation:
-
__call__on the metaclass interceptsClassName() - It calls the class's
__new__to create the instance - It calls the instance's
__init__to initialize it"
Conclusion: Classes Are Objects Made by Metaclasses
Timothy sat back, his notebook full of diagrams showing the relationships between objects, classes, and metaclasses.
"So let me see if I understand," he said slowly. "Everything in Python is an object. Instances are objects created by classes. Classes are objects created by metaclasses. And the default metaclass is type, which is its own metaclass."
"Exactly!" Margaret beamed. "The hierarchy is:
# Instance level
fido = Dog() # fido is an instance
type(fido) == Dog # type of instance is its class
# Class level
class Dog: pass # Dog is a class (also an object)
type(Dog) == type # type of class is its metaclass
# Metaclass level
type(type) == type # type is its own metaclass
Every object has a type. The type of instances is their class. The type of classes is their metaclass. And type is the metaclass of itself."
"One important distinction," Margaret added. "When we say 'the type of a class,' we're talking about what created it—its metaclass. This is different from what the class inherits from, which is its MRO."
class Dog:
pass
# The metaclass (what created Dog):
print(type(Dog)) # <class 'type'>
print(Dog.__class__) # <class 'type'>
# The MRO (what Dog inherits from):
print(Dog.__mro__) # (<class 'Dog'>, <class 'object'>)
# Notice: 'type' is NOT in Dog's MRO!
# The metaclass creates the class; it's not a parent class.
"Exactly!" Timothy said, the pieces falling into place.
"And metaclasses let you customize class creation," Timothy continued. "You can validate, register, wrap methods, enforce patterns—anything that needs to happen when the class is defined, not when instances are created."
"Precisely. But remember:
- Metaclasses are rare - most problems don't need them
-
Try simpler solutions first - decorators,
__init_subclass__, composition - Metaclasses run at class creation - not instance creation
- Use them for frameworks - Django, SQLAlchemy, ABCs use them because they need to customize classes
- They're not magic - just classes that create classes"
Timothy looked at his original confusing code:
print(type(Dog)) # <class 'type'>
print(type(type)) # <class 'type'>
"It's not mysterious anymore," he said. "Classes are objects. Their type is their metaclass. And type is the ultimate metaclass—it makes classes, including itself."
"You've grasped one of Python's deepest concepts," Margaret said warmly. "Most Python developers go their entire careers without needing custom metaclasses. But understanding how they work—that classes are just objects made by type—that illuminates the entire object model."
"What's next?" Timothy asked, already eager.
"Well," Margaret said with a smile, "now that you understand how classes are created, perhaps you'd like to learn about how Python finds and loads those classes in the first place? The import system has secrets of its own..."
Timothy grinned. "The secret life of imports?"
"Indeed," Margaret laughed. "But that's a story for another day."
Key Takeaways
- Everything is an object - including classes
-
Classes are instances of metaclasses - usually
type -
type(name, bases, dict)creates classes programmatically -
Custom metaclasses inherit from
type -
__new__creates the class,__init__initializes it -
__init_subclass__is simpler for most use cases (Python 3.6+) - Metaclasses run at class creation time - not instance creation
- Use metaclasses for: validation, registration, method wrapping, ABCs
-
Avoid metaclasses when: decorators or
__init_subclass__would work -
type(type) == type- type is its own metaclass
Common Metaclass Patterns
Registration Pattern:
class PluginMeta(type):
plugins = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if name != 'Plugin':
mcs.plugins[name] = cls
return cls
Validation Pattern:
class ValidatedMeta(type):
def __new__(mcs, name, bases, namespace):
# Validate attributes/methods
return super().__new__(mcs, name, bases, namespace)
Singleton Pattern:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
Next in The Secret Life of Python: "The Import System: How Python Finds Your Code"
Discussion Questions
- When would you use a metaclass vs. a class decorator?
- How does Django's model metaclass enable the ORM magic?
- What are the performance implications of metaclasses?
- Can you have a metaclass of a metaclass?
- How do metaclasses interact with the MRO?
Share your metaclass adventures (and misadventures!) in the comments below!
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
Top comments (0)