4

(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.

1

2 Answers 2

9

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.

Sign up to request clarification or add additional context in comments.

1 Comment

I've been stuck on this particular problem for 3 days now. Even my teacher can't figure it out. Thank you so much!!
0

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.

1 Comment

I've tried your solution and it works fine, but it wasn't really under the scope of my course, so I couldn't really use it. However I did try the solution from @unutbu also using lambda. Thanks anyway!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.