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

如何加速含大量子部件的PySide窗口创建速度?

PySide窗口创建耗时过长的优化方案

问题描述

我遇到创建PySide窗口耗时过长(可达数秒)的问题:该窗口包含大量子部件,每个子部件基于QWidget且由多个小部件组成。测试显示,创建含100个子部件的窗口约需0.6秒,1000个则约6秒,耗时随子部件数量线性增长。请问有哪些可大幅或小幅降低窗口创建时间的优化技巧?能否通过pickle序列化部件,实现一次创建后多次复用?

复现代码

import sys
import time
from PySide2 import QtWidgets
from PySide2 import QtCore

# Adjust this number to increase/ decrease the number of sub widgets being
# created.
NUMBER_ELEMENTS = 1000

class Main(QtWidgets.QWidget):
    def __init__(self):
        super(Main, self).__init__()
        self.setMinimumSize(QtCore.QSize(1000, 700))
        self.table = QtWidgets.QTableWidget()
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.table)
        self.setLayout(self.layout)
        self.table.setColumnCount(1)
        self.table.setRowCount(NUMBER_ELEMENTS)
        for row in range(NUMBER_ELEMENTS):
            widget = SubWidget()
            self.table.setCellWidget(row, 0, widget)
        self.table.resizeColumnsToContents()

class SubWidget(QtWidgets.QWidget):
    def __init__(self):
        super(SubWidget, self).__init__()
        self.label_name = QtWidgets.QLabel("My name")
        self.label_format = QtWidgets.QLabel("my format")
        self.label_frames = QtWidgets.QLabel("XXX")
        self.label_type = QtWidgets.QLabel("some type")
        self.label_size = QtWidgets.QLabel("some size")
        self.push_favorite = QtWidgets.QPushButton("hello")
        self.label_comment = QtWidgets.QTextEdit("something")
        self.label_comment.setMaximumHeight(80)
        self.label_comment.setProperty("style", "comment")
        self.video = QtWidgets.QLabel("my video")
        self.layout_main = QtWidgets.QHBoxLayout()
        self.layout_main.addWidget(self.video, 17)
        self.layout_main.addWidget(self.label_name, 20)
        self.layout_main.addWidget(self.label_format, 10)
        self.layout_main.addWidget(self.label_frames, 10)
        self.layout_main.addWidget(self.label_type, 10)
        self.layout_main.addWidget(self.label_size, 10)
        self.layout_main.addWidget(self.push_favorite, 15)
        self.layout_main.addWidget(self.label_comment, 50)
        self.setLayout(self.layout_main)

if __name__ == "__main__":
    start = time.time()
    app = QtWidgets.QApplication(sys.argv)
    main_window = Main()
    main_window.raise_()
    main_window.show()
    print "Duration to build window: ", time.time() - start
    app.exec_()

解决方案

先聊pickle的问题——这条路走不通

很明确地说:Qt的QWidget对象不能用pickle序列化。因为每个QWidget都绑定了系统底层的窗口资源(比如窗口句柄、绘图上下文),这些资源是无法被序列化存储的。哪怕你强行尝试,要么直接报错,要么反序列化出来的部件完全没法正常工作,甚至会搞崩程序。

如果想复用部件的配置,可以写个工厂函数,提前定义好SubWidget的所有属性,每次需要的时候调用函数生成新实例,这比序列化靠谱多了。

优化技巧:从大幅到小幅分等级来

1. 用QStyledItemDelegate替代CellWidget(大幅提速)

你现在用的setCellWidget其实是性能最差的方式——每个单元格都是一个独立的QWidget,1000行就是1000个完整的Widget实例,开销拉满。换成QStyledItemDelegate自定义绘制单元格内容,能把创建时间砍到原来的1/10甚至更低。

原理是Delegate复用一套绘制逻辑,直接把内容画到单元格上,而不是为每个cell创建独立部件。你可以在Delegate的paint方法里绘制所有标签、按钮的样式,通过editorEvent处理按钮点击、文本编辑这些交互。虽然需要写点自定义绘制的代码,但性能提升是质的飞跃。

2. 禁用批量创建时的自动更新(小幅但见效快)

