Tkinter lambda function

(As the ‘homework’ tag indicates, this is part of a big project in Computer Science.)

I am writing a Jeopardy! simulation in Python with tkinter, and I’m having a big problem regarding the use of the lambda function in buttons. Assume root = Tk() and categories is a list.

# Variable to keep the buttons
root._buttons = {}

# Display headers on top of page
for i in range(5):
    # Get category name for display in main window
    name = categories[i]
    b = Label(root, text=fill(name.upper(), 10), width=18, height=3,
        bg="darkblue", fg="white", font=("Helvetica bold", "", 11))
    b.grid(row=0, column=i)

    # Create list of buttons in that variable (root._buttons)
    btnlist = [None]*5

    # Display individual questions
    for j in range(5):

        # Make a button for the question
        b = Button(root, text="$" + str(200 * (j+1)), width=8, height=1,
            bg="darkblue", fg="orange", font=("Impact", "", 30))
        b.cat = name
        b.value = 200 * (j + 1)
        b.sel = lambda: select(b.cat, b.value)

        # Add callback event to button
        print(b.cat, b.value, b.sel)
        b.config(command=b.sel)

        # Add button to window
        b.grid(row=j+1, column=i)

        # Append to list
        btnlist[j] = b

    root._buttons[categories[i]] = btnlist

For all of the code, see my little Code Viewer (under construction!)

It’s at lambda: select(b.cat, b.value) where the problem seems to occur, because when I click any button on the board, it always goes to the one last button on the board. I’ve tried other approaches, unfortunately all using lambda, and I have not seen any approach that does not involve lambda.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Change

lambda: select(b.cat, b.value)

to

lambda b = b: select(b.cat, b.value)

In your original code, b is not a local variable of the lambda; it is found in enclosing scope. Once the for-loop is completed, b retains it last value. That is why the lambda functions all use the last button.

If you define the lambda to take one argument with a default value, the default value is determined (and fixed) at the time the lambda is defined. Now b is a local variable of the lambda, and when the lambda is called with no arguments, Python sets b to the default value which happily is set to various different buttons as desired.

Method 2

It would let you be more expressive if you replaced the lambda expression with a function factory. (presuming that you’re going to call this multiple times). That way you can do assignments, add more complicated logic, etc later on without having to deal with the limitations of lambda.

For example:

def button_factory(b):
    def bsel():
        """ button associated with question"""
        return select(b.cat, b.value)
    return bsel

Given an input b, button_factory returns a function callable with () that returns exactly what you want. The only difference is that you can do assignments, etc.

Even though it may take up more lines of code initially, it gives you greater flexibility later on. (for example, you could attach a counter to bsel and be able to count how many times a particular question was selected, etc).

It also aids introspection, as you could make each docstring clearly identify which question it is associated with, etc.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x