【问题标题】:An Object is created twice in Python一个对象在 Python 中被创建了两次
【发布时间】:2016-06-08 04:55:15
【问题描述】:

我已阅读 Expert Python Programming,其中包含一个多继承示例。书的作者有解释,但我没看懂,所以想换个说法。

示例显示对象B 被创建了两次!

能否给我一个直观的解释。

In [1]: class A(object):
    ...:     def __init__(self):
    ...:         print "A"
    ...:         super(A, self).__init__()

In [2]: class B(object):
    ...:     def __init__(self):
    ...:         print "B"
    ...:         super(B, self).__init__()

In [3]: class C(A,B):
    ...:     def __init__(self):
    ...:         print "C"
    ...:         A.__init__(self)
    ...:         B.__init__(self)

In [4]: print "MRO:", [x.__name__ for x in C.__mro__]
MRO: ['C', 'A', 'B', 'object']

In [5]: C()
C
A
B
B
Out[5]: <__main__.C at 0x3efceb8>

本书作者说:

这是由于A.__init__(self) 调用而发生的,该调用是使用 C实例,从而使super(A, self).__init__()调用B的构造函数

我不明白的一点是A.__init__(self) 调用将如何使super(A, self).__init__() 调用B 的构造函数

【问题讨论】:

  • 你为什么不在 C 的 init 方法中使用super
  • 如果您认为super 调用的意思是“调用 MRO 中的下一个方法”,而不是“调用我的父类的方法”,那么这种行为应该更有意义。在这种情况下,您看到 B 打印两次的原因是因为 super 已经安排 B 的 init 被调用(由 A 的 init),因此当您从 C 显式调用 B.init 时,您会得到第二次调用。
  • 查看 Raymond Hettinger 关于 super 的演讲,可能会更有意义:youtube.com/watch?v=EiOglTERPEo
  • 关于您的问题标题,该对象不是“创建两次”。 __init__ 函数被调用了两次,但在此之前已经创建了对象。
  • 澄清@interjay 的评论,__init__() 方法是一个initializer,而不是一个constructor。 C 实例由 __new__() 创建,然后传递给 C.__init__()。 C 的 __init__() 中的错误代码导致对 B 的 __init__() 的两次单独调用,用于已创建的唯一对象。

标签: python oop inheritance


【解决方案1】:

让我们稍微修改一下代码并将__init__ 替换为doit,以确保行为是通用的并且与__init__ 无关。

让我们也添加更多的输出来看看到底发生了什么:

class A(object):
     def doit(self):
         print "A", self, super(A, self)
         super(A, self).doit()

class B(object):
     def doit(self):
         print "B", self, super(B, self)

class C(A,B):
     def doit(self):
         print "C", self
         A.doit(self)
         B.doit(self)

print "MRO:", [x.__name__ for x in C.__mro__]
#MRO: ['C', 'A', 'B', 'object']

C().doit()

这将输出:

C <__main__.C object at ...>
A <__main__.C object at ...> <super: <class 'A'>, <C object>>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>

你看,self 实际上到处都是C 对象,所以当你点击A.doit 时,你实际上有&lt;super: &lt;class 'A'&gt;, &lt;C object&gt;&gt;

翻译成:

对于对象C,调用MRO列表中A之后的下一个(超)类的doit方法

而在 MRO 中 A 之后的下一个类是 B,所以我们最终调用了 B.doit()

还要检查这段代码:

class C(A,B):

     def doit_explain(self):
         print "C", self
         # calls B.doit()
         super(A, self).doit()
         print "Back to C"
         # calls A.doit() (and super in A also calls B.doit())
         super(C, self).doit()
         print "Back to C"
         # and just B.doit()
         B.doit(self)

这里我直接使用super(A, self).doit()而不是A.doit(self),它也会导致B.doit()调用,这是输出:

C <__main__.C object at ...>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>
Back to C
A <__main__.C object at ...> <super: <class 'A'>, <C object>>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>
Back to C
B <__main__.C object at ...> <super: <class 'B'>, <C object>> 

【讨论】:

    【解决方案2】:

    super() 仅表示“下一行”,其中行是mro ['C', 'A', 'B', 'object']。所以A 的下一个是B

    mro 是根据称为C3 linearization 的算法计算的。 当你使用super() 时,Python 会按照这个顺序进行。当您编写课程A 时,您还不知道下一个课程将是哪个课程。只有在您创建具有多重继承的类 C 并运行您的程序后,您才会获得 mro 并“知道”A 的下一步。

    对于您的示例,这意味着:

    C() 调用C__init__(),其中调用A__init__()。现在,A 使用super() 并在mro 中找到B,因此它调用B__init__()。接下来,C__init__() 再次调用B__init__()

    __init__() 中调用super() 会创建不同的mro,并避免对B__init__() 的双重调用。

    from __future__ import print_function
    
    class A(object):
        def __init__(self):
            print("A")
            super(A, self).__init__()
    
    class B(object):
        def __init__(self):
            print("B")
            super(B, self).__init__()
    
    class C(A,B):
        def __init__(self):
            print("C")
            super(C, self).__init__()
    

    用途:

    >>> C.mro()
    [__main__.C, __main__.A, __main__.B, object]
    >> C()
    C
    A
    B
    

    【讨论】:

    • 我还是不明白,你能解释一下吗
    • C 的 MRO 是 ['C', 'A', 'B', 'object']。 C调用A.__init__(),A调用super(),在C的MRO(A.__init__()被调用的地方)下一行是'B',所以print 'B'在A的super()和B.__init__()中被调用
    猜你喜欢
    • 2017-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-10
    • 2018-10-10
    • 2013-10-28
    • 1970-01-01
    相关资源
    最近更新 更多