Qt在创建和添加部件时,会自动触发布局计算和界面重绘,批量操作时这些重复计算会浪费很多时间。你可以临时关掉表格的自动更新,等所有部件都创建好再打开:

# 在循环创建子部件前加上
self.table.setUpdatesEnabled(False)

# 循环结束后恢复
self.table.setUpdatesEnabled(True)
self.table.resizeColumnsToContents()

这个小改动能帮你把1000行的创建时间从6秒降到4-5秒左右,几乎不需要改太多代码。

3. 延迟加载/懒加载(初始耗时直接砍到接近0)

如果用户不需要一开始就看到所有1000行,可以只创建当前可见区域的部件,滚动时再动态加载:

  • 监听表格的滚动事件,计算当前可见的行范围
  • 只给可见行设置CellWidget,不可见的行把之前的部件销毁掉(或者缓存起来)
  • 这种方式下,初始窗口创建时间会从几秒变成几十毫秒,用户体验直接拉满

4. 优化SubWidget本身的开销(小幅累加)

  • 减少部件数量:比如把几个静态标签合并成一个QLabel,用HTML格式排版(比如<b>My name</b> | my format),少创建几个QLabel实例
  • 固定部件尺寸:给SubWidget和内部的所有部件设置固定大小(比如self.setFixedHeight(100)),避免Qt反复计算布局
  • 延迟非必要操作:把一些非初始化必须的逻辑(比如设置样式、加载非默认内容)延迟到部件第一次显示时再执行,不要堆在__init__里

优化后的代码示例

这里给你改了Main类,加上了禁用自动更新的优化,同时给SubWidget加了固定高度:

import sys
import time
from PySide2 import QtWidgets
from PySide2 import QtCore

NUMBER_ELEMENTS = 1000

class Main(QtWidgets.QWidget):
    def __init__(self):
        super(Main, self).__init__()
        self.setMinimumSize(QtCore.QSize(1000, 700))
        self.table = QtWidgets.QTableWidget()
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.table)
        self.setLayout(self.layout)
        self.table.setColumnCount(1)
        self.table.setRowCount(NUMBER_ELEMENTS)
        
        # 优化:禁用表格自动更新,避免重复重绘
        self.table.setUpdatesEnabled(False)
        
        for row in range(NUMBER_ELEMENTS):
            widget = SubWidget()
            self.table.setCellWidget(row, 0, widget)
        
        # 恢复更新并调整列宽
        self.table.setUpdatesEnabled(True)
        self.table.resizeColumnsToContents()

class SubWidget(QtWidgets.QWidget):
    def __init__(self):
        super(SubWidget, self).__init__()
        # 优化:固定高度,减少布局计算
        self.setFixedHeight(100)
        
        self.label_name = QtWidgets.QLabel("My name")
        self.label_format = QtWidgets.QLabel("my format")
        self.label_frames = QtWidgets.QLabel("XXX")
        self.label_type = QtWidgets.QLabel("some type")
        self.label_size = QtWidgets.QLabel("some size")
        self.push_favorite = QtWidgets.QPushButton("hello")
        self.label_comment = QtWidgets.QTextEdit("something")
        self.label_comment.setMaximumHeight(80)
        self.label_comment.setProperty("style", "comment")
        self.video = QtWidgets.QLabel("my video")
        
        self.layout_main = QtWidgets.QHBoxLayout()
        self.layout_main.addWidget(self.video, 17)
        self.layout_main.addWidget(self.label_name, 20)
        self.layout_main.addWidget(self.label_format, 10)
        self.layout_main.addWidget(self.label_frames, 10)
        self.layout_main.addWidget(self.label_type, 10)
        self.layout_main.addWidget(self.label_size, 10)
        self.layout_main.addWidget(self.push_favorite, 15)
        self.layout_main.addWidget(self.label_comment, 50)
        self.setLayout(self.layout_main)

if __name__ == "__main__":
    start = time.time()
    app = QtWidgets.QApplication(sys.argv)
    main_window = Main()
    main_window.raise_()
    main_window.show()
    print("Duration to build window: ", time.time() - start)
    app.exec_()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 09:27:38