You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 19:42:02