如果我要在 PyQt 小部件上调用 deleteLater,是否需要手动断开信号?

问题描述 投票:0回答:1

我有一个 QMainWidnow 会生成许多不同的对话框,在删除对话框之前我已经手动断开了所有信号。这是必要的吗,还是它们被垃圾收集器或 PyQt 后端清理掉了?

class AlignmentStationInterface(QMainWindow):
    def __init__(self)
        super().__init__()
        add_button = QPushButton("Add Position")
        add_button.clicked.connect(self.add_position)
        self.position_builder_dialog = None

    def add_position(self):
        print('write new location')
        if self.new_position_builder_popup is not None:
            return
        self.position_builder_dialog = PositionBuilderDialog()
        self.position_builder_dialog.new_position_return.connect(self.save_position)
        self.position_builder_dialog.finished.connect(self.cleanup_dialog)

    def cleanup_dialog(self):
        # Are these disconnect lines necessary?
        self.position_builder_dialog.new_position_return.disconnect(self.save_position)
        self.position_builder_dialog.finished.disconnect(self.cleanup_dialog)
        self.position_builder_dialog.deleteLater()

    def save_position(self):
        pass
        

我希望后端能处理好事情,但我担心内存泄漏。

python user-interface pyqt5 signals-slots
1个回答
0
投票

理论上没有必要,但也要看情况。

信号通常会在其某一部分被破坏时自动断开:只要信号连接到被破坏的 QObject 的函数,它们通常会在删除时自动断开。

但是您需要记住,使用 PyQt(和 PySide)等 Python 绑定最终总会得到两个对象:“Python 对象”和“Qt 对象”(存在于“C++ 世界”中的对象) .

这意味着Python对象和Qt对象可以有不同的生命周期:

  • Python 对象可能会被垃圾回收,因为它的引用计数已降至 0,但其 C++ 对象仍然可能存在(通常,因为它有一个 QObject 父对象);
  • 同样,C++ 对象可能会被销毁,但 Python 引用仍然存在并且理论上仍然有效;

您的代码是示例性的:您正在创建对 Qt 包装对象的 python 引用,但是

deleteLater()
只会销毁 C++ 对应项,因为
self.position_builder_dialog.deleteLater()
不会删除 Python 解释器中的 self.position_builder_dialog
 对象。

事实上,如果在调用

cleanup_dialog()之后再次点击按钮(依次调用add_position()

),不会发生任何事情,因为
self.new_position_builder_popup is not None
。虽然 C++ 对象已被销毁,但 Python 引用仍然存在。
理论上,即使对话框实际被销毁(使用 
deleteLater()

),您仍然可以调用

PositionBuilderDialog

 的方法,只要它们不依赖于与该对话框严格相关的 Qt 函数。那是因为这些方法存在于 Python 命名空间中,并且它们仍然“有意义”。
让我们假设以下基本示例:

class PositionBuilderDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.value = '' self.lineEdit = QLineEdit() layout = QVBoxLayout(self) layout.addWidget(self.lineEdit) self.lineEdit.textChanged.connect(self.edited) def edited(self, text): self.value = text

然后考虑以下情况:
class AlignmentStationInterface(QMainWindow):
    ...
    def add_position(self):
        print('write new location')
        if self.new_position_builder_popup is not None:
            print(self.new_position_builder_popup.value)
            print(self.new_position_builder_popup.lineEdit)
            print(self.new_position_builder_popup.lineEdit.text())
            return
        ...

单击按钮
一次
并且对话框已关闭(然后在

cleanup_dialog()中删除)后,单击按钮将执行以下操作:

进入

if
    块,因为
  1. new_position_builder_popup
    不是
    None
    打印对话框的内容 
    value
  2. ;
  3. 打印对 
    lineEdit
  4. 对象的 Python 引用;
  5. 崩溃
  6. ,因为
  7. text()会尝试调用已不存在的包装C++对象的相关函数;
    在您的情况下,您不需要断开信号,但您
  8. 确实
需要实际删除Python引用:

def cleanup_dialog(self): self.position_builder_dialog.deleteLater() self.position_builder_dialog = None

原则上,通常需要调用
deleteLater()
,但通常建议确保所有 Python 引用也被删除。前者实际上会删除

real

Qt对象(这将释放程序正在使用的大部分内存),而后者通常主要用于
reliable代码:在担心内存泄漏之前,您应该担心对象引用的使用。 请注意,在某些情况下(尤其是使用 lambda 时),信号连接可能会创建进一步的闭包,这意味着即使理论上 Python 对象已被销毁,它们仍然可能存在。请记住,在处理绑定中的包装对象时,您永远不

完全依赖垃圾收集,并且对象的生命周期可能完全不一致。

根据上面的内容,采取以下代码:

def add_position(self): dialog = PositionBuilderDialog(self) # note the "self" argument dialog.show()

在 Python 世界中,这会自动进行垃圾收集
dialog
。尽管如此,该对话框仍会毫无问题地显示。这是因为该对话框是使用父级创建的,这确保了只要父级对话框存在,它就始终保持活动状态。 Qt 对 Python 一无所知,它只知道对话框有一个父级,然后它不会销毁它,直到该父级出现为止。

© www.soinside.com 2019 - 2024. All rights reserved.