1

I'm currently trying to write a GUI for Tic Tac Toe in Python. For that purpose I am using tkinter. I want to create a button for every position to be able to make my moves by clicking one of these buttons. The problem now is that I want a function with different parameters to be called depending on the button I clicked. Therefore I tried to use lambda expressions, but no matter which button I click, it results in the same call.

import tkinter as tk

class GameWindow:

    def __init__(self):
        win = tk.Tk()

        buttons = [[tk.Button(win,text=str(i)+str(j)) for j in range(3)] for i in range(3)]
        for i in range(3):
            for j in range(3):
                buttons[i][j]["command"] = lambda: print([i,j])
                buttons[i][j].grid(row=i, column=j)
                print([i,j])
        win.mainloop()

test = GameWindow()

In this case, I just want the position to be printed, but no matter which button I click, [2,2] is being printed.

cerdn
  • 11
  • 1

1 Answers1

1

The problem you're seeing is due to the fact that you've created a closure. This means that the stack frame in which the lambda was created remains active, and the lambda refers to the current value of those variables, rather than their values when it was created. You can capture their values by doing this:

def make_lambda(i, j):
    return lambda: print([i, j])

Then in your code, replace:

buttons[i][j]["command"] = lambda: print([i,j])

with:

buttons[i][j]["command"] = make_lambda(i, j)

This will capture and preserve the values at the time that make_lambda was called. It will be a closure, but its frame will have exited and the lambda it returns will be the only remaining reference to it.

Tom Karzes
  • 22,815
  • 2
  • 22
  • 41
  • 1
    There's a simpler solution, which is to use arguments defaults to bind the lambda to the current value ie `buttons[i][j]["command"] = lambda i=i, j=j: print([i,j])` – bruno desthuilliers Apr 15 '20 at 11:12
  • @brunodesthuilliers That works, although it adds two optional arguments to the lambda. If you want to get an error if any arguments are passed to it, this will instead block the error and override those values. That may be sufficient in some cases, but if you want to preserve the desired arguments, then you need to encapsulate it as I have done. – Tom Karzes Apr 15 '20 at 13:26
  • what you say is, technically, totally true. Now in practice, this has never ever been an issue to me in the last 15 year of using Python for production apps. YMMV... – bruno desthuilliers Apr 15 '20 at 14:09