In my codebase I have tens of already existing sub classes and I want to hook extra processing code into one public method inherited and overridden by all of those sub classes.
So I am trying to use __init_subclass__ to do that, and this is the result:
def log(return_value):
print('Logged:', return_value)
class Car:
@staticmethod
def logged_run(run):
def wrap(self):
result = run(self)
log(result)
return result
return wrap
def run(self):
return 'Car'
def __init_subclass__(cls) -> None:
super().__init_subclass__()
cls.run = cls.logged_run(cls.run)
class AutoCar(Car):
def run(self):
return 'AutoCar'
auto = AutoCar()
car_type = auto.run()
Output:
Logged: AutoCar
I searched and did not find such usage for __init_subclass__, so is there anything wrong with such an implementation?
Do I need to call super __init__subclass_ as in my example super().__init_subclass__()?
Do I need **kwargs in the parameter list of __init_subclass__?
Finally, is there a better way to do it?
So, yes - your usage will produce you the right results - and, yes, your code would be better if it included the provisions you are in doubt in
__init_subclass__. That way your class hierarchy will not be broken -__init_subclass__wise, if at any point someone on the same codebase decides to add some functionality via mixin classes there.So, to sumarize:
__init_subclass__is automatically considered as aclassmethod: no need for the decorator. And it will receive any kwargs passed as part of theclassstatement, butmetaclass(it is consumed by the the class creation machinery) - and any non-recognized such parameters should be passed on tosuper().__init_subclass__:whether your code for
__init_subclass__is before or after the call tosuper()does not matter for Python, and is up to you - it won't make any difference but if you have class initialisations designed to work in collaboratively.It is also worth mentioning
__init_subclass__is called before the metaclass__init__method, if there is any, in a custom metaclass.Also, even if working with strict typing, I think it would be redundant to annotate the return type (None) of
__init_subclass__: your tools should know about it, as it is in the language spec, and human readers of the code will just see there is no return statement (along with no typing errors or warnings)Apart from the workings of
__init_subclass__, one peculiarity of your task is that it will wrap each version of a given method in each subclass in your hierarchy - if the methods use asuper()call to themselves, the decorator will run multiple-times.This is not easy to overcome if undesired, and have to be done "the hard way": setting a state somewhere (usually in an instance attribute) to indicate the decorator has already "entered" and have an
ifguard clause at the decorator code so that it does not run more than once per call.