计算机系统应用教程网站

网站首页 > 技术文章 正文

实战PyQt5: 079-如何彻底删除布局中一个部件

btikc 2024-10-24 09:31:27 技术文章 7 ℃ 0 评论

问题的提出

在Qt的GUI布局中,有时候需要彻底删除一个部件,释放其占用的内存,比如在一个网格布局中,在不影响布局的情况下,需要删除其中的某个部件,我们调用removeWidget()函数将其从布局中删除,这种操作下,虽然在界面上看不到了被移除的部件,但这并不说明,部件已经被删除,所占用的内存已经被释放。实际上,这些部件并没有被删除,占用的内存并没有释放。在Python里,可以采用以下三种方法来达到目的。

方法1:使用部件的deleteLater的函数

在Qt中,所有继承自QObject的类都有一个deleteLater函数,用于在适当的时候销毁对象自身。其原理是QObject.deleteLater()并不立即将对象销毁,而是向主循环发送了-个事件(event),在主循环收到这个事件后,让所有事件都发送完一切处理好后才销毁对象,释放相应的内存。这样做的好处是可以在这些延迟删除的时间内完成一些操作,而且就算调用多次的deletelater也是安全的。坏处就是内存释放会不及时。

方法2:使用sip.delete函数删除部件

导入sip库

import PyQt5.sip

使用其delete函数来删除部件对象

sip.delete(widget)

这种方法和C++中使用delete删除对象的原理是一样的。

这种方法的好处是,立即删除对象和释放内存,坏处是,可能会导致不可预知的崩溃现象。

Qt中不建议使用手动使用delete来释放QObject对象,原因有二:

  • 不注意父子关系可能或导致某个对象释放两次,一次是手动释放,一次是parent释放。
  • 删除一个pending events等待传递的QObject会导致崩溃,所以不能直接跨线程删除对象,而QObject析构函数会断开所有信号和槽,因此用deleteLater代替比较好,它会让所有事件都发送完一切处理好后马上清除这片内存,而且就算调用多次的deletelater也是安全的。

如果没有上述两种原因存在,可以直接使用delete来删除对象和释放内存空间。

方法3:使用del函数

python提供了del 删除变量的方式,del 删除变量的原理是,它使变量的引用计数减一,如果引用计数为0,就会被回收。这样也可以达到目的,其使用方式同方法2,效果和方法2也是一样的。

测试代码

测试代码演示使用按钮,添加一些标签,可以选择用两种不同的方式来删除添加的标签,右边打印出操作后的信息,可以看到delteLater 函数并不马上执行删除并释放空间的操作,而sip.delete则马上执行删除操作并释放组件所占用的内存空间。完整测试代码如下:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import sip
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, 
                             QGridLayout, QHBoxLayout, QVBoxLayout, QSplitter,
                             QPushButton, QLabel, QPlainTextEdit)
 
class DemoWidgetDelete(QMainWindow):
    def __init__(self, parent=None):
        super(DemoWidgetDelete, self).__init__(parent)   
        
         # 设置窗口标题
        self.setWindowTitle('实战PyQt5: 演示彻底删除部件')      
        # 设置窗口大小
        self.resize(480, 300)
      
        self.initUi()
        
    def initUi(self):
        
        mainLayout = QVBoxLayout()
               
        self.labelList = []
        
        mainWidget = QWidget()
        mainWidget.setLayout(mainLayout)
        self.setCentralWidget(mainWidget)
        
        #三个按钮
        btnAdd = QPushButton('添加')
        btnAdd.clicked.connect(self.onButtonAdd)
        btnRemove1 = QPushButton('移除(deleteLater)')
        btnRemove1.clicked.connect(self.onButtonRemove1)
        btnRemove2 = QPushButton('移除(sip.delete)')
        btnRemove2.clicked.connect(self.onButtonRemove2)
        hLayout = QHBoxLayout()
        hLayout.addWidget(btnAdd)
        hLayout.addWidget(btnRemove1)
        hLayout.addWidget(btnRemove2)
        
        self.gLayout = QGridLayout()
        self.gLayout.setSpacing(10)
        widLeft = QWidget()
        widLeft.setLayout(self.gLayout)
        
        self.txtShow = QPlainTextEdit()
        splitter = QSplitter()
        splitter.addWidget(widLeft)
        splitter.addWidget(self.txtShow)
        splitter.addWidget(self.txtShow)
        splitter.setStretchFactor(0, 1)
 
        mainLayout.addLayout(hLayout)
        mainLayout.addWidget(splitter)
        #mainLayout.addStretch(Qt.Vertical)
        
    def onButtonAdd(self):
        addPos = self.gLayout.count() + 1
        addNum = len(self.labelList)
        self.labelList.append(QLabel('标签 ' + str(addPos)))
        self.gLayout.addWidget(self.labelList[addNum], addPos, 0)
    
    #方法1
    def onButtonRemove1(self):
        self.txtShow.clear()
        self.txtShow.appendPlainText('deleteLater:')
        for idx in range(self.gLayout.count()+1, 1, -1):
            #print(self.gLayout.rowCount(), len(self.gLayout), self.gLayout.count())
            self.txtShow.appendPlainText('{},{},{}'.format(self.gLayout.rowCount(), len(self.gLayout), self.gLayout.count()))
            
            wid = self.gLayout.itemAtPosition(idx-1, 0).widget()
            wid.deleteLater()
            
        #尝试继续访问
        for idx in range (self.gLayout.count()+1, 1, -1):
            #print(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size())
            self.txtShow.appendPlainText('{},{}'.format(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size()))
    
    #方法2
    def onButtonRemove2(self):
        self.txtShow.clear()
        self.txtShow.appendPlainText('sip.delete:')
        for idx in range(self.gLayout.count()+1, 1, -1):
            #print(self.gLayout.rowCount(), len(self.gLayout), self.gLayout.count())
            self.txtShow.appendPlainText('{},{},{}'.format(self.gLayout.rowCount(), len(self.gLayout), self.gLayout.count()))
            
            wid = self.gLayout.itemAtPosition(idx-1, 0).widget()     
            self.gLayout.removeWidget(wid)
            sip.delete(wid)
                   
        #尝试继续访问
        for idx in range (self.gLayout.count()+1, 1, -1):
            #print(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size())
            self.txtShow.appendPlainText('{},{}'.format(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size()))
    
    
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DemoWidgetDelete()
    window.show()
    sys.exit(app.exec())

运行结果如下图:

本文知识点

  • 彻底释放Qt对象的三种方法;
  • deleteLater的优点和缺点;
  • 什么时候可以使用delete来删除部件,释放空间;
  • 使用del来删除部件。

喜欢本文内容就关注, 收藏,点赞,评论和转发。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表