Skip to main content

Python __init_subclass__ Practice Problems & Exercises

Practice: __init_subclass__

11 problems3 Easy4 Medium4 Hard65–85 min
← Back to lesson

Easy

#1Basic Subclass RegistrationEasy
__init_subclass__registrationclass-hook

Use __init_subclass__ to automatically register every subclass of Animal in a class-level list.

Python
class Animal:
    _registry = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Animal._registry.append(cls.__name__)


class Dog(Animal):
    pass

class Cat(Animal):
    pass

class Parrot(Animal):
    pass

print(f"Registered subclasses: {Animal._registry}")
Expected Output
Registered subclasses: ['Dog', 'Cat', 'Parrot']
Hints

Hint 1: __init_subclass__ is a classmethod called automatically on the parent when a new subclass is defined.

Hint 2: Maintain a list on the base class (e.g., cls._registry). Append the subclass name or the subclass itself in __init_subclass__.


#2Keyword Argument in Subclass DefinitionEasy
__init_subclass__keyword-argumentclass-parameter

Build a Shape base class that requires every subclass to declare a shape_type keyword ('2d' or '3d'). Store it as a class attribute.

Python
class Shape:
    def __init_subclass__(cls, shape_type=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if shape_type not in ("2d", "3d"):
            raise TypeError(
                "Shape subclass must declare shape_type='2d' or '3d'"
            )
        cls.shape_type = shape_type


class Circle(Shape, shape_type="2d"):
    pass

class Sphere(Shape, shape_type="3d"):
    pass

print(f"Circle shape_type: {Circle.shape_type}")
print(f"Sphere shape_type: {Sphere.shape_type}")

try:
    class Blob(Shape):
        pass
except TypeError as e:
    print(f"TypeError: {e}")
Expected Output
Circle shape_type: 2d
Sphere shape_type: 3d
TypeError: Shape subclass must declare shape_type='2d' or '3d'
Hints

Hint 1: __init_subclass__ receives keyword arguments passed in the class definition: class Foo(Base, key=val).

Hint 2: Validate the value inside __init_subclass__ and raise TypeError if it is missing or invalid.


#3Auto-Add Abstract Method CheckEasy
__init_subclass__abstract-methodenforcement

Write a Task base class that uses __init_subclass__ to verify that concrete subclasses implement an execute method. Raise TypeError if they do not.

Python
class Task:
    def execute(self):
        raise NotImplementedError

    def __init_subclass__(cls, abstract=False, **kwargs):
        super().__init_subclass__(**kwargs)
        if abstract:
            return
        # Check that cls overrides execute (not still the base version)
        if cls.execute is Task.execute:
            raise TypeError(f"{cls.__name__} must implement 'execute'")


class ConcreteWorker(Task):
    def execute(self):
        return "working"

print("ConcreteWorker created OK")

try:
    class ConcreteWorker2(Task):
        pass
except TypeError as e:
    print(f"TypeError: {e}")
Expected Output
ConcreteWorker created OK
TypeError: ConcreteWorker2 must implement 'execute'
Hints

Hint 1: In __init_subclass__, check whether the subclass has overridden the required method. Use hasattr(cls, name) and getattr(cls, name) is not getattr(base, name).

Hint 2: Only enforce on non-abstract subclasses — skip classes that still define the method as NotImplemented or that end their name with 'Abstract'.


Medium

#4Plugin System with __init_subclass__Medium
__init_subclass__pluginregistryfactory

Build a parser plugin system where each Parser subclass registers itself by name. A factory method Parser.get_parser(name) returns the right subclass instance.

Python
class Parser:
    _parsers = {}

    def __init_subclass__(cls, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if name:
            Parser._parsers[name] = cls

    @classmethod
    def get_parser(cls, name):
        if name not in cls._parsers:
            raise KeyError(f"No parser registered for '{name}'")
        return cls._parsers[name]()

    def parse(self, data):
        raise NotImplementedError


class JSONParser(Parser, name="json"):
    def parse(self, data):
        import json
        return json.loads(data)


class CSVParser(Parser, name="csv"):
    def parse(self, data):
        import csv, io
        reader = csv.reader(io.StringIO(data))
        return list(reader)


class XMLParser(Parser, name="xml"):
    def parse(self, data):
        return f"<parsed>{data}</parsed>"


print(f"Available parsers: {list(Parser._parsers.keys())}")

jp = Parser.get_parser("json")
print(f'Parsed with json: {jp.parse(\'{"key": "value"}\') }')

cp = Parser.get_parser("csv")
print(f"Parsed with csv: {cp.parse('a,b\\n1,2')}")
Expected Output
Available parsers: ['json', 'csv', 'xml']
Parsed with json: {'key': 'value'}
Parsed with csv: [['a', 'b'], ['1', '2']]
Hints

Hint 1: Accept a name keyword in __init_subclass__ and store the subclass in a dict keyed by name.

Hint 2: Add a classmethod get_parser(name) on the base class that looks up the registry and instantiates the right subclass.


#5Inherit and Override Class-Level ConfigMedium
__init_subclass__configurationinheritanceclass-attribute

Create a Service base class where subclasses declare a timeout keyword in their class definition. The value should propagate to sub-subclasses through normal inheritance unless explicitly overridden.

Python
class Service:
    timeout = 30  # default

    def __init_subclass__(cls, timeout=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if timeout is not None:
            cls.timeout = timeout
        # If not explicitly set, cls.timeout inherits from parent via MRO


class FastService(Service, timeout=5):
    pass

class SlowService(Service, timeout=120):
    pass

class CachedFastService(FastService):
    # Inherits timeout=5 from FastService
    pass


print(f"Base timeout: {Service.timeout}")
print(f"FastService timeout: {FastService.timeout}")
print(f"SlowService timeout: {SlowService.timeout}")
print(f"CachedFastService timeout: {CachedFastService.timeout}")
Expected Output
Base timeout: 30
FastService timeout: 5
SlowService timeout: 120
CachedFastService timeout: 5
Hints

Hint 1: __init_subclass__ can accept keyword arguments that set class attributes. These attributes are then inherited by further subclasses automatically.

Hint 2: Check cls.__dict__ (not just hasattr) when you only want the value explicitly set on that specific class — not inherited.


#6Enforce Docstring on Every SubclassMedium
__init_subclass__docstringquality-enforcement

Write a Documented mixin that uses __init_subclass__ to enforce that every subclass has a non-empty class-level docstring.

Python
class Documented:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        doc = cls.__dict__.get("__doc__")
        if not doc or not doc.strip():
            raise TypeError(
                f"Class '{cls.__name__}' must have a class-level docstring"
            )


class WellDocumented(Documented):
    """This class is well documented."""
    pass

print("WellDocumented: OK")

try:
    class LazyClass(Documented):
        pass
except TypeError as e:
    print(f"TypeError: {e}")
Expected Output
WellDocumented: OK
TypeError: Class 'LazyClass' must have a class-level docstring
Hints

Hint 1: Access cls.__doc__ in __init_subclass__. It is None if no docstring was written.

Hint 2: Only enforce on leaf classes — skip if the class is itself a known abstract base.


#7Auto-Generate CLI Command RegistryMedium
__init_subclass__CLIcommand-patternregistry

Build a CLI command framework where each command class registers itself via __init_subclass__. A dispatcher runs the right command by name.

Python
class Command:
    _commands = {}

    def __init_subclass__(cls, command=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if command:
            Command._commands[command] = cls

    @classmethod
    def run_command(cls, name, *args):
        if name not in cls._commands:
            print(f"Unknown command: {name}")
            return
        cmd = cls._commands[name]()
        cmd.execute(*args)

    def execute(self, *args):
        raise NotImplementedError


class DeployCommand(Command, command="deploy"):
    def execute(self, *args):
        print("deploy: Deploying to production...")


class RollbackCommand(Command, command="rollback"):
    def execute(self, *args):
        print("rollback: Rolling back last deployment...")


class StatusCommand(Command, command="status"):
    def execute(self, *args):
        print("status: All systems operational")


print(f"Available commands: {list(Command._commands.keys())}")
Command.run_command("deploy")
Command.run_command("rollback")
Command.run_command("restart")
Expected Output
Available commands: ['deploy', 'rollback', 'status']
deploy: Deploying to production...
rollback: Rolling back last deployment...
Unknown command: restart
Hints

Hint 1: Each subclass declares a command keyword. Store {command_name: cls} in a dict on the base class.

Hint 2: A classmethod run_command(name, *args) looks up the registry and calls .execute() on a fresh instance.


Hard

#8Versioned Model RegistryHard
__init_subclass__versioningregistryfactory

Design a Model base class where each schema version registers itself. A factory retrieves the right version and a latest() method returns the most recent one.

Python
class Model:
    _versions = {}
    _latest_version = 0

    def __init_subclass__(cls, version=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if version is not None:
            Model._versions[version] = cls
            if version > Model._latest_version:
                Model._latest_version = version

    @classmethod
    def get_version(cls, version):
        if version not in cls._versions:
            raise KeyError(f"No model registered for version {version}")
        return cls._versions[version]

    @classmethod
    def latest(cls):
        return cls._versions[cls._latest_version]

    def serialize(self):
        raise NotImplementedError


class UserV1(Model, version=1):
    def __init__(self, id, name):
        self.id = id
        self.name = name

    def serialize(self):
        return {"id": self.id, "n": self.name}


class UserV2(Model, version=2):
    def __init__(self, id, name):
        self.id = id
        self.name = name

    def serialize(self):
        return {"id": self.id, "name": self.name, "_v": 2}


v1_cls = Model.get_version(1)
v2_cls = Model.get_version(2)

u1 = v1_cls(1, "Alice")
u2 = v2_cls(1, "Alice")

print(f"v1 serialize: {u1.serialize()}")
print(f"v2 serialize: {u2.serialize()}")
print(f"Latest version: {Model._latest_version}")
Expected Output
v1 serialize: {'id': 1, 'n': 'Alice'}
v2 serialize: {'id': 1, 'name': 'Alice', '_v': 2}
Latest version: 2
Hints

Hint 1: Accept a version keyword in __init_subclass__. Store subclasses in a dict keyed by version number.

Hint 2: Track the maximum version number seen so far on the base class. A classmethod get_version(n) returns the matching subclass or raises KeyError.


#9Cooperative __init_subclass__ Mixin ChainHard
__init_subclass__cooperativemixinMRO

Build two mixins (LoggedMixin, ThrottledMixin) that each use __init_subclass__ to configure themselves. They must cooperate via super() so a class inheriting from both works correctly.

Python
class LoggedMixin:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.logged = True
        print(f"LoggedMixin applied to {cls.__name__}")


class ThrottledMixin:
    def __init_subclass__(cls, rate_limit=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if rate_limit is not None:
            cls.rate_limit = rate_limit
            print(f"ThrottledMixin applied to {cls.__name__}: rate_limit={rate_limit}")


class Base(LoggedMixin, ThrottledMixin):
    pass


class Service(Base, rate_limit=100):
    pass

print("FinalService OK")
print(f"rate_limit: {Service.rate_limit}")
print(f"logged: {Service.logged}")
Expected Output
LoggedMixin applied to Service
ThrottledMixin applied to Service: rate_limit=100
FinalService OK
rate_limit: 100
logged: True
Hints

Hint 1: Each mixin defines __init_subclass__ and must call super().__init_subclass__(**kwargs) to pass remaining kwargs up the chain.

Hint 2: Mixins can pop their own kwargs from kwargs before forwarding to super(), so each mixin handles exactly its own keyword.


#10Inheritance Depth LimiterHard
__init_subclass__inheritancedepthMRO

Write a DepthLimited base class that raises TypeError if any subclass is created more than max_depth levels deep in the hierarchy.

Python
class DepthLimited:
    _max_depth = 2

    def __init_subclass__(cls, max_depth=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if max_depth is not None:
            cls._max_depth = max_depth

        # Measure depth: count classes in MRO that are subclasses of DepthLimited,
        # excluding DepthLimited itself and object
        depth = sum(
            1 for c in cls.__mro__
            if c not in (cls, DepthLimited, object)
            and issubclass(c, DepthLimited)
        )
        # depth = number of intermediate ancestors
        actual_depth = len([
            c for c in cls.__mro__
            if c is not object and c is not DepthLimited and issubclass(c, DepthLimited)
        ])

        if actual_depth > cls._max_depth:
            raise TypeError(
                f"{cls.__name__} inheritance chain exceeds max depth of "
                f"{cls._max_depth} (current depth: {actual_depth})"
            )


class Level1(DepthLimited):
    pass

print(f"Level1 OK (depth {len([c for c in Level1.__mro__ if c is not object and c is not DepthLimited and issubclass(c, DepthLimited)])})")


class Level2(Level1):
    pass

print(f"Level2 OK (depth {len([c for c in Level2.__mro__ if c is not object and c is not DepthLimited and issubclass(c, DepthLimited)])})")


try:
    class Level3(Level2):
        pass
except TypeError as e:
    print(f"TypeError: {e}")
Expected Output
Level1 OK (depth 1)
Level2 OK (depth 2)
TypeError: MaxDepth2 inheritance chain exceeds max depth of 2 (current depth: 3)
Hints

Hint 1: Walk cls.__mro__ to determine how many levels deep from the root the current class is.

Hint 2: Exclude object and the root class itself from the count. Raise TypeError if the depth exceeds the configured maximum.


#11Event Bus with Auto-Subscription via __init_subclass__Hard
__init_subclass__event-bussubscriptionhandler

Design an event bus where handler classes register themselves for specific events via __init_subclass__. A dispatch method routes events to all registered handlers.

Python
class EventBus:
    _handlers = {}  # event_name -> [HandlerClass, ...]

    def __init_subclass__(cls, events=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if events:
            if isinstance(events, str):
                events = [events]
            for event in events:
                EventBus._handlers.setdefault(event, []).append(cls)

    @classmethod
    def dispatch(cls, event_name, **data):
        handlers = cls._handlers.get(event_name, [])
        for handler_cls in handlers:
            handler_cls().handle(event_name, **data)

    def handle(self, event_name, **data):
        raise NotImplementedError


class OrderAuditHandler(EventBus, events="OrderCreated"):
    def handle(self, event_name, **data):
        print(f"OrderCreated -> OrderAuditHandler: order_id={data.get('order_id')}")


class OrderEmailHandler(EventBus, events="OrderCreated"):
    def handle(self, event_name, **data):
        print(f"OrderCreated -> OrderEmailHandler: order_id={data.get('order_id')}")


class PaymentAuditHandler(EventBus, events="PaymentReceived"):
    def handle(self, event_name, **data):
        print(f"PaymentReceived -> PaymentAuditHandler: amount={data.get('amount')}")


EventBus.dispatch("OrderCreated", order_id=42)
EventBus.dispatch("PaymentReceived", amount=99.99)
Expected Output
OrderCreated -> OrderAuditHandler: order_id=42
OrderCreated -> OrderEmailHandler: order_id=42
PaymentReceived -> PaymentAuditHandler: amount=99.99
Hints

Hint 1: Accept an events keyword (a list or tuple of event names) in __init_subclass__. Register cls against each event name in a dict.

Hint 2: The dispatch classmethod iterates all handlers registered for the event name and calls .handle(event_data) on a fresh instance of each.

© 2026 EngineersOfAI. All rights reserved.