I have encountered a peculiar behavior with the __new__ method in Python and would like some clarification on its functionality in different scenarios. Let me illustrate with two unrelated classes, A and B, and provide the initial code:
class A:
def __new__(cls, *args, **kwargs):
return super().__new__(B)
def __init__(self, param):
print(param)
class B:
def __init__(self, param):
print(param)
if __name__ == '__main__':
a = A(1)
In this case, no output is generated, and neither A's __init__ nor B's __init__ is called.
However, when I modify the code to make B a child of A:
......
class B(A):
......
Suddenly, B's __init__ is invoked, and it prints 1.
I am seeking clarification on how this behavior is occurring. In the first case, if I want to invoke B's __init__ explicitly, I find myself resorting to the following modification:
class A:
def __new__(cls, *args, **kwargs):
obj = super().__new__(B)
obj.__init__(*args, **kwargs)
return obj
Can someone explain why the initial code behaves as it does and why making B a child of A alters the behavior? Additionally, how does the modified code explicitly calling B's __init__ achieve the desired outcome and not without it?
TL;DR
super().__new__(B)is not the same asB().As @jonsharpe pointed out in a comment,
__init__is only called when an instance ofclsis returned. But this implicit call to__init__is handled* bytype.__call__, not the call to__new__itself before it returns.For example, you can imagine
type.__call__is implemented as something likeSo when you call
A(1)in the first case, this translates toA.__call__(1), which gets implemented astype.__call__(A, 1).Inside
__call__,clsis bound toA, so the first line is equivalent toInside
A.__new__, you don't passclsas the first argument tosuper().new, butB, soobjgets assigned an instance ofB. That object is not an instance ofA, soobj.__init__never gets called, and then the instance is returned.When you make
Ba subclass ofA, nowobjis an instance ofA, soobj.__init__gets called.* I believe this to be the case; everything I've ever tried is consistent with this model, but I haven't delved deeply enough into CPython to confirm.
For example, if you change
A's metaclass tothen
B.__init__fails to be called, even whenBis a subclass ofA.The sentence in the documentation,
is vague about what exactly "during object construction" means and who is invoking
__init__. My understanding oftype.__call__conforms to this, but alternate implementations may be allowed. That would seem to make defining any__call__method in a custom metaclass that doesn't returnsuper().__call__(*args, **kwargs)a dicey proposition.