Back to Blog
Python

Python Subclass Hook: __init_subclass__ in Practice

python subclass hook: Learn how Python's __init_subclass__ hook works, when it fires, and how to use it for validation, registration, and attribute injection in your c...

__init_subclass____subclasshook__metaclassesabstract base classesclass registration
Illustration of a Python class hierarchy where a parent class hook intercepts the creation of a subclass.

The __init_subclass__ method is the python subclass hook that fires when a class inherits from a class defining it. Python calls the hook automatically with the newly created subclass as its first argument, giving the parent class a chance to validate, register, or annotate the subclass before any instance exists. Because the hook runs at class definition time, problems surface during import rather than later at runtime.

What the Subclass Hook Fires On

__init_subclass__ is called once per subclass definition, not per instance. The hook receives the new class object as cls. Here is the minimal form:

class Base: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) print(f"Subclass created: {cls.__name__}") class Child(Base): pass

When Child is defined, Python calls Base.__init_subclass__(Child). The print statement runs immediately, and Child is a fully formed class at that point. The hook is not called for Base itself, only for classes that inherit from it.

The super().__init_subclass__(**kwargs) call matters in multiple inheritance. If Base participates in a cooperative MRO, that call lets other classes in the hierarchy also receive the hook. Skipping it silently breaks that chain.

Passing Arguments Through the Class Definition

Keyword arguments in a class definition are forwarded to __init_subclass__:

class Base: def __init_subclass__(cls, required: bool = False, **kwargs): super().__init_subclass__(**kwargs) cls.required = required class Child(Base, required=True): pass print(Child.required) # True

required=True never becomes a class attribute by itself; it is consumed by the hook. The hook decides what to do with it. In this example, cls.required is set on Child. If a keyword argument is not declared by the hook, it must be absorbed by **kwargs and forwarded to super().__init_subclass__, or Python raises a TypeError.

This is useful when a base class wants to accept configuration without exposing it as a normal class attribute.

Registering Subclasses Automatically

A common use is automatic registration:

class Plugin: registry = {} def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) Plugin.registry[cls.__name__] = cls class AudioPlugin(Plugin): pass class VideoPlugin(Plugin): pass print(Plugin.registry) # {'AudioPlugin': <class '__main__.AudioPlugin'>, 'VideoPlugin': <class '__main__.VideoPlugin'>}

Each subclass is added to the registry the moment it is defined. No decorator or manual registration step is needed. This pattern fits plugin systems, serialization formats, and command dispatchers where the set of classes is known at import time.

One operational caveat: registration happens when the module defining the subclass is imported. If a module that looks up the registry is imported before the module that defines the subclasses, the lookup finds nothing. Import order becomes part of the contract.

Validating Subclass Structure

The hook can enforce structural requirements at class definition time:

class Shape: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) if not callable(getattr(cls, "area", None)): raise TypeError(f"{cls.__name__} must define an area() method") class Circle(Shape): def area(self): return 3.14159 * self.radius ** 2 class Broken(Shape): # raises TypeError at class definition pass

The error is raised when Broken is defined, not when an instance is created. That moves a whole class of mistakes from runtime to import time, which is usually easier to debug. The check uses getattr with a default so that inherited methods count as valid.

The Other Subclass Hook: subclasshook

__subclasshook__ is a different mechanism, despite the similar name. It belongs to abstract base classes and customizes the behavior of issubclass():

from abc import ABC class Readable(ABC): @classmethod def __subclasshook__(cls, subclass): return hasattr(subclass, "read") class FileReader: def read(self): return "data" print(issubclass(FileReader, Readable)) # True

FileReader does not inherit from Readable, but issubclass() returns True because the hook checks for the read method. This is structural subtyping: a class is considered a subclass if it satisfies the interface, regardless of inheritance.

The two hooks serve different purposes. __init_subclass__ reacts to real inheritance. __subclasshook__ decides what counts as a subclass without inheritance. They can coexist in the same class, but they do not interact.

Performance and Runtime Cost

The hook runs once per class definition, not per instance. For a typical hierarchy with a handful of subclasses, the cost is negligible. The work happens at import time, so the main cost is added to module import duration.

If the hook performs expensive operations, such as filesystem access, network calls, or database queries, and the module defines many subclasses, startup time increases accordingly. Keep the hook synchronous and cheap. If registration requires I/O, consider deferring it until the registry is actually used.

There is no per-instance overhead from __init_subclass__. Instances of the subclass are created normally.

Common Mistakes and Edge Cases

Forgetting to call super().__init_subclass__(**kwargs) is the most common error. In a single-inheritance hierarchy it may not matter, but in a cooperative multiple-inheritance chain it silently prevents other classes from receiving the hook.

The hook is inherited. If Grandchild inherits from Child, and Child inherits from Base, then Grandchild also triggers Base.__init_subclass__. The hook fires for every class in the hierarchy below the defining class, not just direct children.

The hook does not fire for the class that defines it. Base itself is not passed to its own hook.

__init_subclass__ is called after the class body has executed and after the class object has been created. It cannot modify the class namespace before the class is built. For that level of control, a metaclass is required.

When to Prefer init_subclass Over a Metaclass

__init_subclass__ covers most subclass-creation needs with less machinery. A metaclass must be declared with metaclass= on every class or through a shared base, and it intercepts the entire class creation process.

Concern__init_subclass__Metaclass
Fires onSubclass creationClass creation
SetupOne method on the parentmetaclass= on classes or a base
Can modify namespace before class buildNoYes
Typical useValidation, registration, attribute injectionNamespace rewriting, class object customization

Use __init_subclass__ when the goal is to react to a subclass after it exists: validate it, register it, or set attributes on it. Use a metaclass when the class object itself must be built differently, such as when you need to alter the class namespace before the type is constructed.

A direct __init_subclass__ implementation is usually the right choice for a codebase where the parent class is under your control and the subclasses are ordinary classes. It keeps the hook close to the base class and avoids the conceptual overhead of a metaclass.

python subclass hook: Practical Usage and Code Examples | RYUSLOG DEV