如何使 super() 在 python 的 non-ideal 情况下工作?

How to make super() work in this non-ideal situation in python?

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(来自单个编译库并且很难更改)和您自己的 ClsThreeeobject在一起。因为 ClsThree 忘记在其构造函数中调用 super(),所以他们的孩子在使用 super()[ 时无法执行 ClsThreee 的构造函数.

因此,输出将是这样的:

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 中没有用。

super() 是一个 proxy object 使用 Method Resolution Order (MRO) 来确定在 super().

上执行调用时要调用的方法

如果我们检查 ClassFour__mro__,我们得到:

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

现在 super(T,self) 是使用来自(但不包括)T 的 MRO 的代理 object。因此,这意味着 super(ClsFour,self) 是一个代理 object,适用于:

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

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

所以super(ClsFour,self).__init__returnClsThree.__init__方法。 MRO还用于查找未在class级别上定义的方法、属性等。因此,如果您使用 self.x 并且 x 不是 object 的属性,也不是 ClsFour object 的属性,它将再次遍历 MRO 以搜索x.

如果你想调用全部__init__和[=19]的直接parent =] 你可以使用:

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

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

编辑:请注意,结果 super() 没有 指向 parent 的必要点, class 的祖父parentand/or。但是要 parent classes 的 object.

ClsThree class 中的 super(ClsThree,self) 将 - 鉴于它是 ClsFour object,与 相同mro(因为它从 self 中获取 mro)。所以 super(ClsThree,self) 将检查以下 classes 序列:

(ClsThreee, ClsTwo, ClsOne, object)

例如,如果我们写(超出任何class的范围)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

总结

Because ClsThree forgets to call super() in its constructor, their kid cannot execute ClsThreee's constructor when it uses super().

Super Considered Super 博客 post 的 "How to Incorporate a Non-cooperative Class" 部分对此进行了介绍。

关键是创建一个 adapter class,它通过调用 super() 来按规则合作行动。使用该适配器包装原始 class.

制定的代码

在下面的代码中,AdaptThree 是新适配器 class 和 ClsFour 现在继承自 AdaptThree 而不是原来的黑盒非合作 class.

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 看起来也不合作,也应该包裹起来。

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

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

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

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 已改进 Support for Cooperative Multi-inheritance,因此此问题不会出现)。