Suppose that I have defined four classes as following:
(The code has been tested on Python 3.6.5. However, I expected it should also works on Python 2.7.x with from __future__ import print_function
)
In [1]: class A(object):
...: pass
...:
...: class B(object):
...: def __init__(self, value):
...: print('B(value=%s)' % value)
...:
...: class C(A):
...: def __init__(self, value):
...: print('C(value=%s)' % value)
...: super(C, self).__init__(value)
...:
...: class D(A, B):
...: def __init__(self, value):
...: print('D(value=%s)' % value)
...: super(D, self).__init__(value)
...:
In [2]: C.mro()
Out[2]: [__main__.C, __main__.A, object]
In [3]: D.mro()
Out[3]: [__main__.D, __main__.A, __main__.B, object]
Note two things:
class A
without __init__
method;
Both C and D have the same successor A
according the mro
information.
So I suppose that both super(C, self).__init__(value)
and super(D, self).__init__(value)
will trigger the __init__
method defined in A
.
However, the following result confused me very much!
In [4]: C(0)
C(value=0)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-75d9b7f7d447> in <module>()
----> 1 C(0)
<ipython-input-1-5252938615c6> in __init__(self, value)
9 def __init__(self, value):
10 print('C(value=%s)' % value)
---> 11 super(C, self).__init__(value)
12
13 class D(A, B):
TypeError: object.__init__() takes no parameters
In [5]: D(1)
D(value=1)
B(value=1)
Out[5]: <__main__.D at 0x2a6e8755b70
We can see that class D
have initialization succeed, while class C
failed.
What make the different behavior between class C
and class D
?
EDIT I don't think my question is about the mro (actually I known the mro about python more or less), my question is about the different behavior of __init___
method.
EDIT2 I'm silly, it's about mro.
Thanks for your help.
Class A
has no separate __init__
method - it is looked up via A
`s own mro
.
>>> A.__init__ is object.__init__
True
Class B has a separate __init__
method - it does not require traversing the mro
.
>>> B.__init__ is object.__init__
False
Note that the difference is having and inheriting __init__
. Just because A.__init__
can be provided does not mean A
has an __init__
method by itself.
When C looks up its super().__init__
, the following is tried:
C.__init__
is skippedA.__init__
does not existobject.__init__
exists and is calledWhen D looks up its super().__init__
, the following is tried:
D.__init__
is skippedA.__init__
does not existB.__init__
exists and is calledobject.__init__
is never looked upThis difference is a major point to using super
instead of an explicit base class. It allows inserting specialised classes into a hierarchy (example).
The reason for this behavior is the way attribute lookup works in python. When you access A.__init__
, python internally traverses A
's MRO until it finds a class that defines an __init__
attribute. Inherited attributes are disregarded during this lookup.
When you call super(C, self).__init__(value)
in C.__init__
, python traverses the MRO [A, object]
until it finds an __init__
attribute. The important thing is that A
does not define an __init__
attribute, so the lookup goes past A
to object
and returns object.__init__
.
The same thing happens in D.__init__
, except in this case the MRO being traversed is [A, B, object]
. Again, A
doesn't define an __init__
, so the lookup continues and returns B.__init__
.
As an experiment, you can change the definition of A
to define an __init__
like so:
class A(object):
__init__ = object.__init__
And you'll notice that instantiating D
now throws the same error as C
:
>>> D(3)
D(value=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "untitled.py", line 17, in __init__
super(D, self).__init__(value)
TypeError: object.__init__() takes no parameters
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With