首页 > 解决方案 > PyQt: crash on double inheritance from QWidget

问题描述

The following problem happens with PyQt5, Qt 5.9.6.

I want to generate modal dialog class (Dialog) based on another class (UserForm). But if UserForm already inherits from QWidget then my script raises exception

'Dialog' object has no attribute 'exec_'

or just crashes silently or crashes with a message in console:

QDialog::exec: Recursive call detected

MRO looks exactly the same if UserForm is inherited from QWidget or not:

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

Example:

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

I've also tried to run this code with PySide2, Qt 5.12 and it works without issues. Does it mean that there's some bug in PyQt?

标签: pythonpyqtpyqt5qwidget

解决方案


相反,这是 PySide2 的一个错误,因为它与文档相矛盾:

多重继承要求 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)多继承
(2)协作多继承


推荐阅读