Skip to content

Python: How to Automatically Register Subclasses

Learn how to use __init_subclass__ and metaclasses in Python to automatically track and iterate over subclasses for plugin systems, factory patterns, or database models.

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: 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.

Navigation