3
class ClsOne(object):
    def __init__(self):
        super(ClsOne, self).__init__()
        print "Here's One"

class ClsTwo(ClsOne):
    def __init__(self):
        super(ClsTwo, self).__init__()
        print "Here's Two"

class ClsThree(ClsTwo): # Refer to one blackbox object
    def __init__(self):
        # super(ClsThree, self).__init__()
        print "Here's Three"

class ClsThreee(ClsTwo): # Refer to your custom object
    def __init__(self):
        super(ClsThreee, self).__init__()
        print "Here's Threee"

class ClsFour(ClsThree, ClsThreee): # Multiple Inheritance
    def __init__(self):
        super(ClsFour, self).__init__()
        print "Here's Four"

entity = ClsFour()

在这种情况下,您尝试将ClsThree(来自单个编译库并且很难更改)和您自己的ClsThree对象组合在一起。因为ClsThree忘记在其构造函数中调用super() ,所以他们的孩子在使用super()时无法执行 ClsThree 的构造函数。

结果,输出将如下所示:

Here's Three
Here's Four

显然,我可以手动调用 ClsFour 的每个基,而不是使用 super(),但是当这个问题分散在我的代码库中时,它有点复杂。

顺便说一句,那个黑盒的东西是 PySide :)

补充:

感谢@WillemVanOnsem 和@RaymondHettinger,解决了之前的ClsFour 问题。但是通过一些进一步的调查,我发现 PySide 中的类似问题没有相同的概念。

在 ClsFour 上下文中,如果您尝试运行:

print super(ClsFour, self).__init__

你会得到:

<bound method ClsFour.__init__ of <__main__.ClsFour object at 0x00000000031EC160>>

但在以下 PySide 上下文中:

import sys
from PySide import QtGui

class MyObject(object):
    def __init__(self):
        super(MyObject, self).__init__()
        print "Here's MyObject"

class MyWidget(QtGui.QWidget, MyObject):
    def __init__(self):
        super(MyWidget, self).__init__()

app = QtGui.QApplication(sys.argv)
widget = MyWidget()
print super(MyWidget, widget).__init__

结果是:

<method-wrapper '__init__' of MyWidget object at 0x0000000005191D88>

它不打印“Here's MyObject”,并且 super() 的 init 属性也有不同的类型。以前,我尝试将这个问题简化为 ClsFour。但现在我认为它并不完全一样。

我猜这个问题出现在 shiboken 库中,但我不确定。

尖端:

这个问题也出现在 PyQt 上下文中,但是可以让 MyObject 继承自 QObject 来解决。这个解决方案在 PySide 中没有用。

4

3 回答 3

3

super()是一个代理对象,它使用方法解析顺序 (MRO)来确定在对 执行调用时要调用的方法super()

如果我们检查 的__mro__ClassFour我们得到:

>>> ClsFour.__mro__
(<class '__main__.ClsFour'>, <class '__main__.ClsThree'>, <class '__main__.ClsThreee'>, <class '__main__.ClsTwo'>, <class '__main__.ClsOne'>, <type 'object'>)

或者我自己让它更短(不是 Python 输出):

>>> ClsFour.__mro__
(ClsFour, ClsThree, ClsThreee, ClsTwo, ClsOne, object)

Nowsuper(T,self)是一个使用来自(但不包括)的 MRO 的代理对象T。这意味着这super(ClsFour,self)是一个代理对象,适用于:

(ClsThree, ClsThreee, ClsTwo, ClsOne, object)  # super(ClsFour,self)

如果你查询一个类的一个属性(一个方法也是一个属性)会发生什么,Python 将遍历 MRO 并检查元素是否具有这样的属性。所以它会首先检查是否ClsThree有一个__init__属性,如果没有它会继续寻找它ClsThreee等等。从找到此类属性的那一刻起,它将停止并返回它。

所以super(ClsFour,self).__init__返回ClsThree.__init__方法。MRO 还用于查找未在类级别定义的方法、属性等。因此,如果您使用self.xandx不是对象的属性,也不是ClsFour对象的属性,它将再次遍历 MRO 以搜索x.

