How do keyword arguments in `class C(Base, tag='x')` reach `__init_subclass__`?
answer
- Configuration written in the class header
- Everything but metaclass is forwarded
- The hook signature declares what it accepts
- Leftovers reach object and raise TypeError
- No default means every subclass must supply it
basics
~20 sEvery keyword in a class header except metaclass is forwarded to the parent's init_subclass as a keyword argument, so a hook declared as def init_subclass(cls, /, tag=None, **kwargs) receives tag='x'. Consume what you own before calling super().
solid answer
~40 sA class statement accepts keyword arguments after the bases, and PEP 487 wired them straight through to the class-creation hook. `metaclass` is special-cased and consumed by the class machinery; everything else is handed to `__init_subclass__` along with the new class, so `class C(Base, tag='x')` calls the hook with `tag='x'`. The hook signature declares the keywords it understands and forwards the rest: `def __init_subclass__(cls, /, tag=None, **kwargs)` then `super().__init_subclass__(**kwargs)`. Anything left over eventually reaches `object.__init_subclass__`, which accepts no keyword arguments and raises `TypeError` at class-definition time — so a misspelled keyword in a class header fails loudly at import instead of being ignored. Declaring a keyword without a default makes it mandatory: subclasses that omit it fail to be created at all.
code
python · 11 linesclass Base:
def __init_subclass__(cls, /, tag=None, **kwargs):
super().__init_subclass__(**kwargs)
cls.tag = tag
class Child(Base, tag="tmx"):
pass
print(Child.tag)go deeper
Know that a class statement can carry keyword arguments after the base list, and that they configure how the class is created rather than how instances are built.
Explain the routing precisely: metaclass is consumed by the machinery, everything else lands in the parent's hook, defaults make a keyword optional, and leftovers raise TypeError at import.
Show when a header keyword beats a class attribute — creation-time configuration that must not be inherited by accident — and how you keep the cooperative chain intact across several bases that each want their own keywords.
Weigh a declarative class-header vocabulary against plain attributes and explicit registration for a library other teams extend: type-checker blindness, discoverability and the cost of import-time failures are the axes to argue on.
## The syntax Since Python 3.0 a class statement has allowed keyword arguments after the base list, and for a long time the only one anybody used was `metaclass=`: ```python class C(Base, tag="tmx"): ... ``` PEP 487 (Python 3.6) gave the rest of them a destination that does not require writing a metaclass. `metaclass` is consumed by the class machinery itself and is *not* forwarded. Every other keyword is passed along to class creation and, from there, to the parent's `__init_subclass__` together with the newly built class. ## Declaring what you accept The hook declares its keywords like any other function and forwards the remainder: ```python class Base: def __init_subclass__(cls, /, tag=None, **kwargs): super().__init_subclass__(**kwargs) cls.tag = tag ``` Three details in that signature earn their place. * **`/`** makes `cls` positional-only. Without it, a subclass writing `class C(Base, cls="x")` would collide with the first parameter and produce a confusing `TypeError`. * **A default** (`tag=None`) makes the keyword optional. Drop the default and the keyword becomes *mandatory*: any subclass that omits it fails at class-definition time with a missing-argument `TypeError`. That is a legitimate, and quite strong, way to force every subclass to declare something. * **`**kwargs` plus the `super()` call** keeps the chain cooperative. If two classes on the MRO both define the hook and both want different keywords, each pops its own and passes the rest up. ## Why unconsumed keywords are an error `object.__init_subclass__` is the end of the chain and it accepts no keyword arguments whatsoever. Forwarding one to it raises a `TypeError` complaining that `__init_subclass__` takes no keyword arguments, raised while the class statement is executing — that is, at import time, with a traceback pointing at the class header. This is a feature rather than an annoyance. Class headers are configuration written in the middle of code, and configuration typos are exactly the failure that silently misbehaves later. Making `class C(Base, tagg="tmx")` blow up at import turns a subtle behavioural bug into a startup error. If you want unknown keywords tolerated, you have to swallow them deliberately by accepting `**kwargs` and never forwarding them — which is worth doing only when you genuinely own an extensible header vocabulary. ## Where else the keywords go The same keywords are also visible to a custom metaclass, which receives them in its own creation hooks. That symmetry is what lets a library migrate from a metaclass to `__init_subclass__` without changing a single class header. The metaclass side of the story belongs to metaclasses proper; what matters here is that the keywords are not exclusive to either mechanism, and that `metaclass=` itself never reaches the hook. ## What the pattern buys you Keyword arguments in the header turn a class statement into a small declarative form: ```python class TmxHandler(Handler, key="tmx", version=2): ... ``` Everything the base needs to know about the subclass is stated in the header rather than in a magic class attribute the base has to go looking for, and the base can validate it immediately. Compared with the alternative of reading class attributes inside the hook (`getattr(cls, "key", None)`), header keywords have three advantages: they cannot be accidentally inherited from a parent, they are visibly *about* class creation rather than about instances, and a missing one is caught by ordinary parameter binding instead of hand-written checks. The counterweight is that header keywords are invisible to type checkers and to readers who do not know the base. A class attribute is self-describing and can be annotated; a header keyword needs the base's documentation. In practice, use keywords for things that configure *creation* — the registry key, an opt-out flag, a validation switch — and class attributes for values the instances themselves need. ## Common mistakes The mistake seen most often is treating the header keywords as constructor arguments: they have nothing to do with `__init__` and are never passed to instances. The second is forgetting `super().__init_subclass__(**kwargs)`, which quietly disables any cooperating hook higher up the MRO. The third is popping a keyword and then forwarding it anyway, which resurrects the `TypeError` you thought you had handled.
- Which class-header keyword is never forwarded to the hook?`metaclass`. The class machinery consumes it to decide which callable builds the class, so it never appears in the keywords handed to `__init_subclass__`. Every other keyword after the base list is forwarded verbatim, which is why a library can accept an arbitrary vocabulary of header options without a metaclass of its own.
- What happens if you forward a keyword you meant to consume?It travels up the MRO until it reaches `object.__init_subclass__`, which accepts no keyword arguments and raises `TypeError` while the class statement is still executing. The fix is to name the keyword in your own signature so ordinary parameter binding removes it, and forward only `**kwargs`.
- How would you make one header keyword mandatory for every subclass?Declare it in the hook signature without a default: `def __init_subclass__(cls, /, key, **kwargs)`. A subclass that omits it fails to be created, with a missing-argument `TypeError` pointing at the class statement. That is stronger than checking a class attribute, because it cannot be satisfied by accidental inheritance from a parent.
saying these in an interview costs you the question
- Thinks class-header keywords are passed to __init__
- Believes only metaclass= is allowed in a header
- Expects an unknown keyword to be ignored silently
- Forgets to consume a keyword before calling super()
- Assumes the keywords become class attributes automatically