首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >PyQt5内存管理

PyQt5内存管理
EN

Stack Overflow用户
提问于 2021-03-17 09:43:43
回答 2查看 265关注 0票数 1

我想了解一下Python和PyQt的垃圾收集器是如何工作的。在下面的示例中,我创建了一个具有python属性'x‘的QWidget (名为TestWidget)。我创建TestWidget,与之交互,然后关闭它的窗口。由于我已经设置了WA_DeleteOnClose,这应该会通知Qt事件循环销毁我的TestWidget实例。与我预期的相反,在这一点上(甚至在事件循环结束之后),TestWidget().x引用的python对象仍然存在。

我正在用PyQt创建一个应用程序,用户可以在其中打开和关闭许多许多小部件。每个小部件都具有占用大量内存的属性。因此,当用户关闭它时,我想对这个小部件及其属性进行垃圾收集( Qt和Python)。我尝试过覆盖closeEvent和deleteEvent,但没有成功。

有人能给我指个方向吗?谢谢!示例代码如下:

代码语言:javascript
复制
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]))
EN

回答 2

Stack Overflow用户

发布于 2021-03-17 10:08:06

重要的是要记住,PyQt是一个绑定,任何引用在Qt ( "C++端“)上创建的对象的python对象都只是一个包装器。

WA_DeleteOnClose只销毁实际的QWidget,而不是它的python对象( TestWidget实例)。

在您的示例中,发生的情况是Qt释放了小部件,但是在Python端(列表中的元素)上仍然有一个引用:当执行最后一行时,widgets列表及其内容仍然存在于该作用域中。

实际上,您可以尝试在结尾处添加以下内容:

代码语言:javascript
复制
    print(widgets[-1].objectName())

您将得到以下异常:

代码语言:javascript
复制
Exception "unhandled RuntimeError"
wrapped C/C++ object of type TestWidget has been deleted

当python对象也被删除时,它的所有属性显然也被删除了。

要澄清这一点,请参阅以下内容:

代码语言:javascript
复制
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],则会发生实际删除。

票数 2
EN

Stack Overflow用户

发布于 2021-03-17 10:24:19

解释

要了解问题,您必须了解以下概念:

仅当

  • Python对象不再具有引用时,才会删除这些对象,例如,在您的示例中,当将小部件添加到列表中时,会创建一个引用;另一个示例是,如果您创建了类的属性,则会创建一个新引用。

  • PyQt (以及PySide)是Qt库的包装器(有关更多信息,请参见1 ),也就是说,当您从python访问QFoo类的对象时,您访问的不是C++对象,而是该对象的句柄。由于这个原因,Qt创建的所有内存逻辑都由Qt处理,但开发人员创建的那些必须由他自己处理。

考虑到上面的情况,python标志所做的是消除C++对象的内存,而不是消除python对象的内存。

要了解内存是如何处理的,您可以使用带有以下代码的memory-profiler工具:

代码语言:javascript
复制
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对象的所有引用:

代码语言:javascript
复制
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()

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66665850

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档