In many advanced Python patterns (such as plugin systems, database model registries, or factory patterns) you often need to keep track of every class that inherits from a specific base class. Manually adding these to a list is error-prone.
This guide demonstrates how to use __init_subclass__ and metaclasses to automatically register subclasses upon definition and make the base class itself iterable.
The Mechanism: __init_subclass__
Prior to Python 3.6, developers often relied on complex metaclasses or decorators to track subclasses. Python 3.6 introduced __init_subclass__, a built-in hook that is called automatically whenever a class is derived.
By overriding this method in a base class, we can intercept the creation of subclasses and add them to a registry.
class Base:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Base.subclasses.append(cls)
class PluginA(Base):
pass
class PluginB(Base):
pass
print(f"Registered plugins: {Base.subclasses}")Output:
Registered plugins: [<class '__main__.PluginA'>, <class '__main__.PluginB'>]Making the Class Iterable with Metaclasses
While Base.subclasses works, a more pythonic API might allow you to iterate directly over the Base class (e.g., for plugin in Base:). To achieve this, we need to modify how the class object itself behaves.
Since classes are instances of metaclasses, we define a custom metaclass with an __iter__ method.
class BaseMeta(type):
def __iter__(cls):
return iter(cls.subclasses)Complete Implementation
Combining the automatic registration hook and the iterable metaclass provides a robust plugin/model system.
Here is the complete self_register.py implementation:
class BaseMeta(type):
"""Metaclass to make the Base class iterable over its subclasses."""
def __iter__(cls):
return iter(cls.subclasses)
class Base(metaclass=BaseMeta):
"""
Base class that automatically registers all classes that inherit from it.
"""
subclasses = []
def __init_subclass__(cls, **kwargs):
"""Hook called when a class subclasses 'Base'."""
super().__init_subclass__(**kwargs)
Base.subclasses.append(cls)
if __name__ == "__main__":
class Txzy(Base):
pass
class SomeComplexCase(Base):
pass
print("Verifying registry...")
if Txzy in list(Base):
print(" - Txzy is registered.")
if SomeComplexCase in list(Base):
print(" - SomeComplexCase is registered.")
print("\nIterating directly over Base class:")
for cls in Base:
print(f" - Found subclass: {cls.__name__}")Output:
Verifying registry...
- Txzy is registered.
- SomeComplexCase is registered.
Iterating directly over Base class:
- Found subclass: Txzy
- Found subclass: SomeComplexCaseConclusion
Automatic subclass registration allows you to decouple your architecture. By using __init_subclass__, you avoid the need for manual registration functions or decorators. By adding a simple Metaclass, you can provide a clean, intuitive API for accessing these registered classes.