python - 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?
解决方案
相反,这是 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_()
推荐阅读
- c# - 使用 UTF-8 编码解码 Base64 字符串
- c# - 如何在流文档中设置页宽 (A4)
- typescript - 有没有办法区分 Javascript/TypeScript 中不同接口类型的对象?
- python - Python - 用最接近的非零颜色填充图像的颜色
- python - 如何根据列值/字符串将数据框中的多行替换为另一个数据框中的行?
- azure-powershell - 如何使用“az storage entity insert”命令将特殊字符插入 Azure 表存储?
- visual-studio - Visual Studio:如何在连续执行中重用相同的控制台
- mongoose - 猫鼬将项目添加到数组
- javascript - 一个函数,它接受两个参数,一个数组和一个元素,它返回给定元素的索引
- javascript - 如何使用 JavaScript 创建用于打印的表格设计