type() as a Metaclass
In Python, everything is an object - including classes. The type() built-in is the default metaclass that creates all classes:
class Dog:
species = 'Canis lupus'
def bark(self):
return 'Woof'
# Dog is an instance of type
print(type(Dog)) #
print(type(Dog) is type) # True
print(isinstance(Dog, type)) # True
# type() with 3 args creates a class dynamically
# type(name, bases, namespace)
Cat = type('Cat', (object,), {
'species': 'Felis catus',
'meow': lambda self: 'Meow',
})
c = Cat()
print(c.meow()) # Meow
print(type(Cat)) #
The chain: int is an instance of type; type is an instance of itself; both int and type are subclasses of object:
print(type(int)) #
print(type(type)) #
print(issubclass(int, object)) # True
print(issubclass(type, object)) # True
print(isinstance(type, type)) # True (type is its own metaclass)
# The class hierarchy
# object ← int, str, list, dict, ...
# type ← (metaclass of all above)
How Classes are Created
When Python encounters a class statement, it follows these steps:
# 1. Python reads the class body into a namespace dict
# 2. Determines the metaclass (explicit metaclass= or inherited, default: type)
# 3. Calls: metaclass.__prepare__(name, bases, **kwargs) -> namespace dict
# 4. Executes the class body inside that namespace
# 5. Calls: metaclass(name, bases, namespace) -> class object
# Equivalent to:
class MyClass(Base, metaclass=Meta):
x = 1
# Python roughly does:
namespace = Meta.__prepare__('MyClass', (Base,))
# exec class body in namespace
MyClass = Meta('MyClass', (Base,), namespace)
You can watch this happen with a minimal tracing metaclass:
class TracingMeta(type):
def __new__(mcs, name, bases, namespace):
print(f'__new__: creating class {name!r}')
print(f' bases: {bases}')
print(f' namespace keys: {list(namespace.keys())}')
cls = super().__new__(mcs, name, bases, namespace)
return cls
def __init__(cls, name, bases, namespace):
print(f'__init__: initialising class {name!r}')
super().__init__(name, bases, namespace)
class Animal(metaclass=TracingMeta):
kingdom = 'Animalia'
def breathe(self): pass
class Dog(Animal):
species = 'Canis lupus'
__new__: creating class 'Animal' bases: () namespace keys: ['__module__', '__qualname__', 'kingdom', 'breathe'] __init__: initialising class 'Animal' __new__: creating class 'Dog' bases: (,) namespace keys: ['__module__', '__qualname__', 'species'] __init__: initialising class 'Dog'
Custom Metaclasses
A custom metaclass inherits from type and overrides __new__ or __init__ to modify the class being created:
Enforcing naming conventions
class UpperCaseAttrMeta(type):
def __new__(mcs, name, bases, namespace):
# Convert all non-dunder attribute names to uppercase
upper_namespace = {}
for key, val in namespace.items():
if key.startswith('__'):
upper_namespace[key] = val
else:
upper_namespace[key.upper()] = val
return super().__new__(mcs, name, bases, upper_namespace)
class Config(metaclass=UpperCaseAttrMeta):
debug = True
max_retries = 3
print(Config.DEBUG) # True
print(Config.MAX_RETRIES) # 3
Auto-registering subclasses
class PluginMeta(type):
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if bases: # skip the base class itself
plugin_name = namespace.get('name', name.lower())
mcs.registry[plugin_name] = cls
print(f'registered plugin: {plugin_name!r}')
return cls
class Plugin(metaclass=PluginMeta):
pass # base class - not registered
class CSVPlugin(Plugin):
name = 'csv'
def process(self, data): pass
class JSONPlugin(Plugin):
name = 'json'
def process(self, data): pass
print(PluginMeta.registry)
# {'csv': , 'json': }
# Lookup by name
plugin_cls = PluginMeta.registry['csv']
plugin = plugin_cls()
plugin.process([])
Singleton via metaclass
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]
class Database(metaclass=SingletonMeta):
def __init__(self, host):
self.host = host
db1 = Database('localhost')
db2 = Database('remotehost') # ignored - returns existing instance
print(db1 is db2) # True
print(db1.host) # localhost
__init_subclass__
Added in Python 3.6, __init_subclass__ lets a parent class hook into subclass creation without a custom metaclass - it is simpler and covers most real-world use cases:
class Base:
subclasses = []
def __init_subclass__(cls, required_method=None, **kwargs):
super().__init_subclass__(**kwargs)
Base.subclasses.append(cls)
# Enforce contract: subclasses must define 'process'
if not hasattr(cls, 'process'):
raise TypeError(f'{cls.__name__} must implement process()')
# Accept keyword arguments from class line
if required_method and not hasattr(cls, required_method):
raise TypeError(f'{cls.__name__} must implement {required_method}()')
class ValidChild(Base):
def process(self): return 'ok'
# This would raise TypeError:
# class BadChild(Base):
# pass # missing process()
print(Base.subclasses) # []
Keyword arguments on the class line are passed to __init_subclass__:
class Shape:
def __init_subclass__(cls, color='black', **kwargs):
super().__init_subclass__(**kwargs)
cls.default_color = color
print(f'New shape: {cls.__name__}, color={color}')
class Circle(Shape, color='red'):
pass
class Square(Shape, color='blue'):
pass
class Triangle(Shape): # uses default color='black'
pass
print(Circle.default_color) # red
print(Triangle.default_color) # black
For the common cases of registering subclasses, validating subclass structure, or adding default attributes, __init_subclass__ is simpler and avoids metaclass conflict errors (which arise when two metaclasses in the hierarchy are not related by inheritance). Use a metaclass only when you need to modify the class namespace before it is created, or when you need to intercept instance creation via __call__.
Class Decorators
Class decorators apply to the fully-constructed class and return a modified or replaced class. They are simpler than metaclasses for post-creation modifications:
def add_repr(cls):
def __repr__(self):
attrs = ', '.join(f'{k}={v!r}' for k, v in self.__dict__.items())
return f'{cls.__name__}({attrs})'
cls.__repr__ = __repr__
return cls
def validate_types(**types):
def decorator(cls):
original_init = cls.__init__
def new_init(self, **kwargs):
for name, expected_type in types.items():
if name in kwargs and not isinstance(kwargs[name], expected_type):
raise TypeError(f'{name} must be {expected_type.__name__}')
original_init(self, **kwargs)
cls.__init__ = new_init
return cls
return decorator
@add_repr
@validate_types(name=str, age=int)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person(name='Alice', age=30)
print(p) # Person(name='Alice', age=30)
Real-World Use Cases
Metaclasses appear in well-known Python frameworks. Understanding them helps you use these tools effectively:
| Framework/Library | What the Metaclass Does |
|---|---|
| Django ORM | Scans class attributes for Field instances, builds SQL schema, creates query managers on Model subclasses |
| SQLAlchemy | Maps Python class attributes to database columns, manages ORM session lifecycle |
| ABCMeta | Tracks abstract methods, prevents instantiation of classes that don't implement all abstract methods |
| Enum | Converts class body assignments to enum members, enforces uniqueness, provides __members__ dict |
| dataclasses | Uses a class decorator (not metaclass) - generates __init__, __repr__, __eq__ from field annotations |
A minimal ORM-style field mapping using a metaclass:
class Field:
def __init__(self, col_type):
self.col_type = col_type
self.name = None
def __set_name__(self, owner, name):
self.name = name
class ModelMeta(type):
def __new__(mcs, name, bases, namespace):
fields = {k: v for k, v in namespace.items() if isinstance(v, Field)}
namespace['_fields'] = fields
cls = super().__new__(mcs, name, bases, namespace)
return cls
def create_table_sql(cls):
cols = ', '.join(f'{name} {f.col_type}' for name, f in cls._fields.items())
return f'CREATE TABLE {cls.__name__.lower()} ({cols})'
class Model(metaclass=ModelMeta):
pass
class User(Model):
id = Field('INTEGER PRIMARY KEY')
name = Field('TEXT NOT NULL')
email = Field('TEXT UNIQUE')
print(User.create_table_sql())
# CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)
If a class inherits from two classes with different metaclasses, Python raises TypeError: metaclass conflict. The fix is to create a new metaclass that inherits from both conflicting metaclasses: class CombinedMeta(MetaA, MetaB): pass. This is another reason to prefer __init_subclass__ and class decorators - they do not introduce metaclass conflicts.