🎓 Advanced · Topic 05
Metaclasses & Descriptors
Go behind the scenes of Python's object model. Control how classes are created with metaclasses and how attribute access works with descriptors.
⏱ ~60 min
🔴 Advanced
🔬 Object model
type — The Metaclass of All Classes
In Python, classes are objects too. type is the class that creates classes. You can use it dynamically to build classes at runtime.
type_basics.py
print(type(42)) # <class 'int'>
print(type(int)) # <class 'type'>
print(type(type)) # <class 'type'>
# Dynamically create a class with type(name, bases, dict)
Dog = type("Dog", (), {
"sound": "Woof",
"bark": lambda self: print(self.sound),
})
rex = Dog()
rex.bark() # Woof
Custom Metaclasses
Override __new__ or __init__ on a metaclass to intercept and modify class creation — perfect for ORMs, API frameworks, and plugin systems.
metaclass_example.py
class Singleton(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 Config(metaclass=Singleton):
def __init__(self):
self.debug = False
a = Config()
b = Config()
print(a is b) # True — same instance
⚠️ Use metaclasses sparingly Tim Peters: "If you wonder whether you need a metaclass, you don't." Prefer class decorators or __init_subclass__ for most use cases.
Descriptors
A descriptor is an object that defines __get__, __set__, or __delete__. This is how @property, classmethod, and Django/SQLAlchemy fields work under the hood.
descriptor.py
class Validated:
"""Descriptor that enforces a minimum value."""
def __init__(self, minimum):
self.minimum = minimum
self.attr = None
def __set_name__(self, owner, name):
self.attr = f"_{name}"
def __get__(self, obj, objtype=None):
return getattr(obj, self.attr, None)
def __set__(self, obj, value):
if value < self.minimum:
raise ValueError(f"Must be >= {self.minimum}")
setattr(obj, self.attr, value)
class Product:
price = Validated(minimum=0)
stock = Validated(minimum=0)
p = Product()
p.price = 9.99 # ok
p.price = -1 # ValueError: Must be >= 0
__init_subclass__ — Lightweight Hook
A simpler alternative to metaclasses for hooking into subclass creation.
init_subclass.py
class Plugin:
_registry = {}
def __init_subclass__(cls, name, **kwargs):
super().__init_subclass__(**kwargs)
cls._registry[name] = cls
class JSONPlugin(Plugin, name="json"): pass
class CSVPlugin(Plugin, name="csv"): pass
print(Plugin._registry)
# {'json': JSONPlugin, 'csv': CSVPlugin}