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
相关产品推荐
相关产品推荐

