1

I'm looking for a way to fix my code. I have some code that will display a tkinter window with buttons that run code. BUT... The problem is that it will run the same line of code no matter which button is pressed.

Here is my code: Crbnk.py: (Main File)

import tkinter
import draw
import ast

#Setup
oldtiledat = None
window = tkinter.Tk()
window.title("CarbonOS 0.1 (BETA)")
window.geometry("1080x720")
tiledat = {}
tiles = []

#Clears tiles. Arguments: Tile List.
def clearTiles(tl):
    for x in tl:
        x.destroy()
        tl.remove(x)

#Redraws tiles. Arguments: Tile List, Tile Metadata. Returns new tile list
def updateTiles(tl,tls):
    clearTiles(tl)
    length = len(list(tls.keys()))
    return draw.draw(window,list(tls.keys()),list(tls.values()),range(length))

#Loads tiles. Arguments: File to load from
def loadtiles(tilefile):
    with open(tilefile,"r") as tf:
        lines = tf.readlines()
        ret = lines[0]
        ret = ret.rstrip("\n")
        ret = ast.literal_eval(ret)
    return ret

#Dumps tiles to a file. Arguments: File to write, data to save.
def savetiles(tilefile,tiledata):
    with open(tilefile,"w") as tf:
        tf.write(str(tiledata))

#Init

#get tiles into memory
tiledat = loadtiles("tile.cov")


while True:
    #Update
    window.update()

    #TileMgr (Updates if needed so no flicker)
    if oldtiledat != tiledat:
        tiles = updateTiles(tiles,tiledat)
        oldtiledat = tiledat

tile.cov (tile storage file):

{"This Prints Hi":"print('hi')","This Prints Hello":"print('hello')"}

draw.py (tile API):

import tkinter

def draw(window,names,cmd,lst):
    allb = []
    for y in lst:
        b = tkinter.Button(window, text=names[y], command=lambda: eval(cmd[y]))
        print(cmd[y])
        b.pack(side=tkinter.LEFT,anchor=tkinter.N)
        allb.append(b)
    return allb

Thanks in advance!

xcrafter_40
  • 107
  • 1
  • 10
  • Specifically: The problem is with the way `y` is being used in your `for y in lst:` loop. – martineau Apr 15 '17 at 02:01
  • What you you mean about that martineau? – xcrafter_40 Apr 15 '17 at 04:30
  • The `lambda: eval(cmd[y])` creates a closure to the variable `y`, later when the `Button` is pressed the then current value of `y` is used. However the value of the variable at then is whatever is was during the last iteration of the loop. This is the same problem discussed in the questions that yours was marked as duplicating—I suggest you read them along with their accepted answers. [This one](http://stackoverflow.com/a/4236228/355230) may be the easier to grasp since it's very similar to the what you're trying to accomplish. – martineau Apr 15 '17 at 08:21
  • Thanks! I just put in lambda y=y: eval(cmd[y]) – xcrafter_40 Apr 15 '17 at 16:01
  • That's good news, but do you now understand what was happening and _why_ doing that fixes the problem? – martineau Apr 15 '17 at 16:33
  • It works because it makes the variable y visible to the lambda function. Otherwise, y would be 0. – xcrafter_40 Apr 16 '17 at 21:59
  • No, not quite. In your modified code, the changing **value** of the `y` of the `for` loop are used, each iteration, to define the default value of the `y` argument passed to the `lambda` functions being created. However, in your original code is was creating a closure which referred back to that `for` loop `y` variable, which meant all the lambda functions would use whatever its value is when the button was pressed later. By then the `draw()` function has finished executing, so the value of `y` will always be that of the **last** element in `lst`, whatever that happens to be, not `0`. – martineau Apr 17 '17 at 02:17

0 Answers0