How to connect multiple PyQt Signals w/ variable arguments to single slot/signal repeater

Viewed 1896

Example:

class MyClass(QObject):
    signal_1 = pyqtSignal(str, int)
    signal_2 = pyqtSignal(int, int)
    signal_3 = pyqtSignal(str, int, int)

Let's say each of these signals is connected elsewhere to perform various functions, however, there's a specific function I want to be performed as well when any of the signals are emitted. What this indiscriminate function does cares only about, say, the last int argument emitted with the signal. The slot would ostensibly look like:

class OtherClass(QObject):

    ...

    @pyqtSlot(int)
    def unifiedResponse(self, index):
        # Here I would do something with that index

Is there a way to connect some arbitrary number of signals with arbitrary arguments either directly to a slot or to a repeater signal?

If there is a way to define a slot like so:

@pyqtSlot(???)
def unifiedResponse(self, *args):
    important_var = args[-1]

Then I could simply capture the last argument as such. However, I have been unsuccessful in determining how to form the slot signature.

Update:

I may have answered my own question, using lambdas:

signal_1.connect(lambda _, source: OtherClass.unifiedResponse(source))
signal_2.connect(lambda _, source: OtherClass.unifiedResponse(source))
signal_3.connect(lambda _, _, source: OtherClass.unifiedResponse(source))

The solution from @eyllanesc below however is preferred, as it allows for more flexibility the larger the signal count.

2 Answers

You have to set all the signatures of the signals through several pyqtSlot:

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QCoreApplication, QObject, QTimer


class MyClass(QObject):
    signal_1 = pyqtSignal(str, int)
    signal_2 = pyqtSignal(int, int)
    signal_3 = pyqtSignal(str, int, int)


class OtherClass(QObject):
    @pyqtSlot(str, int)
    @pyqtSlot(int, int)
    @pyqtSlot(str, int, int)
    def unifiedResponse(self, *args):
        print(args)


def main():
    import sys

    app = QCoreApplication(sys.argv)

    sender = MyClass()
    receiver = OtherClass()

    sender.signal_1.connect(receiver.unifiedResponse)
    sender.signal_2.connect(receiver.unifiedResponse)
    sender.signal_3.connect(receiver.unifiedResponse)

    def on_timeout():
        sender.signal_1.emit("StackOverflow", 1)
        sender.signal_2.emit(1, 2)
        sender.signal_3.emit("StackOverflow", 1, 2)

        QTimer.singleShot(1000, QCoreApplication.quit)

    QTimer.singleShot(1000, on_timeout)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

If in general you want to send several types of data then it is best to use a more generic data type such as a list (or an object):

class MyClass(QObject):
    signal_1 = pyqtSignal(list)
    signal_2 = pyqtSignal(list)
    signal_3 = pyqtSignal(list)


class OtherClass(QObject):
    @pyqtSlot(list)
    def unifiedResponse(self, args):
        print(args)


def main():
    import sys

    app = QCoreApplication(sys.argv)

    sender = MyClass()
    receiver = OtherClass()

    sender.signal_1.connect(receiver.unifiedResponse)
    sender.signal_2.connect(receiver.unifiedResponse)
    sender.signal_3.connect(receiver.unifiedResponse)

    def on_timeout():
        sender.signal_1.emit(["StackOverflow", 1])
        sender.signal_2.emit([1, 2])
        sender.signal_3.emit(["StackOverflow", 1, 2])

        QTimer.singleShot(1000, QCoreApplication.quit)

    QTimer.singleShot(1000, on_timeout)

    sys.exit(app.exec_())

Update:

There is no elegant way to access the last element, but the first ones since the slot signature must be a subset of the signal signature, for example the slot with signature "int" can accept signals with a signature that has the first type to "int", the other arguments are discarded:

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QCoreApplication, QObject, QTimer


class MyClass(QObject):
    signal_1 = pyqtSignal(int, str)
    signal_2 = pyqtSignal(int, int)
    signal_3 = pyqtSignal(int, str, int)


class OtherClass(QObject):
    @pyqtSlot(int)
    def unifiedResponse(self, index):
        print(index)


def main():
    import sys

    app = QCoreApplication(sys.argv)

    sender = MyClass()
    receiver = OtherClass()

    sender.signal_1.connect(receiver.unifiedResponse)
    sender.signal_2.connect(receiver.unifiedResponse)
    sender.signal_3.connect(receiver.unifiedResponse)

    def on_timeout():
        sender.signal_1.emit(1, "StackOverflow")
        sender.signal_2.emit(2, 2)
        sender.signal_3.emit(3, "StackOverflow", 1)

        QTimer.singleShot(1000, QCoreApplication.quit)

    QTimer.singleShot(1000, on_timeout)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

I've been having problems to connect multiple PyQt Signals with variable arguments to a single slot.

That was the solution I found for connecting all the widgets from input_lineEdit_widgets to validate_values

class View_Controller():

    def connect_signals(self,ui):

        #This is a list of lineEdit widgets from a QtWidgets.QMainWindow (ui)
        input_lineEdit_widgets = [ui.prim_press_lineEdit, ui.gas_vol_lineEdit, ui.melt_1_temp_lineEdit,
                                 ui.melt_1_time_lineEdit, ui.melt_1_flow_lineEdit, ui.melt_2_temp_lineEdit,
                                 ui.melt_2_time_lineEdit, ui.melt_2_flow_lineEdit]

        for widgets in input_lineEdit_widgets:
            widgets.textChanged.connect(lambda *args, widgets = widgets: self.validate_values(widgets))


    def validate_values(self, widget):
        print(widget.objectName())

More details here

Related