Kivy button.bind() with a for loop binds buttons after loop finished

Question:

While trying to bind a button with a for loop, but the value is the last one in the for loop.

Code:

def add(self):
    for i in range(10)
        button = Button(text="hello world")
        button.bind(on_release=(lambda dt: self.function(i)))
        self.add_widget(button)

def function(value):
    print(value)

When I click on any button, it prints out 9, however, when I press the first one, I want output to be 0, then 1, etc…

Asked By: mm4096

||

Answers:

There appears to be a bug in your code:

The local variable i is bound and updated in each loop iteration. In this case, the bug happens because lambda dt: self.function(i), where i references the value of local i in each loop iteration. As this is defined in the scope of a loop, i will eventually end up at the last value in the iteration over range(10), or 9 in this case.

For a simple fix, update your lambda definition to pass in the local variable i as a default parameter, thus copying the current value of i over to the lambda locals:

lambda dt, i=i: self.function(i)

Another Example

Here is yet another reproducible example where the output is similar to the example with the for loop you had posted – note that using a list comprehension as below is just a shorthand way of writing a for loop.

>>> funcs = [lambda: i for i in range(5)]
>>> [f() for f in funcs]
[4, 4, 4, 4, 4]

Scoping the value of i to the lambda locals, fixes the bug that we notice in the above output:

>>> funcs = [lambda _i=i: _i for i in range(5)]
>>> [f() for f in funcs]
[0, 1, 2, 3, 4]
Answered By: rv.kvetch
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.