2

I'm writing a program in Python that'll take in an input of a list, which will then copy certain elements of that list into another list of button attributes for Tkinter (name, image path, and command). However, Python only keeps the last value from the for-loop when the Tkinter button command is run. In the example below, it'll only run setCurrProg("Civ6"), even if it should be "Spotify".

    programs = [
        ["Spotify", ""],
        ["Firefox", ""],
        ["Discord", ""],
        ["Civ6", ""]]
# Please ignore the weird tabbing, that's SO
         def importProgs(newProgs):
            for prog in newProgs:
                audio_progs.append([prog[0], prog[1], lambda: setCurrProg(prog[0])])
    
importProgs(programs)

What's even more confusing, is that if I print out the list after I complete the import, I can see that the list's first value is correct, but setCurrProg() doesn't use that value.

[['Spotify', '', <function importProgs.<locals>.<lambda> at 0x000002774968F160>], ['Firefox', '', <function importProgs.<locals>.<lambda> at 0x000002774968F1F0>], ['Discord', '', <function importProgs.<locals>.<lambda> at 0x000002774968F280>], ['Civ6', '', <function importProgs.<locals>.<lambda> at 0x000002774968F310>]]

I tried to make a new variable within the loop as a way to allocate new memory to each prog[0], as a way to save it as another solution had pointed out, but it gets destroyed and rewritten with each iteration of the loop.

Is there some way I can get Python to keep all of the data with the correct indices?

1 Answers1

4

You have an issue with variable scopes.

The lambda keeps the scope of the outer code, meaning that the reference to prog in the lambda will be re-assigned on each update of the outer loop. Example:

funcs = []
for i in range(3):
    funcs.append(lambda: print(i))

for func in funcs:
    func()
2
2
2

A simple solution would be to create a small factory function to construct the lambda. The new function will have it's own scope and wont have its value re-assigned by loop updates, which would now be modifying a different scope.

def program_setter(prog):
    return lambda: setCurrProg(prog)
...

for prog in newProgs:
    audio_progs.append([prog[0], prog[1], program_setter(prog[0])])

Or put the entire entry creation into a function to achieve the same goal of keeping the scope for the lambda isolated:

def program_entry(prog):
    return [prog[0], prog[1], lambda: setCurrProg(prog[0])]
...

for prog in newProgs:
    audio_progs.append(program_entry(prog))
flakes
  • 21,558
  • 8
  • 41
  • 88