Python: How to pass different arguments in lambda function when creating Tkinter buttons in a loop?

Question:

Here is my script currently:

import tkinter as tk

class Soundboard(tk.Frame):
    def __init__(self, sounds, master=None):
        super().__init__(master)

        self.sounds = sounds

        self.pack()    
        self.create_button_loop()

    def create_button_loop(self):
        self.buttons = {}
        for name, sound in self.sounds.items():
            self.buttons[name] = tk.Button(self, text=name, command = lambda :play_sound(sound))
            self.buttons[name].pack(side='top')    
        #sound = 10



def play_sound(a_sound):
    print(a_sound)


#MAIN SCRIPT
if __name__ == '__main__':

    sounds = {'a':1, 'b':2, 'c':3}
    board = Soundboard(sounds)

There is one class, Soundboard, and one function, play_sound, and the script section at the bottom. Sounds is a dict with the pattern {name : sound} Currently sound is just a number, and the function play_sound just prints what is called to it.

The problem I’ve been having is in the create_button_loop method of Soundboard. In the loop, for each name/sound combination, a button is created in the dict self.buttons where name is the button’s text and sound is passed to play_sound (the lambda statement).

What I want to happen is for the sound passed through the lambda statement to be sound when the button is created, not what sound is when the button is pressed, so play_sound should print the original value of sound. What actually happens is that as sound gets reassigned later (by the next iteration of the loop), when the button is pressed play_sound plays whatever the current value of sound is.

In this example, 3 buttons are created, “a” “b” and “c”. When “a” is pressed, “3” is printed (most recent reassignment of sound is in the last iteration of the for loop, so name/sound = c/3 instead of “1”, as it was when button “a” was created.

How can I avoid the sound value being passed in the lambda statement changing when sound changes? I’d much prefer some iterated method like this as opposed to writing a different callback function for each {name:sound} combination, but if that is the best way then let me know. Thank you in advance!

Asked By: Bryan

||

Answers:

Use functools.partial instead of lambda.

from functools import partial
...
self.buttons[name] = tk.Button(self, text=name, command=partial(play_sound, sound))
Answered By: Novel
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.