Connecting Qt Signal in For Loop

Introduction

We often need to create ui elements on the fly, sometimes we do it in something like a for loop. An example would be creating a series of QPushButton and connect them to a function through different argument values. An example is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Demo, self).__init__(parent)

# initialization object
layout = QtWidgets.QVBoxLayout()

for index in range(6):
pushbutton = QtWidgets.QPushButton('button {}'.format(index))
pushbutton.clicked.connect(lambda: self.trigger(index))
layout.addWidget(pushbutton)

self.setLayout(layout)

@staticmethod
def trigger(index):
print('button {} clicked'.format(index))

Here I created six QPushButton and when I click them it should output which button is being clicked. But if we run this script and try to click each button it will always output “button 5 clicked” (aka, the last button). It is safely to assume that the argument passed during the for loop always result in the last index.

Explanation

Based on a kind response from stackoverflow: lambdas do not store the value of button when it is defined. The code describing the lambda function is parsed and compiled but not executed until we actually call the lambda. Therefore, when a button is clicked, the current value of that variable is used (the last index).

What’s the solution?

Lambda with solid variable

Passing solid variable to the lambda

1
pushbutton.clicked.connect(lambda _, i=index: self.trigger(index=i))

Note that we created another temporary variable before index as the first argument passed in the lambda will always return as False. Because Qt defines the signal QAbstractButton.clicked to take a single argument with a default value of False. Since our lambda is handling that signal, it gets called with False.

Partial approach

Use functools.partial also works

1
2
from functools import partial
pushbutton.clicked.connect(partial(self.trigger, index))

Note that in some cases where wrappers are being used in trigger function, it could be trickier to use this as oppose to lambda

Reference

Stack Overflow - First lambda capture of local variable always False

Stack Overflow - Connecting multiples signal/slot in a for loop in pyqt