I stumbled upon this last night and felt a little confused as to why this is currently allowed:
def __init_subclass__(cls, test, /) -> None:
since it’s impossible to provide that argument when subclassing.
As for regular positional arguments, the exception currently says
TypeError: Test.__init_subclass__() missing 1 required positional argument: 'test', although when actually providing it, it is functionally a keyword only argument.
Would it make sense to give more informative errors/tracebacks for these issues?
I think the idiomatic use of
__init_subclass__ is to never give it positional-only arguments (apart from the first
cls argument). Seems to be a bit of a flaw in the stdlib implementation that the method does allow this kind of definition – perhaps a bit of a disconnect between the syntax and the semantics of that function (positional-only args were introduced in Python 3.8, while
__init_subclass__ was added in 3.6, so perhaps core devs never considered this usecase?).
One other thing I noticed is that the PEP states
As an example, the first use case looks as follows:
>>> class QuestBase:
... # this is implicitly a @classmethod (see below for motivation)
... def __init_subclass__(cls, swallow, **kwargs):
... cls.swallow = swallow
>>> class Quest(QuestBase, swallow="african"):
The base class
object contains an empty
__init_subclass__ method which serves as an endpoint for cooperative multiple inheritance. Note that this method has no keyword arguments, meaning that all methods which are more specialized have to process all keyword arguments.
If I’ve understood correctly, I wonder if this was meant to support calling
__init_subclass__ of all bases of a subclass? Because currently iirc it only called the first one when I was testing.
On second thought: I didn’t try calling
super() so I could be wrong here
I think in any real code you definitely need to make sure you call
super().__init_subclass__(**kwargs), since you want to make sure that any kwargs are passed on to the base classes. But - on the other hand - this is only really important, imo, if you’re developing a library. If you’re developing an app and know that nothing special is needed in base classes (you know the whole
mro chain of all classes), you could decide to leave this out (and doing so might only come back to bite you later, when you forget about this and add a deeper subclass…). When you call
super (and all base classes in their
__init_subclass__ methods also do so), then all base classes can update their class variables accordingly.