Python pyqt:将多个信号连接到pyqt中同一函数的正确方法(QSignalMapper不适用)

Python pyqt:将多个信号连接到pyqt中同一函数的正确方法(QSignalMapper不适用),python,garbage-collection,pyqt,signals-slots,qsignalmapper,Python,Garbage Collection,Pyqt,Signals Slots,Qsignalmapper,我已经准备了许多关于如何在python和pyqt中将多个信号连接到同一个事件处理程序的帖子。例如,将多个按钮或组合框连接到同一个函数 许多示例显示了如何使用QSignalMapper执行此操作,但当信号带有参数时,它不适用,例如combobox.currentIndexChanged 很多人建议用兰姆达做。这是一个干净漂亮的解决方案,我同意,但是没有人提到lambda创建了一个闭包,它包含一个引用-因此引用的对象不能被删除。你好,内存泄漏 证明: from PyQt4 import QtGui,

我已经准备了许多关于如何在python和pyqt中将多个信号连接到同一个事件处理程序的帖子。例如,将多个按钮或组合框连接到同一个函数

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

  • 很多人建议用兰姆达做。这是一个干净漂亮的解决方案,我同意,但是没有人提到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的连接-它将被正确删除


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

    因为信号连接在闭包中持有引用,所以不能删除对象是不正确的。当Qt删除一个对象时,它将自动删除所有信号连接,这将依次删除python端对
    lambda
    的引用

    但这意味着您不能总是仅依靠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
    
    $ python2 test.py
    wdg.deleteLater called
    del widget executed
    starting event-loop
    deleted
    
    输出:

    <__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
    
    $ python2 test.py
    wdg.deleteLater called
    del widget executed
    starting event-loop
    deleted
    

    在许多情况下,可以通过另一种方式捕获信号携带的参数,例如,如果为发送对象设置了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()
    

    deleteLater()似乎隐藏了小部件,但仍然没有调用析构函数。在计划删除第一个窗口小部件后添加第二个窗口小部件会显示第二个窗口小部件,但没有删除第一个窗口小部件的迹象。与前面一样,删除连接可以解决问题。wdg.deleteLater()wdg2=Widget()wdg2.move(300100)wdg2.show()@grigorimakeev。不,根本不是这样。显然,Python包装不会立即被删除,因为您仍然持有对它的全局引用。但是你需要做的是<代码> DEWDG < /C>,并且一旦QT删除C++部分,将调用<代码>我已经在我的答案中添加了另一个调试示例,它应该可以更清楚地显示实际发生的情况。确实,它现在正在工作,谢谢!只有一件事我还不清楚:如果我在del wdg之后添加gc.collect(),析构函数仍然不会被调用。知道为什么吗?@GrigoryMakeev。有关
    \uuu del\uu
    ,请参阅。引用仍然由Qt持有,因此python垃圾收集器必须等待
    deleteLater
    事件处理完毕。在我的调试示例中,我将print语句放在事件循环之前,以明确只有在事件处理开始后才会调用
    \uu del\uu
    。所以QT首先删除C++部分,然后允许垃圾回收器删除Python部分。是的,谢谢,这是我们目前使用的解决方案。基本上,它只强调了这样一个事实:在本例中,我们无法捕获参数信号,因此应该尝试以其他方式推断它,在本例中,使用combo.currentIndex()。只有我们使用self.signalMapper.mapped[QtCore.QWidget]表单,所以我们不必使用findChild。