pyqt 类 的方法解析顺序是什么?

What's the method resolution order with pyqt classes?

我最近被问到有关 pyqt 继承的问题,问题是:假设我有 class A 和方法 closeEvent(event),以及 class B 继承 A 和QMainWindow(因此具有 closeEvent(event) 方法)。 closeEvent 的解决顺序是什么?考虑到 python 文档,这取决于继承顺序。但是下面的例子表明情况并非如此......有人可以提供帮助吗? 我在 windows 上使用 Python3.6.2 和 PyQt5.

from PyQt5.QtWidgets import QApplication, QMainWindow
import sys

class A():
    def closeEvent(self,event):
        print("A")

class B(QMainWindow):
    def closeEvent(self,event):
        print("B")
        super().closeEvent(event)

class C(A,B):
    pass

class D(B,A):
    pass

class E(QMainWindow,A):
    pass

class F(A,QMainWindow):
    pass

def test(TestClass, msg):
    """Create class instance and show it. Click on cross to close."""
    print(msg)
    app = QApplication(sys.argv)
    test = TestClass()
    test.show()
    app.exec()

test(C,"C(A,B)?") #           >>> A
test(D,"D(B,A)?") #           >>> B
test(E,"E(QMainWindow,A)?") # >>> A ??? Why ???
test(F,"F(A,QMainWindow)?") # >>> A

这是一个非常复杂的问题,涉及到 Python 和 C++ 在协作多重继承的工作方式方面的差异。所以我要在这里给出的答案更多是简要概述的方式。我认为完整的治疗对于单个 SO 答案可能是不可行的。

部分问题在于 PyQt 如何确定 class 它应该继承什么。这可以通过比较纯 Python 的 class 层次结构的 PyQt 版本的 __base__ 属性的值来说明:

class A: pass
class B(QMainWindow): pass
class C(A, B): pass
class D(B, A): pass
class E(QMainWindow, A): pass
class F(A, QMainWindow): pass

print('pyqt:')
print('C:', C.__base__)
print('D:', D.__base__)
print('E:', E.__base__)
print('F:', F.__base__)

class M: pass
class A: pass
class B(M): pass
class C(A, B): pass
class D(B, A): pass
class E(M, A): pass
class F(A, M): pass

print('python:')
print('C:', C.__base__)
print('D:', D.__base__)
print('E:', E.__base__)
print('F:', F.__base__)

输出:

pyqt:
C: <class '__main__.B'>
D: <class '__main__.B'>
E: <class 'PyQt5.QtWidgets.QMainWindow'>
F: <class 'PyQt5.QtWidgets.QMainWindow'>
python:
C: <class '__main__.A'>
D: <class '__main__.B'>
E: <class '__main__.M'>
F: <class '__main__.A'>

这不是错误。这里的差异完全是由于实现细节,我不打算解释(即使我可以)。 PyQt 很可能会 更改其实现以更紧密地与 Python 行为保持一致。但它选择了妥协,而不是与 Qt/C++ 中的工作方式相去甚远。我猜想这个决定是受到向后兼容性问题的影响,也许还有维护代码库的便利性。如果你想在这一点上得到明确的答案,可能你必须问 PyQt 的作者。

问题的另一个(更大的)部分在于当层次结构中的某些 classes 本质上是不合作的(例如,因为它们是用 C++ 编写的,如 Qt是)。我认为或许可以在某种程度上解决此类问题——PyQt 至少已经为 class 构造做到了这一点(参见 Support for Cooperative Multi-inheritance)。但是对于像 Qt 这样的大型库来说,这样做显然是一项更大的任务。我想总会有一些皱纹(比如你发现的那个)永远不会完全消除。