【问题标题】:PyQt: crash on double inheritance from QWidgetPyQt:从 QWidget 双重继承时崩溃
【发布时间】:2019-11-14 13:48:06
【问题描述】:

PyQt5、Qt 5.9.6 会出现以下问题。

我想基于另一个类 (UserForm) 生成模态对话框类 (Dialog)。但是如果UserForm 已经从QWidget 继承,那么我的脚本会引发异常

“Dialog”对象没有属性“exec_”

或者只是静默崩溃或在控制台中显示一条消息崩溃:

QDialog::exec: 检测到递归调用

如果UserForm 是否继承自 QWidget,MRO 看起来完全一样:

(<class '__main__.Dialog'>, <class '__main__.UserForm'>, <class
'PyQt5.QtWidgets.QDialog'>, <class 'PyQt5.QtWidgets.QWidget'>, <class
'PyQt5.QtCore.QObject'>, <class 'sip.wrapper'>, <class
'PyQt5.QtGui.QPaintDevice'>, <class 'sip.simplewrapper'>, <class 'object'>)

例子:

from PyQt5 import QtWidgets
app = QtWidgets.QApplication([])

class UserForm(QtWidgets.QWidget):
    pass

# class Dialog(UserForm, QtWidgets.QDialog):
#     pass
Dialog = type("Dialog", (UserForm, QtWidgets.QDialog), {})

print(Dialog.__mro__)
Dialog().exec_()

我还尝试使用 PySide2、Qt 5.12 运行此代码,它可以正常工作。这是否意味着 PyQt 中存在一些错误?

【问题讨论】:

    标签: python pyqt pyqt5 qwidget


    【解决方案1】:

    相反,这是 PySide2 的一个 bug,因为它与 docs 相矛盾:

    多重继承要求QObject在前

    如果您正在使用 多重继承,moc 假设第一个继承的类是 QObject 的子类。另外,请确保只有第一个继承的类 是一个 QObject。

    如果考虑到上一部分中描述的行为,那么 Dialog 类应该只考虑 UserForm 而不是 QDialog 所以 Dialog 不应该有 exec_() 方法,因为只有从 QDialog 继承的类才能拥有它。

    • 在Qt中你不应该从2个QObject继承,只支持mixins(QObject + not QObject)(1)(2).

    • 如果是mixin,那么第一个必须是QObject。

    这就是 pyuic 要继承的模式:

    from PyQt5 import QtCore, QtGui, QtWidgets
    # or
    # from PySide2 import QtCore, QtGui, QtWidgets
    
    class Ui_Dialog(object):
        def setupUi(self, Dialog):
            Dialog.setObjectName("Dialog")
            Dialog.resize(400, 300)
            self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
            self.buttonBox.setGeometry(QtCore.QRect(30, 240, 341, 32))
            self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
            self.buttonBox.setStandardButtons(
                QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok
            )
            self.buttonBox.setObjectName("buttonBox")
    
            self.retranslateUi(Dialog)
            self.buttonBox.accepted.connect(Dialog.accept)
            self.buttonBox.rejected.connect(Dialog.reject)
            QtCore.QMetaObject.connectSlotsByName(Dialog)
    
        def retranslateUi(self, Dialog):
            _translate = QtCore.QCoreApplication.translate
            Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
    
    
    def init(self, parent=None):
        super(self.__class__, self).__init__(parent)
        self.setupUi(self)
    
    # or
    # class Dialog(QtWidgets.QDialog, Ui_Dialog):
    #    def __init__(self, parent=None):
    #        super(Dialog, self).__init__(parent)
    #        self.setupUi(self)
    
    Dialog = type("Dialog", (QtWidgets.QDialog, Ui_Dialog), {"__init__": init})
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication([])
        Dialog().exec_()
    

    (1) Multiple Inheritance
    (2) Cooperative Multi-inheritance

    【讨论】:

    • 但是mro() 的结果对我来说看起来不错,我认为在 MRO 过程中解决了类之间的所有依赖关系。
    • @Winand 好吧,Qt 不会发生这种情况,不幸的是,Qt 的设计有点特别:-(
    • 对我来说还有一个新事实:PySide 的 QObject 不像 PyQt 那样初始化下一个超类(如Cooperative Multi-inheritance 文章所示)
    • @Winand 每个绑定都在实现自己的方法以使其更加 Pythonic(这 2 个仍在轨道上)但具有不同的基础(SIP 和 shiboken)通常有自己的限制,并且在那个路径中恕我直言尽管 PySide2 已经在更新,但 PyQt5 正在获得进步(仅比较错误)
    猜你喜欢
    • 2013-10-21
    • 1970-01-01
    • 2014-10-11
    • 1970-01-01
    • 1970-01-01
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多