我想了解一下Python和PyQt的垃圾收集器是如何工作的。在下面的示例中,我创建了一个具有python属性'x‘的QWidget (名为TestWidget)。我创建TestWidget,与之交互,然后关闭它的窗口。由于我已经设置了WA_DeleteOnClose,这应该会通知Qt事件循环销毁我的TestWidget实例。与我预期的相反,在这一点上(甚至在事件循环结束之后),TestWidget().x引用的python对象仍然存在。
我正在用PyQt创建一个应用程序,用户可以在其中打开和关闭许多许多小部件。每个小部件都具有占用大量内存的属性。因此,当用户关闭它时,我想对这个小部件及其属性进行垃圾收集( Qt和Python)。我尝试过覆盖closeEvent和deleteEvent,但没有成功。
有人能给我指个方向吗?谢谢!示例代码如下:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit
from PyQt5.QtCore import Qt
class TestWidget(QWidget):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.setAttribute(Qt.WA_DeleteOnClose)
self.widget = None
self.x = '1' * int(1e9)
def load(self):
layout = QVBoxLayout(self)
self.widget = QTextEdit(parent=self)
layout.addWidget(self.widget)
self.setLayout(layout)
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
import gc
app = QApplication([])
widgets = []
widgets.append(TestWidget(parent=None))
widgets[-1].load()
widgets[-1].show()
widgets[-1].activateWindow()
app.exec()
print(gc.get_referrers(gc.get_referrers(widgets[-1].x)[0]))发布于 2021-03-17 10:08:06
重要的是要记住,PyQt是一个绑定,任何引用在Qt ( "C++端“)上创建的对象的python对象都只是一个包装器。
WA_DeleteOnClose只销毁实际的QWidget,而不是它的python对象( TestWidget实例)。
在您的示例中,发生的情况是Qt释放了小部件,但是在Python端(列表中的元素)上仍然有一个引用:当执行最后一行时,widgets列表及其内容仍然存在于该作用域中。
实际上,您可以尝试在结尾处添加以下内容:
print(widgets[-1].objectName())您将得到以下异常:
Exception "unhandled RuntimeError"
wrapped C/C++ object of type TestWidget has been deleted当python对象也被删除时,它的所有属性显然也被删除了。
要澄清这一点,请参阅以下内容:
class Attribute(object):
def __del__(self):
print('Deleting Attribute...')
class TestWidget(QWidget):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.setAttribute(Qt.WA_DeleteOnClose)
self.widget = None
self.x = Attribute()
def load(self):
layout = QVBoxLayout(self)
self.widget = QTextEdit(parent=self)
layout.addWidget(self.widget)
self.setLayout(layout)
def __del__(self):
print('Deleting TestWidget...')您将看到,在任何情况下,您的代码都不会调用__del__。
如果您添加del widgets[-1],则会发生实际删除。
发布于 2021-03-17 10:24:19
解释
要了解问题,您必须了解以下概念:
仅当
考虑到上面的情况,python标志所做的是消除C++对象的内存,而不是消除python对象的内存。
要了解内存是如何处理的,您可以使用带有以下代码的memory-profiler工具:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit
from PyQt5.QtCore import Qt, QTimer
from PyQt5 import sip
class TestWidget(QWidget):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.setAttribute(Qt.WA_DeleteOnClose)
self.widget = None
self.x = ""
period = 1000
QTimer.singleShot(4 * period, self.add_memory)
QTimer.singleShot(8 * period, self.close)
QTimer.singleShot(12 * period, QApplication.quit)
def load(self):
layout = QVBoxLayout(self)
self.widget = QTextEdit()
layout.addWidget(self.widget)
def add_memory(self):
self.x = "1" * int(1e9)
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication
import gc
app = QApplication([])
app.setQuitOnLastWindowClosed(False)
widgets = []
widgets.append(TestWidget(parent=None))
widgets[-1].load()
widgets[-1].show()
widgets[-1].activateWindow()
app.exec()

在前面的代码中,在第4秒中创建了"x“的内存,在第8秒中消除了C++对象,但没有消除与"x”相关联的内存,并且这只在程序关闭时才被消除,因为它清除了列表,从而清除了python对象引用。
解决方案
在这种情况下,一种可能的解决方案是使用删除C++对象时发出的销毁信号来删除对该python对象的所有引用:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit
from PyQt5.QtCore import Qt, QTimer
from PyQt5 import sip
class TestWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.widget = None
self.x = ""
period = 1000
QTimer.singleShot(4 * period, self.add_memory)
QTimer.singleShot(8 * period, self.close)
QTimer.singleShot(12 * period, QApplication.quit)
def load(self):
layout = QVBoxLayout(self)
self.widget = QTextEdit()
layout.addWidget(self.widget)
def add_memory(self):
self.x = "1" * int(1e9)
class Manager:
def __init__(self):
self._widgets = []
@property
def widgets(self):
return self._widgets
def add_widget(self, widget):
self._widgets.append(widget)
widget.destroyed.connect(self.handle_destroyed)
def handle_destroyed(self):
self._widgets = [widget for widget in self.widgets if not sip.isdeleted(widget)]
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication
app = QApplication([])
app.setQuitOnLastWindowClosed(False)
manager = Manager()
manager.add_widget(TestWidget())
manager.widgets[-1].load()
manager.widgets[-1].show()
manager.widgets[-1].activateWindow()
app.exec()

https://stackoverflow.com/questions/66665850
复制相似问题