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

PyQt5如何仅加载一次UI文件到内存实现复用?

问题:缓存UI文件到内存以重复加载,避免重复磁盘读取

我需要按需生成QGroupBox控件,为降低磁盘负载、节省时间,希望将dataholder.ui文件首次从磁盘加载后存入内存,后续直接从内存加载。此前尝试在__init__方法中将实例保存到静态变量,出现AttributeError: 'ViewContainer' object has no attribute 'tr'异常,目前只能每次从磁盘加载该UI文件。

项目相关文件

main.py

from PyQt5.QtWidgets import QApplication
import traceback
import sys
from mainwindow import MainWindow


def main() -> None:
    app = QApplication(sys.argv)
    mainwindow = MainWindow()
    mainwindow.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    try:
        main()
    except Exception:
        print(traceback.format_exc())
        sys.exit(-1)

mainwindow.py

from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QWidget
from ViewContainer import ViewContainer
from PyQt5.uic import loadUi
from PyQt5.QtCore import Qt


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        loadUi("main.ui", self)

        top_widget = QWidget()
        lay = QVBoxLayout(self)
        lay.addWidget(self.scrollArea)
        self._top_layout = QVBoxLayout()

        top_widget.setLayout(self._top_layout)
        self.scrollArea.setWidget(top_widget)
        # self.setCentralWidget(self.scrollArea)
        self.scrollArea.setWidgetResizable(True)

        data = ["1", "2"]

        for row in data:
            group_box = ViewContainer("dataholder.ui")
            group_box.id = row
            self._top_layout.addWidget(group_box, alignment=Qt.AlignCenter)

Container.py

from PyQt5.QtWidgets import QGroupBox
from abc import abstractmethod


class Container(QGroupBox):
    def __init__(self, height: int = 250, width: int = 800) -> None:
        super(Container, self).__init__()

        self._width = width
        self._height = height

        self._id = -1

        self.setFixedSize(self._width, self._height)

    @property
    def id(self) -> int:
        return self._id

    @id.setter
    def id(self, value: int) -> None:
        self._id = value

    @abstractmethod
    def _loadUi(self):
        raise NotImplementedError

ViewContainer.py

from PyQt5.uic import loadUi

from Container import Container


class ViewContainer(Container):
    def __init__(self, file_name: str) -> None:
        super().__init__()
        self._file_name = file_name

        self._loadUi()

    def _loadUi(self):
        ui_file = self._file_name

        loadUi(ui_file, self)

main.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>965</width>
    <height>771</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QScrollArea" name="scrollArea">
    <property name="geometry">
     <rect>
      <x>60</x>
      <y>90</y>
      <width>851</width>
      <height>600</height>
     </rect>
    </property>
    <property name="sizePolicy">
     <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
      <horstretch>0</horstretch>
      <verstretch>0</verstretch>
     </sizepolicy>
    </property>
    <property name="sizeAdjustPolicy">
     <enum>QAbstractScrollArea::AdjustToContents</enum>
    </property>
    <property name="widgetResizable">
     <bool>true</bool>
    </property>
    <widget class="QWidget" name="scrollAreaWidgetContents">
     <property name="geometry">
      <rect>
       <x>0</x>
       <y>0</y>
       <width>849</width>
       <height>598</height>
      </rect>
     </property>
    </widget>
   </widget>
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>840</x>
      <y>700</y>
      <width>75</width>
      <height>23</height>
     </rect>
    </property>
    <property name="text">
     <string>Einfügen</string>
    </property>
   </widget>
   <widget class="QWidget" name="gridLayoutWidget">
    <property name="geometry">
     <rect>
      <x>60</x>
      <y>0</y>
      <width>641</width>
      <height>80</height>
     </rect>
    </property>
    <layout class="QGridLayout" name="gridLayout">
     <item row="2" column="1">
      <widget class="QComboBox" name="comboBox_2"/>
     </item>
     <item row="1" column="1">
      <widget class="QComboBox" name="comboBox"/>
     </item>
     <item row="1" column="2">
      <widget class="QComboBox" name="comboBox_3"/>
     </item>
     <item row="2" column="2">
      <widget class="QComboBox" name="comboBox_4"/>
     </item>
    </layout>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>965</width>
     <height>21</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuOptionen">
    <property name="title">
     <string>Optionen</string>
    </property>
    <addaction name="actionInsert"/>
   </widget>
   <addaction name="menuOptionen"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <action name="actionInsert">
   <property name="text">
    <string>Einfügen</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>

dataholder.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>GroupBox</class>
 <widget class="QGroupBox" name="GroupBox">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>790</width>
    <height>281</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>GroupBox</string>
  </property>
  <widget class="QWidget" name="verticalLayoutWidget_2">
   <property name="geometry">
    <rect>
     <x>220</x>
     <y>220</y>
     <width>158</width>
     <height>25</height>
    </rect>
   </property>
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="QPushButton" name="btnedit">
      <property name="text">
       <string>Edit</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="btnDelete">
      <property name="text">
       <string>Delete</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QLabel" name="lblImage">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>40</y>
     <width>171</width>
     <height>131</height>
    </rect>
   </property>
   <property name="text">
    <string>TextLabel</string>
   </property>
  </widget>
  <widget class="QWidget" name="horizontalLayoutWidget">
   <property name="geometry">
    <rect>
     <x>220</x>
     <y>20</y>
     <width>361</width>
     <height>171</height>
    </rect>
   </property>
   <layout class="QHBoxLayout" name="horizontalLayout_2">
    <item>
     <layout class="QVBoxLayout" name="verticalLayout_2">
      <item>
       <widget class="QLabel" name="label_2">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_3">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_4">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_5">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_6">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_7">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_8">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
    <item>
     <layout class="QVBoxLayout" name="verticalLayout">
      <item>
       <widget class="QLabel" name="lblDescription">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="lblSize">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="lblTotalStock">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="lblStorageStock">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="lblUnitPrice">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="lblTotalPrice">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="lblShop">
        <property name="text">
         <string>TextLabel</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

解决方案:缓存UI文件的XML内容,而非控件实例

之前的错误在于试图缓存控件实例,但Qt控件实例绑定了特定上下文,无法重复加载到多个对象上。正确做法是缓存UI文件的原始XML内容,后续从内存流读取,避免重复磁盘IO。

修改后的ViewContainer.py

from PyQt5.uic import loadUi
from io import BytesIO
from Container import Container

class ViewContainer(Container):
    # 静态字典缓存UI文件字节内容
    _ui_cache = {}

    def __init__(self, file_name: str) -> None:
        super().__init__()
        self._file_name = file_name
        self._loadUi()

    def _loadUi(self):
        if self._file_name not in ViewContainer._ui_cache:
            # 首次读取文件并缓存
            with open(self._file_name, 'rb') as f:
                ViewContainer._ui_cache[self._file_name] = f.read()
        
        # 从内存字节流加载UI
        ui_stream = BytesIO(ViewContainer._ui_cache[self._file_name])
        loadUi(ui_stream, self)

错误原因说明

控件实例的tr方法和自身QObject上下文绑定,复用已加载的实例会导致上下文混乱,触发属性缺失异常。而缓存原始文件内容,每次创建新实例时从内存加载,能保证每个控件都有独立且正确的上下文。

额外优化:预加载缓存

如果UI文件不会动态修改,可在程序启动时提前加载缓存,减少首次创建控件的磁盘IO:

# 在main.py的main函数开头添加
from ViewContainer import ViewContainer
with open("dataholder.ui", 'rb') as f:
    ViewContainer._ui_cache["dataholder.ui"] = f.read()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 03:20:29