lambda i=i: foo(i) in for loop not working
Question:
First read this. It is about lambda x=x: foo(x)
catching x even in for
loop.
This is a window with label and two buttons generated in for
loop. When button is clicked, it name appears in label.
If we use usual lambda: label.setText("button -- " + str(i))
, then the result is last i
in the loop, no matter what button is pressed:
And this is right.
When we change to lambda i=i: label.setText("button -- " + str(i))
(snipet) and expect that now it will be everything ok, the result is:
False!
Where this False
comes from?
import sys
from PyQt4.QtGui import *
class MainWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
vbox = QVBoxLayout(self)
# label for action
label = QLabel('')
vbox.addWidget(label)
# adding buttons
for i in range (1, 3):
btn = QPushButton(str(i))
btn.clicked.connect( lambda i=i: label.setText("button " + str(i)) )
vbox.addWidget(btn)
app = QApplication(sys.argv)
myapp = MainWindow()
myapp.show()
sys.exit(app.exec_())
Why this solution is not working as it should be? What this false
means?
I know that you can make foo_factory
, as in first link, but the question is what is wrong with lambda i=i: foo(i)
Answers:
I don’t have PyQt4 installed to test at this very instant, but it seems clear to me that when your lambda callback is called, it’s being given an argument. i
is then equal to whatever the argument is, instead of the default value. Try this and tell me if it works (or if it at least changes the output):
btn.clicked.connect( lambda throw_away=0, i=i: label.setText("button " + str(i)) )
Signal “clicked” passes a boolean argument to your connected lambda slot.
Documentation
What you are trying to accomplish is better done by this:
btn.clicked.connect( lambda clicked, i=i : label.setText("button " + str(i)) )
Instead of binding via a default argument, binding via functools.partial
makes these problems easier to debug.
The correct code (if I have understood the other answers correctly; I don’t have PyQT experience) should look like:
from functools import partial
# and then:
set_to_i = partial(label.setText, f"button {i}")
btn.clicked.connect(lambda clicked: set_to_i())
This way, we are binding the value using a tool explicitly made for the job, rather than exploiting what is usually considered a gotcha. Notably, if we had taken this approach initially, but overlooked the clicked
argument (for example, directly writing btn.clicked.connect(set_to_i)
), we would get a TypeError
, rather than the default-bound i
being overridden by what is supposed to be the clicked
boolean parameter. Seeing that the function was given an extra positional parameter, would be a clue to check the documentation.
First read this. It is about lambda x=x: foo(x)
catching x even in for
loop.
This is a window with label and two buttons generated in for
loop. When button is clicked, it name appears in label.
If we use usual lambda: label.setText("button -- " + str(i))
, then the result is last i
in the loop, no matter what button is pressed:
And this is right.
When we change to lambda i=i: label.setText("button -- " + str(i))
(snipet) and expect that now it will be everything ok, the result is:
False!
Where this False
comes from?
import sys
from PyQt4.QtGui import *
class MainWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
vbox = QVBoxLayout(self)
# label for action
label = QLabel('')
vbox.addWidget(label)
# adding buttons
for i in range (1, 3):
btn = QPushButton(str(i))
btn.clicked.connect( lambda i=i: label.setText("button " + str(i)) )
vbox.addWidget(btn)
app = QApplication(sys.argv)
myapp = MainWindow()
myapp.show()
sys.exit(app.exec_())
Why this solution is not working as it should be? What this false
means?
I know that you can make foo_factory
, as in first link, but the question is what is wrong with lambda i=i: foo(i)
I don’t have PyQt4 installed to test at this very instant, but it seems clear to me that when your lambda callback is called, it’s being given an argument. i
is then equal to whatever the argument is, instead of the default value. Try this and tell me if it works (or if it at least changes the output):
btn.clicked.connect( lambda throw_away=0, i=i: label.setText("button " + str(i)) )
Signal “clicked” passes a boolean argument to your connected lambda slot.
Documentation
What you are trying to accomplish is better done by this:
btn.clicked.connect( lambda clicked, i=i : label.setText("button " + str(i)) )
Instead of binding via a default argument, binding via functools.partial
makes these problems easier to debug.
The correct code (if I have understood the other answers correctly; I don’t have PyQT experience) should look like:
from functools import partial
# and then:
set_to_i = partial(label.setText, f"button {i}")
btn.clicked.connect(lambda clicked: set_to_i())
This way, we are binding the value using a tool explicitly made for the job, rather than exploiting what is usually considered a gotcha. Notably, if we had taken this approach initially, but overlooked the clicked
argument (for example, directly writing btn.clicked.connect(set_to_i)
), we would get a TypeError
, rather than the default-bound i
being overridden by what is supposed to be the clicked
boolean parameter. Seeing that the function was given an extra positional parameter, would be a clue to check the documentation.