Winand
Winand

Reputation: 2433

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?

Upvotes: 2

Views: 894

Answers (1)

eyllanesc
eyllanesc

Reputation: 243945

On the contrary, it is a bug of PySide2 since it contradicts the docs:

Multiple Inheritance Requires QObject to Be First

If you are using multiple inheritance, moc assumes that the first inherited class is a subclass of QObject. Also, be sure that only the first inherited class is a QObject.

If the behavior described in the previous part is considered then the Dialog class should only take into account UserForm and not QDialog so Dialog should not have the exec_() method since only the classes that inherit from QDialog can have it.

  • In Qt you should not inherit from 2 QObject, only supports mixins (QObject + not QObject) (1)(2).

  • If it is a mixin then the first must be the QObject.

And that's the pattern that pyuic to inherit:

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

Upvotes: 2

Related Questions