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

子类化QSortFilterProxyModel中lessThan()调用次数过多问题排查

排序耗时异常排查:QSortFilterProxyModel的lessThan()调用次数远超预期

问题背景

我实现了自定义QAbstractItemModel,搭配QTreeView使用(数据是一维的,但需要QTreeView的部分显示功能),中间用了重写filterAcceptsRow()和lessThan()方法的QSortFilterProxyModel,已将DynamicSortFilter设为False,通过其他部件的信号手动触发invalidate()。

填充更多数据后,排序耗时剧增。确认lessThan()直接使用模型存储的数据做整数比较,但问题依旧。统计该函数调用次数发现远超预期:15行数据排序时,lessThan()被调用880次,其中仅前55次来自手动触发:

--- 调用链(从上到下)---
调用者: (文件: main.py, 行: 48)
调用者: (文件: mainwindow.py, 行: 60)
调用者: set_filter(文件: eventlistproxymodel.py, 行: 126)
126 self.invalidate()

其余825次调用来自内部,无法追溯完整调用链:

--- 调用链(从上到下)---
调用者: (文件: main.py, 行: 48)
48 app.exec()

数据量越大问题越严重:100多行数据需要7万+次调用,耗时2秒。额外调用次数始终等于行数 × 手动触发的调用次数,像是重复了行数遍,具体数据如下:

行数可追溯调用次数总调用次数计算公式
6963(1+6) * 9 = 63
1555880(1+15) * 55 = 880
421847912(1+42) * 184 = 7912

我是一名医生,为简化工作自学编程,不清楚如何利用这一线索排查问题,最初怀疑伪树结构的模型或视图,但检查无果,希望得到排查思路。

相关代码片段

自定义QAbstractItemModel

class EventListModel(QAbstractItemModel):

    COLUMN_COUNT: int = 15

    EVT_RECEIVER: int = 0
    EVT_TYPE: int = 1
    EVT_ID: int = 2
    EVT_CATEGORY: int = 3
    EVT_NAME: int = 4
    EVT_REMAINAMOUNT: int = 5
    EVT_TOTALAMOUNT: int = 6
    EVT_PERCENTAGE: int = 7
    EVT_DUEDATE: int = 8
    EVT_PAYMENTTYPE: int = 9
    EVT_CREATEDATE: int = 10
    EVT_DESCR: int = 11
    EVT_RESPONSIBLE: int = 12
    EVT_TODAYSHARE: int = 13
    EVT_TERMFLAGS: int = 14

    internalValueRole = Qt.ItemDataRole.UserRole + 1
    customSpanRole = Qt.ItemDataRole.UserRole + 2
    sortRole = Qt.ItemDataRole.UserRole + 3

    stats_recalculated = Signal(object)

    def __init__(self, parent=None):
        super().__init__(parent)

        self.root = 0
        self.event_list: List = []

    def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
        return len(self.event_list)

    def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
        return self.COLUMN_COUNT

    def index(self, row, column, parent: QModelIndex = QModelIndex()) -> QModelIndex:
        return self.createIndex(row, column, self.root)

    def parent(self, index: QModelIndex) -> QModelIndex:
        return QModelIndex()

    def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
        if not index.isValid():
            return None
        try:
            entry = self.event_list[index.row()]
        except IndexError:
            return None
        if role == Qt.ItemDataRole.DisplayRole:
            return self.display_value(entry, index.column())
        elif role == self.internalValueRole:
            return list(vars(entry).values())[index.column()]
        elif role == self.sortRole:
            return entry.category

自定义QSortFilterProxyModel

