0

I'm using pyqt and am trying to connect a list of Qpushbuttons to their respective functions and parameters, but they all end up connected to the final parameter instead.

I encountered this issue twice and could only find a solution the first time.

I use a list called vessel to hold information about the buttons I want to create, including the function and a list containing the parameters.

In the setButtons function I take the parameter list and loop through it, replacing the list itself with each parameter and passing the updated vessel into the setButton function.


def setButtons(vessel):

#vessel[1] is the parameter list, vessel[2] is the function

    objList = vessel[1]

    for obj in objList:
          
         vessel[1] = obj
         self.setButton(vessel)

def setButton(vessel):

    button = QPushButton(name + value)

    button.clicked.connect(lambda: vessel[2](vessel[1]))

Whenever I run the program and click a button, the parameter used will always be that of the final button added, so adding buttons for parameters 'a' 'b' and 'c' and then clicking the 'a' button will just open up a 'c' window. I fixed this by adding an intermediate variable:

def setButton(vessel):

    button = QPushButton(name + value)

    x = vessel[1]

    button.clicked.connect(lambda: vessel[2](x))

After doing this the buttons were all connected to the proper parameters, but I don't understand why this helps. When I encountered a similar issue later seeming to occur in the following update function, the same fix did not help.

def update(self)

    for sub in self.subList:

#loops through list containing all subwindows

         for l in sub.subVessel:

#subVessel is another list for button info. subVessel[0] is the parameter, [1] is the function, [2] is the button

               obj = l[0]
               func = l[1]
               button = l[2]
               button.clicked.disconnect()
               button.clicked.connect(lambda: func(obj))
  • Does this answer your question? [How to use lambda function with the PyQt "connect" function when the widgets are instanciated in a loop?](https://stackoverflow.com/questions/72616801/how-to-use-lambda-function-with-the-pyqt-connect-function-when-the-widgets-are) – mahkitah May 30 '23 at 09:33
  • Python has [late binding closures](https://docs.python-guide.org/writing/gotchas/#late-binding-closures). That means that the lambdas are evaluated at runtime and it uses the values from the last loop. One solution is to use a functools partial instead of a lambda. – mahkitah May 30 '23 at 09:37

1 Answers1

0

I fixed this by adding an intermediate variable ... but I don't understand why this helps.

Remember that in Python all names are just references. This is especially relevant when you pass a variable to a function that modifies this variable because it is most likely not what you want.

Your original implementation of setButtons() modified its parameter vessel thereby modifying the original list so that all lambdas were affected by that change.

When I encountered a similar issue later seeming to occur in the following update function, the same fix did not help.

The lambda uses a reference, too, not the value the variables had when defining the lambda. So only the last value of func and obj are taken into account when executing each lambda. See this example to illustrate the behaviour:

my_list = []
for i in range(5):
    my_list.append(lambda: print(i))

my_list[0]()  # prints 4, though one might expect 0

To work around this you need to create each lambda in a separate function like this:

def gen_l(x):
    return lambda: print(x)

my_list = []
for i in range(5):
    my_list.append(gen_l(i))

mylist[0]()  # prints 0
SebDieBln
  • 3,303
  • 1
  • 7
  • 21