PyQt多EnumComboBoxDelegate实例导致启动崩溃问题排查与修复
QTableView多枚举下拉委托实例导致崩溃的问题分析与修复
问题背景
为QTableView的两列分别实现基于枚举的下拉框委托,运行代码时程序直接崩溃,退出码为-1073741819 (0xC0000005);但复用同一个委托实例时无报错。该问题存在于PyQt4+Python2.7环境,Python3.x中也会出现。
示例代码
from PyQt4 import QtGui, QtCore import enum Color = enum.Enum("Color", ("RED", "BLUE")) Shape = enum.Enum("Shape", ("TRIANGLE", "CIRCLE")) class EnumComboBoxDelegate(QtGui.QItemDelegate): def __init__(self, enum_cls, parent=None): super(EnumComboBoxDelegate, self).__init__(parent) self.enum_cls = enum_cls self.enum_objects = list(enum_cls) self.enum_names = [enum_obj.name for enum_obj in self.enum_objects] self.enum_values = [enum_obj.value for enum_obj in self.enum_objects] def createEditor(self, widget, option, index): editor = QtGui.QComboBox(widget) for user_friendly_name in self.enum_names: editor.addItem(user_friendly_name) return editor def setEditorData(self, editor, index): combobox_index, is_int = index.model().data(index, QtCore.Qt.EditRole).toInt() if is_int: editor.setCurrentIndex(combobox_index) else: editor.setCurrentIndex(0) def setModelData(self, editor, model, index): combobox_index = editor.currentIndex() if not combobox_index: combobox_index = 0 model.setData(index, combobox_index, QtCore.Qt.EditRole) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) class CentralWidget(QtGui.QWidget): def __init__(self, *args, **kwargs): super(CentralWidget, self).__init__(*args, **kwargs) main_layout = QtGui.QVBoxLayout() table_view = QtGui.QTableView() table_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) table_view.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) table_model = QtGui.QStandardItemModel(2, 2, None) table_view.setModel(table_model) color_combo_delegate = EnumComboBoxDelegate(Color) table_view.setItemDelegateForColumn(0, color_combo_delegate) shape_combo_delegate = EnumComboBoxDelegate(Shape) table_view.setItemDelegateForColumn(1, shape_combo_delegate) main_layout.addWidget(table_view) self.setLayout(main_layout) def run_self_contained_widget(): import sys app = QtGui.QApplication(sys.argv) main_window = QtGui.QMainWindow() main_window.setCentralWidget(CentralWidget()) main_window.show() sys.exit(app.exec_()) if __name__ == "__main__": run_self_contained_widget()
崩溃原因分析
核心问题是Python垃圾回收与Qt对象生命周期不匹配:
- 在
CentralWidget.__init__中创建的两个委托实例是局部变量,当__init__执行完毕后,Python侧对这两个实例的引用计数降为0,触发垃圾回收,导致对应的C++委托对象被销毁。 - PyQt4中
QTableView.setItemDelegateForColumn仅保存C++对象指针,不会主动持有Python层面的委托引用。当QTableView后续尝试访问已销毁的委托对象时,会触发内存访问违例(错误码0xC0000005),直接导致程序崩溃。 - 复用同一个委托实例时,该实例的引用计数不会在
__init__结束后降为0,因此不会被垃圾回收,C++对象得以保留,程序正常运行。
修复方案
有两种可靠的修复方式:
方案1:为委托设置父对象为QTableView
利用Qt的父子对象机制管理生命周期,Qt会在父对象销毁时才销毁子对象,避免提前释放:
# 修改CentralWidget中的委托创建代码 color_combo_delegate = EnumComboBoxDelegate(Color, parent=table_view) table_view.setItemDelegateForColumn(0, color_combo_delegate) shape_combo_delegate = EnumComboBoxDelegate(Shape, parent=table_view) table_view.setItemDelegateForColumn(1, shape_combo_delegate)
方案2:将委托实例保存为CentralWidget的属性
通过绑定到CentralWidget的实例属性,让Python侧一直持有引用,避免垃圾回收:
# 修改CentralWidget中的委托创建代码 self.color_combo_delegate = EnumComboBoxDelegate(Color) table_view.setItemDelegateForColumn(0, self.color_combo_delegate) self.shape_combo_delegate = EnumComboBoxDelegate(Shape) table_view.setItemDelegateForColumn(1, self.shape_combo_delegate)
两种方案都能保证委托对象在QTableView生命周期内有效,解决内存访问违例问题。
内容的提问来源于stack exchange,提问作者Kaia
相关产品推荐
相关产品推荐