如果您想调用您__init__所有直接父母,可以ClassFour使用:

class ClsFour(ClsThree, ClsThreee):
    def __init__(self):
        # call *all* *direct* parents __init__
        for par in ClsFour.__bases__:
            par.__init__(self)

这可能是最优雅的,因为如果基础发生变化,它仍然可以工作。请注意,您必须确保__init__每个父母都存在。然而,由于它是在object级别上定义的,我们可以安全地假设这一点。然而,对于其他属性,我们不能做出这样的假设。

编辑:请注意,因此没有super()必要指向该班级的父母、祖父母和/或祖先。而是对象的父类。

在类super(ClsThree,self)ClsThree,将 - 给定它是一个ClsFour对象,使用相同的 mro(因为它mro从 中获取self)。因此super(ClsThree,self)将检查以下类序列:

(ClsThreee, ClsTwo, ClsOne, object)

例如,如果我们写(超出任何类的范围)super(ClsTwo,entity).__init__(),我们会得到:

>>> super(ClsTwo,entity).__init__()
Here's One
>>> super(ClsThree,entity).__init__()
Here's Two
Here's Threee
>>> super(ClsThreee,entity).__init__()
Here's Two
>>> super(ClsFour,entity).__init__()
Here's Three
于 2017-07-28T16:28:55.617 回答
0

概括

因为 ClsThree 忘记在其构造函数中调用 super(),所以他们的孩子在使用 super() 时无法执行 ClsThree 的构造函数。

这在Super Considered Super博客文章的“如何合并非合作类”部分中进行了介绍。

关键是创建一个适配器类,通过调用super()来按照规则进行协作。使用该适配器来包装原始类。

制定的代码

在下面的代码中,AdaptThree是新的适配器类,而ClsFour 现在继承自AdaptThree,而不是原来的黑盒非协作类。

class ClsOne(object):
    def __init__(self):
        print "Here's One"

class ClsTwo(ClsOne):
    def __init__(self):
        print "Here's Two"

class ClsThree(ClsTwo): # Refer to one blackbox object
    def __init__(self):
        # super(ClsThree, self).__init__()
        print "Here's Three"

class AdaptThree(object):
    def __init__(self):
        _three = ClsThree()
        super(AdaptThree, self).__init__()          

class ClsThreee(ClsTwo): # Refer to your custom object
    def __init__(self):
        super(ClsThreee, self).__init__()
        print "Here's Threee"

class ClsFour(AdaptThree, ClsThreee): # Multiple Inheritance
    def __init__(self):
        super(ClsFour, self).__init__()
        print "Here's Four"

entity = ClsFour()

这输出:

Here's Three
Here's Two
Here's Threee
Here's Four

其他详情

  • 在 OP 的示例中,ClsTwo看起来也不是合作的,也应该被包装。

  • 大概这些类具有初始化以外的方法。适配器类也需要包装和分派这些调用。

  • 根据应用程序,使用组合可能比继承更容易。

于 2017-07-28T18:19:05.033 回答
0

关于 PySide/PyQt4 特有的问题:一种解决方案是确保任何 mixin 始终在基类定义中的 Qt 类之前:

import sys
from PySide import QtGui

class MyObject(object):
    def __init__(self, parent=None, other=None):
        super(MyObject, self).__init__(parent)
        print "Here's MyObject: %r" % other

class MyWidget(MyObject, QtGui.QWidget):
    def __init__(self, parent=None, other=None):
        super(MyWidget, self).__init__(parent, other)

app = QtGui.QApplication(sys.argv)
parent = QtGui.QWidget()
widget = MyWidget(parent, 'FOO')
print super(MyWidget, widget).__init__
# check that the QWidget.__init__ was called correctly
print 'widget.parent() is parent:', widget.parent() is parent

输出:

Here's MyObject: 'FOO'
<bound method MyWidget.__init__ of <__main__.MyWidget object at 0x7f53b92cca28>>
widget.parent() is parent: True

(注意:PyQt5 改进了对 Cooperative Multi-inheritance 的支持,因此不会出现此问题)。

于 2017-07-29T15:05:58.367 回答