Python: How to Automatically Register Subclasses
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):
# ✅ Correct: Call super() to handle multiple inheritance correctly
super().__init_subclass__(**kwargs)
# Register the new subclass
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'>]
Always call super().__init_subclass__(**kwargs) to ensure that if your class is part of a complex inheritance hierarchy, other classes in the chain also get initialized correctly.
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.
# Define the metaclass to control the Base class's behavior
class BaseMeta(type):
# This allows: for cls in Base:
def __iter__(cls):
return iter(cls.subclasses)
Standard methods like __iter__ defined inside a class (e.g., class Base) control the behavior of instances of that class. To control the behavior of the class itself, the method must be defined in the metaclass.
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:
# 1. Define the Metaclass
class BaseMeta(type):
"""Metaclass to make the Base class iterable over its subclasses."""
def __iter__(cls):
return iter(cls.subclasses)
# 2. Define the Base Class
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)
# 3. Testing the Implementation
if __name__ == "__main__":
# Define subclasses (plugins)
class Txzy(Base):
pass
class SomeComplexCase(Base):
pass
# ✅ Verify Registration
print("Verifying registry...")
if Txzy in list(Base):
print(" - Txzy is registered.")
if SomeComplexCase in list(Base):
print(" - SomeComplexCase is registered.")
# ✅ Verify Iteration
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: SomeComplexCase
Conclusion
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.