为什么pyqtSlot装饰器会导致“TypeError: connect() failed”错误?

9
我有一个使用Python 3.5.1和PyQt5的程序,其中包含一个由QtCreator ui文件创建的GUI。在这个程序中,pyqtSlot装饰器导致了"TypeError: connect() failed between textChanged(QString) and edited()"的错误。
为了重现问题,我在示例代码中定义了两个自定义类:MainApp和LineEditHandler。MainApp实例化了主GUI(从文件"mainwindow.ui"中读取),而LineEditHandler处理一个QLineEdit对象。LineEditHandler的存在是为了将与QLineEdit对象相关的自定义方法集中到一个类中。它的构造函数需要一个QLineEdit对象和MainApp实例(以便在需要时访问其他对象/属性)。
在MainApp中,我将QLineEdit的textChanged信号连接到LineEditHandler的edited()方法。如果我不使用pyqtSlot()装饰LineEditHandler的edited()方法,一切都正常工作。但是,如果我使用@pyqtSlot()装饰该方法,代码运行将失败,并显示"TypeError: connect() failed between textChanged(QString) and edited()"。我在这里做错了什么?
您可以在此处获取mainwindow.ui文件:https://drive.google.com/file/d/0B70NMOBg3HZtUktqYVduVEJBN2M/view 这是生成问题的示例代码:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSlot


Ui_MainWindow, QtBaseClass = uic.loadUiType("mainwindow.ui")


class MainApp(QMainWindow, Ui_MainWindow):

    def __init__(self):
        # noinspection PyArgumentList
        QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)

        # Instantiate the QLineEdit handler.
        self._line_edit_handler = LineEditHandler(self, self.lineEdit)
        # Let the QLineEdit handler deal with the QLineEdit textChanged signal.
        self.lineEdit.textChanged.connect(self._line_edit_handler.edited)


class LineEditHandler:

    def __init__(self, main_window, line_edit_obj):
        self._line_edit = line_edit_obj
        self._main_window = main_window

    # FIXME The pyqtSlot decorator causes "TypeError: connect() failed between
    # FIXME textChanged(QString) and edited()"
    @pyqtSlot(name="edited")
    def edited(self):
        # Copy the entry box text to the label box below.
        self._main_window.label.setText(self._line_edit.text())


def main():
    app = QApplication(sys.argv)
    window = MainApp()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

2
这不是一个有用的回答。 如果我遇到了这个问题并且提出了问题,那是因为我不是专家,而是和每个人在某个时候都处于同样的位置。 - R01k
2个回答

11

为什么要使用 @pyqtSlot

它失败的原因是因为 LineEditHandler 不是一个 QObject。使用 @pyqtSlot 的作用是创建一个真正的 Qt slot,而不是使用内部代理对象(这是没有 @pyqtSlot 的默认行为)。


我想要使用@pyqtSlot,因为edited()textChanged信号的一个槽。虽然在这里不一定需要使用@pyqtSlot,但建议对所有槽都使用它。 如果@pyqtSlot不能在QObjects之外使用,那么为什么可以在没有类的模块中使用呢? - R01k
@R01k。当然不建议将其用于所有插槽 - 我不知道你从哪里得到这个建议。 - ekhumoro
据我所知,在连接信号到槽时,建议这样做。当跨线程完成时,强烈建议这样做。再次强调,这只是我听说的。 - R01k
1
@R01k。嗯,你可能已经从我在这个话题上给出的众多答案中听说过了。 - ekhumoro
@ekhumoro 我曾经理解过,在获取速度和内存性能方面,@pyqtslot 应该在任何槽之前推荐使用,除了 moveToThread 问题。但根据你的解释,这个装饰器实际上可以大多数情况下避免使用。 - R01k

2

我不知道出了什么问题,但我找到了一个解决方法:将textChanged信号连接到一个用pyqtSlot装饰的MainApp方法上,该方法调用LineEditHandler.edited():

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSlot


Ui_MainWindow, QtBaseClass = uic.loadUiType("mainwindow.ui")


class MainApp(QMainWindow, Ui_MainWindow):

    def __init__(self):
        # noinspection PyArgumentList
        QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)

        # Instantiate the QLineEdit handler.
        self._line_edit_handler = LineEditHandler(self, self.lineEdit)
        self.lineEdit.textChanged.connect(self._line_edited)

        @pyqtSlot(name="_line_edited")
        def _line_edited(self):
            self._line_edit_handler.edited()

class LineEditHandler:

    def __init__(self, main_window, line_edit_obj):
        self._line_edit = line_edit_obj
        self._main_window = main_window

    def edited(self):
        # Copy the entry box text to the label box below.
        self._main_window.label.setText(self._line_edit.text())


def main():
    app = QApplication(sys.argv)
    window = MainApp()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接