class EventListProxyModel(QSortFilterProxyModel):

    FILTER_TERM: int = 0
    FILTER_CATEGORY: int = 1
    FILTER_RECEIVER: int = 2
    FILTER_RESPONSIBLE: int = 3
    FILTER_HEADER: int = 10
    FILTER_FOOTER: int = 11

    def __init__(self, parent=None):
        super(EventListProxyModel, self).__init__(parent)

        self.sort(0)
        self.setDynamicSortFilter(False)

        self.sortfilter_enabled: bool = True

        self.term_filter: int = 0
        self.category_filter: int = 0
        self.receiver_filter: str = ""
        self.responsible_filter: str = ""
        self.header_filter: bool = False
        self.footer_filter: bool = False

    def enable_sortfilter(self, enable: bool):
        self.sortfilter_enabled = enable

    def set_filter(self, filter_type: int, condition: str | int | bool):
        if filter_type == self.FILTER_TERM:
            self.term_filter = condition
        elif filter_type == self.FILTER_CATEGORY:
            if condition == 0:
                self.category_filter = 0
            else:
                self.category_filter = list(self.sourceModel().CATEGORIES.keys())[condition]
        elif filter_type == self.FILTER_RECEIVER:
            self.receiver_filter = condition
        elif filter_type == self.FILTER_RESPONSIBLE:
            self.responsible_filter = condition
        elif filter_type == self.FILTER_HEADER:
            self.header_filter = condition
        elif filter_type == self.FILTER_FOOTER:
            self.footer_filter = condition

    def filterAcceptsRow(self, source_row, source_parent):
        <...>
        return True

    def lessThan(self, source_left, source_right, /):

        if not self.sortfilter_enabled:
            return True

        left_category = self.sourceModel().index(source_left.row(), 0, source_left.parent()).data(EventListModel.sortRole)
        right_category = self.sourceModel().index(source_right.row(), 0, source_right.parent()).data(EventListModel.sortRole)

        if left_category != right_category:
            return left_category < right_category
        else:
            left_type = self.sourceModel().index(source_left.row(), EventListModel.EVT_TYPE, source_left.parent()).data(EventListModel.internalValueRole)
            right_type = self.sourceModel().index(source_right.row(), EventListModel.EVT_TYPE,  source_right.parent()).data(EventListModel.internalValueRole)
            if left_type != right_type:
                return right_type > left_type
            else:
                left_duedate = self.sourceModel().index(source_left.row(), EventListModel.EVT_DUEDATE,  source_left.parent()).data(EventListModel.internalValueRole)
                right_duedate = self.sourceModel().index(source_right.row(), EventListModel.EVT_DUEDATE, source_right.parent()).data(EventListModel.internalValueRole)
                return left_duedate < right_duedate

QTreeView子类

class EventWidget(QTreeView):

    def __init__(self, parent=None):
        super().__init__(parent)

        self.setUniformRowHeights(True)
        self.setRootIsDecorated(False)
        self.setAllColumnsShowFocus(True)
        self.setSortingEnabled(False)
        self.setItemsExpandable(False)
        self.setAutoScroll(True)
        self.setAcceptDrops(False)
        self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        self.setMouseTracking(True)

        self.header().setFirstSectionMovable(True)
        self.header().setStretchLastSection(False)
        self.header().setTextElideMode(Qt.TextElideMode.ElideRight)

    def set_eventlistmodel(self, model: EventListProxyModel) -> bool:
        if model:
            self.setModel(model)
            if len(self.DEFAULT_COLUMN_WIDTH) != self.model().sourceModel().COLUMN_COUNT:
                raise ValueError
            return True
        else:
            return False

    def regain_state_after_model_changes(self):
        self.model().enable_sortfilter(False)
        for row in range(self.model().rowCount()):
            if self.model().index(row, 0, QModelIndex()).data(EventListModel.customSpanRole):
                self.setFirstColumnSpanned(row, QModelIndex(), True)
        self.model().enable_sortfilter(True)

触发重排序的调用代码

# Mainwindow.py
self.ui.lw_term.currentRowChanged.connect(lambda row: self.ui.trw_event.model().set_filter(EventListProxyModel.FILTER_TERM, row))

问题解决(编辑补充)

在Berti先生的帮助下,修改自定义QAbstractItemModel的以下代码后,问题彻底解决:

def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
    if parent.isValid():
        return 0
    return len(self.event_list)

def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
    if parent.isValid():
        return 0
    return self.COLUMN_COUNT

def index(self, row, column, parent: QModelIndex = QModelIndex()) -> QModelIndex:
    return self.createIndex(row, column, QModelIndex())

def parent(self, index: QModelIndex) -> QModelIndex:
    return QModelIndex()

def hasChildren(self, /, parent = ...):
    if parent.isValid():
        return False
    return self.rowCount(parent) > 0 and self.columnCount(parent) > 0

内容的提问来源于stack exchange,提问作者Aleee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 16:05:54