如果定义一个没有__init__方法的类会发生什么?

9
假设我定义了以下四个类:
(该代码已在Python 3.6.5上进行了测试。然而,我预计它也可以在Python 2.7.x上使用“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]

请注意以下两点:
  1. 没有 __init__ 方法的 class A;

  2. 根据 mro 信息,C 和 D 都有相同的后继类 A

因此我认为 super(C, self).__init__(value)super(D, self).__init__(value) 都会触发在 A 中定义的 __init__ 方法。

然而,下面的结果让我非常困惑!

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

我们可以看到,class D 的初始化成功了,而class C 失败了。
是什么造成了class Cclass D之间的不同行为呢? 编辑:我认为我的问题不是关于mro(实际上我对python的mro多少有些了解),我的问题是关于__init___方法的不同行为。 编辑2:我太傻了,是mro的问题。
谢谢您的帮助。

可能是Python中的super()如何处理多重继承?的重复问题。 - Yassine Faris
2
对于 D,它可以工作是因为当 super 在父类 A 中找不到 init 时,它会尝试在类 B 中查找 init,而 init 存在于其中。 - Yassine Faris
2个回答

9
  1. Class A has no separate __init__ method - it is looked up via A `s own mro.

    >>> A.__init__ is object.__init__
    True
    
  2. Class B has a separate __init__ method - it does not require traversing the mro.

    >>> B.__init__ is object.__init__
    False
    
请注意,区别在于拥有继承__init__。仅因为可以提供A.__init__,并不意味着A本身具有__init__方法。
当C查找其super().__init__时,将尝试以下操作:
  • 跳过C.__init__
  • A.__init__不存在
  • 存在并调用object.__init__
当D查找其super().__init__时,将尝试以下操作:
  • 跳过D.__init__
  • A.__init__不存在
  • 存在并调用B.__init__
  • 不会查找object.__init__
这种区别是使用super而不是明确的基类的主要重点。它允许将专业类插入到层次结构中(示例)。

1
C.__init__不存在?看起来它已经被定义了。 - Qiang Xu
1
@QiangXu 感谢您指出这一点。我已经相应地调整了答案。 - MisterMiyagi
super(C, self).__init__ 是在 C__init__ 内部调用的,是吗?如果是这样的话,那么 C.__init__ 就没有被跳过对吧?如果我理解有误,请指出来。 - Qiang Xu
1
@QiangXu 在 C 的 super().__init__ 查找中被跳过。super 从当前方法的类之后的 mro 开始搜索。 - MisterMiyagi
super(C, self).__init__ 是被 C.__init__ 调用的,对吗?我有点困惑... - Qiang Xu
1
@QiangXu 是的,“C.__init__”会调用“super(C, self).__init__”。但是当“super(C, self).__init__”在mro中查找“__init__”时,它会跳过“C.__init__”。否则,“C.__init__”将无限地调用自身。 - MisterMiyagi

3
这种行为的原因在于Python属性查找的方式。当你访问A.__init__时,Python内部会遍历A的MRO,直到找到一个定义了__init__属性的类。在此查找过程中,继承的属性将被忽略。
当你在C.__init__中调用super(C, self).__init__(value)时,Python会遍历MRO [A, object],直到找到一个__init__属性。重要的是,A没有定义__init__属性,所以查找会跳过A并返回object.__init__
D.__init__中也会发生同样的事情,只不过这次遍历的MRO是[A, B, object]。同样地,A没有定义__init__属性,所以查找会继续并返回B.__init__
作为一个实验,你可以尝试将A的定义更改为以下内容来定义__init__
class A(object):
    __init__ = object.__init__

你会发现,现在实例化DC一样会抛出同样的错误:

>>> 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接