Today I learned how to use the dunder method __init_subclass__ to be notified when a class is subclassed.

__init_subclass__

The dunder method __init_subclass__ is a class method that Python runs when a subclass of that class is instantiated. The snippet below sums it all:

class Parent:
    def __init_subclass__(cls):
        print(f"Subclass {cls} was created.")

class A(Parent):
    pass

class B(A):
    pass

"""
Output:
Subclass <class '__main__.A'> was created.
Subclass <class '__main__.B'> was created.
"""

The code above shows that when subclasses are created (even if they are not direct subclasses, like the case of B) the class method Parent.__init_subclass__ is called.

The class method __init_subclass__ will also receive the keyword arguments that you specify on class definition. The snippet below shows this:

class Parent:
    def __init_subclass__(cls, **kwargs):
        print(f"Subclass {cls.__name__} created with {kwargs}")

class A(Parent, kwarg1=73, kwarg2=True):
    pass

# Output: Subclass A created with {'kwarg1': 73, 'kwarg2': True}

Metaprogramming with __init_subclass__

The point of the dunder method __init_subclass__ is that a parent class can modify its child classes when they are being created, thus enabling metaprogramming. For example, I needed to use __init_subclass__ in Textual to make sure that all subclasses of a particular class, named Widget, had a name that starts with an upper case letter or with an underscore _.

You can check the Textual codebase for the full context, but this was essentially what I implemented:

class BadWidgetName(Exception):
    """Raised when widget names do not satisfy the required restrictions."""

class Widget:
    def __init_subclass__(cls):
        name = cls.__name__
        if not name[0].isupper() or not name.startswith("_"):
            raise BadWidgetName(
                f"Widget class {name!r} must start with an upper case letter or underscore '_'."
            )

class A(Widget):   # Ok
    pass

class _b(Widget):  # Ok
    pass

class c(Widget):   # raises BadWidgetName exception.
    pass

Become a better Python 🐍 developer πŸš€

+35 chapters. +400 pages. Hundreds of examples. Over 30,000 readers!

My book β€œPydon'ts” teaches you how to write elegant, expressive, and Pythonic code, to help you become a better developer. >>> Download it here πŸπŸš€.

References

Previous Post Next Post

Blog Comments powered by Disqus.