Search code examples
pythonpyqtpyqt5qpushbutton

function call through signal changes default keyed arguments to 'False'. Why?


When calling a function via a signal connection as in

mybutton.clicked.connect(myfunction)

the function is called with all its arguments set to False. Even though default arguments have been set. Is this the expected behavior?

The code below shows a simple example. For my particular case having all arguments set to False is not a big issue as I can simply catch it with an if statement. However I feel that knowledge on why it is implemented this way may be helpful for further projects.

This simple program shows the behavior I would expect.

def function(foo=None):
    print(foo)

function()

None

With this minimal Qt example however, the functions argument is no longer 'None', but 'False'.

import sys
from PyQt5.QtWidgets import QMainWindow, QGridLayout, QWidget, QPushButton, QApplication

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        centralWidget = QWidget(self)
        self.setCentralWidget(centralWidget)
        gridLayout = QGridLayout(self)
        centralWidget.setLayout(gridLayout)
        btn = QPushButton("button",self)
        gridLayout.addWidget(btn)
        btn.clicked.connect(self.function)

    def function(self, foo=None):
        print(foo)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()
    sys.exit( app.exec_() )

False

The question, i guess, is: Do I have to live with no having the default values available in my functions (i.e. do I need to reset them once I notice that they were set to False) or is there any smarter way to handle it?


Solution

  • clicked is an overloaded signal, that is, they are 2 signals with the same name and with different signatures.

    clicked = QtCore.pyqtSignal([], [bool])
    # clicked = QtCore.pyqtSignal()
    # clicked = QtCore.pyqtSignal(bool)
    

    For more detail read this answer.

    When the connection is made PyQt determines which of the signals will use, in this case the arguments of your function are similar to the second form so the signal will send the boolean False, and in your case it will replace the default value.

    If you do not want that to happen then you must force PyQt to use the first form using the @QtCore.pyqtSlot() decorator so that it does not send you any parameter and therefore your function uses the default parameter.

    import sys
    from PyQt5 import QtCore, QtWidgets
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            centralWidget = QtWidgets.QWidget()
            self.setCentralWidget(centralWidget)
            gridLayout = QtWidgets.QGridLayout(centralWidget)
            btn = QtWidgets.QPushButton("button")
            gridLayout.addWidget(btn)
            btn.clicked.connect(self.function)
    
        @QtCore.pyqtSlot()
        def function(self, foo=None):
            print(foo)
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        mainWin = MainWindow()
        mainWin.show()
        sys.exit(app.exec_())
    

    Output:

    None