0

Hi I have a very specific code problem I would need a fresh pair of eyes to look at. I'm trying to make a GUI for an Othello game I programmed. I've just started and made green tiles that I will play the game in. To see that everything worked I made a simple function that is called every time one of the tiles is clicked. The function should then print the coordinates of the tile I just clicked to the console. However what it does is that it prints [7, 7]. This of course are the coordinates of the very last tile created. Anyway I don't know what I'm doing wrong but my guess is that I don't fully understand the .bind() function, thanks for any help.

Code:

from tkinter import *

def print_cors(event, cors):
    print(cors)


class GridFrame:
    def __init__(self, master):
        board = Frame(master, height=500, width=800, bg="brown")
        board.grid()
        self.column_frame_list =  [None]*8
        for x in range(0, len(self.column_frame_list)):
            self.column_frame_list[x] = [None]*8
            for y in range(0, len(self.column_frame_list[x])):
                self.column_frame_list[x][y] = Canvas(board, height=55, width=55, bg="green", bd=0, highlightthickness=0, relief='ridge')
                self.column_frame_list[x][y].bind("<Button-1>", lambda event: print_cors(event, [x,y]))
                self.column_frame_list[x][y].grid(row=y, column=x)

root = Tk()
root.wm_title("Reversi")
grid_frame = GridFrame(root)
root.mainloop()
Ivar Eriksson
  • 863
  • 1
  • 15
  • 30

2 Answers2

3

Have you tried closing x and y in your function?

The problem is that x and y in the scope that the functions looks in are already changed to 7, 7 by the time the loop ends. The solution is to create a new scope where the wanted variables are set, and it can be done using a function that returns a function.

Try changing:

lambda event: print_cors(event, [x,y])

To:

(lambda _x, _y: lambda event: print_cors(event, [_x,_y]))(x,y)

This is a reconstruction of your problem:

>>> l = []
>>> for lam in l:
...     print lam()
... 
>>> l = []
>>> for x in range(3):
...     l.append(lambda : x)
... 
>>> for lam in l:
...     print lam()
... 
2
2
2

And this is the solution with boxing the loop variables into the function's scope (closure) by adding them as parameters of an outer function:

>>> l = []
>>> for x in range(3):
...     l.append((lambda _x: lambda : _x)(x)) # close x into the function
... 
>>> for lam in l:
...     print lam()
... 
0
1
2
Reut Sharabani
  • 30,449
  • 6
  • 70
  • 88
  • Thank you very much that solved it but would you mind explaining what that actually does or maybe direct me to a source which can tell me? – Ivar Eriksson Sep 12 '15 at 22:56
  • It's basically creating an environment with the function: https://en.wikipedia.org/wiki/Closure_%28computer_programming%29 – Reut Sharabani Sep 12 '15 at 22:56
1

The usual solution is to replace:

self.column_frame_list[x][y].bind("<Button-1>", lambda event: print_cors(event, [x,y]))

with:

self.column_frame_list[x][y].bind("<Button-1>", lambda event, x=x, y=y: print_cors(event, [x,y]))

See Tkinter assign button command in loop with lambda

for more explanations.

Community
  • 1
  • 1
Eric Levieil
  • 3,554
  • 2
  • 13
  • 18