Metaclasses
Metaclasses are classes whose instances are classes. They control class creation, allowing you to modify class behavior at definition time.
What is a Metaclass?
Everything in Python is an object, including classes. A metaclass defines how classes are created:
# type is the default metaclass
print(type("MyClass", (), {})) # Creates a class
print(type(type)) # type is its own metaclass
class MyMeta(type):
pass
class MyClass(metaclass=MyMeta):
pass
print(type(MyClass)) # MyMeta
print(MyClass.__class__) # MyMeta
Class Creation Hooks
Metaclasses intercept class definition with __new__ and __init__:
class EntityMeta(type):
def __new__(mcs, name, bases, namespace):
# mcs - the metaclass (self for the metaclass)
# name - class name
# bases - tuple of base classes
# namespace - class attributes/methods dict
# Can modify namespace before class creation
if 'validate' not in namespace:
namespace['_is_valid'] = True
cls = super().__new__(mcs, name, bases, namespace)
return cls
class Entity(metaclass=EntityMeta):
def __init__(self, data):
self.data = data
e = Entity("test")
print(e._is_valid) # True - added by metaclass
__init_subclass__
Simpler than full metaclasses - called when a class is subclassed:
class Registry:
_registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if cls.__name__ not in cls._registry:
cls._registry[cls.__name__] = cls
class PluginA(Registry):
pass
class PluginB(Registry):
pass
print(Registry._registry) # {'PluginA': ..., 'PluginB': ...}
Tracking Class Attributes
Automatically track attributes across all instances:
class AutoAttrMeta(type):
def __new__(mcs, name, bases, namespace):
# Track all attribute names
attrs = []
for key, value in namespace.items():
if not key.startswith('_') and callable(value):
attrs.append(key)
# Add _attributes to class
namespace['_attributes'] = attrs
return super().__new__(mcs, name, bases, namespace)
class Model(metaclass=AutoAttrMeta):
def save(self): pass
def delete(self): pass
def update(self): pass
print(Model._attributes) # ['save', 'delete', 'update']
Singleton Pattern
Use metaclass for singletons:
class SingletonMeta(type):
_instances = {}
def __call__(cls, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(**kwargs)
return cls._instances[cls]
class Config(metaclass=SingletonMeta):
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
config1 = Config(host="localhost", port=8080)
config2 = Config(host="other")
print(config1.host) # localhost
print(config2.host) # localhost - same instance!
print(config1 is config2) # True
Abstract Base Classes with Metaclass
Create custom abstract class systems:
class ABCMeta(type):
_abstract_methods = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Collect abstract methods from bases
abstract_methods = set()
for base in bases:
if hasattr(base, '_abstract_methods'):
abstract_methods.update(base._abstract_methods)
# Check this class for abstract methods
for attr in namespace.get('_abstract_', []):
abstract_methods.add(attr)
cls._abstract_methods = abstract_methods
return cls
class Base(metaclass=ABCMeta):
_abstract_ = ['process']
def run(self):
return self.process()
class Concrete(Base):
def process(self):
return "done"
c = Concrete()
print(c.run()) # done
When to Use Metaclasses
| Use Case | Recommended Approach |
|---|---|
| Register classes | __init_subclass__ |
| Modify class at creation | Metaclass |
| Singleton | Metaclass or closure |
| ORM models | Metaclass |
| Plugin system | __init_subclass__ |
Tip: Prefer
__init_subclass__ when possible. It's simpler and often sufficient.