4
  1. 我已经准备好很多关于如何在 python 和 pyqt 中将多个信号连接到同一个事件处理程序的帖子。例如,将多个按钮或组合框连接到同一个功能。

  2. 许多示例展示了如何使用 QSignalMapper 执行此操作,但是当信号带有参数时它不适用,例如 combobox.currentIndexChanged

  3. 许多人建议它可以用 lambda 制作。我同意,这是一个干净而漂亮的解决方案,但没有人提到 lambda 创建了一个包含引用的闭包——因此不能删除被引用的对象。你好内存泄漏!

证明:

from PyQt4 import QtGui, QtCore

class Widget(QtGui.QWidget):
    def __init__(self):
        super(Widget, self).__init__()

        # create and set the layout
        lay_main = QtGui.QHBoxLayout()
        self.setLayout(lay_main)

        # create two comboboxes and connect them to a single handler with lambda

        combobox = QtGui.QComboBox()
        combobox.addItems('Nol Adyn Dwa Tri'.split())
        combobox.currentIndexChanged.connect(lambda ind: self.on_selected('1', ind))
        lay_main.addWidget(combobox)

        combobox = QtGui.QComboBox()
        combobox.addItems('Nol Adyn Dwa Tri'.split())
        combobox.currentIndexChanged.connect(lambda ind: self.on_selected('2', ind))
        lay_main.addWidget(combobox)

    # let the handler show which combobox was selected with which value
    def on_selected(self, cb, index):
        print '! combobox ', cb, ' index ', index

    def __del__(self):
        print 'deleted'

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)

    wdg = Widget()
    wdg.show()

    wdg = None

    sys.exit(app.exec_())

尽管我们清除了引用,但小部件并未被删除。删除与 lambda 的连接 - 它被正确删除。

所以,问题是:在不泄漏内存的情况下,将多个带参数的信号连接到单个处理程序的正确方法是什么?

4

2 回答 2

5

因为信号连接在闭包中持有引用,所以不能删除对象是完全不正确的。Qt 在删除对象时会自动删除所有信号连接,这反过来又会删除对lambdapython 端的引用。

但这意味着您不能总是仅依靠 Python删除对象。每个 PyQt 对象都有两个部分:Qt C++ 部分和 Python 包装器部分。必须删除这两个部分 - 有时以特定顺序删除(取决于 Qt 或 Python 当前是否拥有该对象的所有权)。除此之外,还需要考虑 Python 垃圾收集器的变幻莫测(尤其是在解释器关闭的短时间内)。

无论如何,在您的具体示例中,简单的解决方法是简单地执行以下操作:

    # wdg = None
    wdg.deleteLater()

这会安排删除对象,因此需要一个正在运行的事件循环才能发挥作用。在您的示例中,这也将自动退出应用程序(因为该对象是最后一个关闭的窗口)。

为了更清楚地了解发生了什么,您还可以尝试以下操作:

    #wdg = None
    wdg.deleteLater()

    app.exec_()

    # Python part is still alive here...
    print(wdg)
    # but the Qt part has already gone
    print(wdg.objectName())

输出:

<__main__.Widget object at 0x7fa953688510>
Traceback (most recent call last):
  File "test.py", line 45, in <module>
    print(wdg.objectName())
RuntimeError: wrapped C/C++ object of type Widget has been deleted
deleted

编辑

这是另一个调试示例,希望它更清晰:

    wdg = Widget()
    wdg.show()

    wdg.deleteLater()
    print 'wdg.deleteLater called'

    del wdg
    print 'del widget executed'

    wd2 = Widget()
    wd2.show()

    print 'starting event-loop'
    app.exec_()

输出:

$ python2 test.py
wdg.deleteLater called
del widget executed
starting event-loop
deleted
于 2015-10-23T17:08:19.070 回答
2

在很多情况下,signal 携带的参数可以通过其他方式捕获,例如,如果为发送对象设置了 objectName,则可以使用 QSignalMapper:

    self.signalMapper = QtCore.QSignalMapper(self)
    self.signalMapper.mapped[str].connect(myFunction)  

    self.combo.currentIndexChanged.connect(self.signalMapper.map)
    self.signalMapper.setMapping(self.combo, self.combo.objectName())

   def myFunction(self, identifier):
         combo = self.findChild(QtGui.QComboBox,identifier)
         index = combo.currentIndex()
         text = combo.currentText()
         data = combo.currentData()
于 2015-10-23T19:33:09.240 回答