Python Forum

Full Version: Commands seem to change when variable is part of a for loop
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hey all, I'm trying to create a gui menu to easily run different snippets of code for my programming class in high school. I'm having a problem when using a for loop to shorten the process of setting commands to run parts of a file. Whenever I click on a button with the command set with a for loop it always runs the command for the last button in the cascade, not the one I clicked on.

Here's my menu I'm using to access all the files (Slightly simplified):
from tkinter import *
import fl1

def add(file, length):
    fileMenu = Menu(menuBar)
    menuBar.add_cascade(label='Stuff', menu=fileMenu)
    for i in range(1, length+1):
        fileMenu.add_command(label='Thing '+str(i), command=lambda:file.thing(i))

root = Tk()
menuBar = Menu(root)

fileMenu = Menu(menuBar)
menuBar.add_cascade(label='Files', menu=fileMenu)
fileMenu.add_command(label='File 1', command=lambda:add(fl1, 3))

root.config(menu=menuBar)
root.mainloop()
And here's an example file I'd use to split up the code and make it more manageable:
def thing(num):
    if num == 1:
        print('This is thing number 1.')
        print('\n--------------------\n')
        
    elif num == 2:
        print('This is thing number 2.')
        print('\n--------------------\n')
        
    elif num == 3:
        print('This is thing number 3.')
        print('\n--------------------\n')
        
    else:
        print('Thing does not exist.')
        print('\n--------------------\n')
At first I was using different functions for each bit of code, but I couldn't find a way to run each function using the same for loop.

As you can see when running the menu code it doesn't matter which button you click on it will always run thing number 3. I have no idea why this is happening as when I type out each command separately it works fine. Thanks for the help!
You can use partial to send arguments to a function from a Button command. Also, it is considered bad form to use wildcard imports as you can not then tell which namespace a variable or function comes from. And please don't use i,l, or O as single character variable names as they can look like numbers.

import sys
if 3 == sys.version_info[0]:  ## 3.X is default if dual system
    import tkinter as tk     ## Python 3.x
else:
    import Tkinter as tk     ## Python 2.x
## do not use wildcard imports.  You do not know what
## is coming from which name-space
##from tkinter import *

##import fl1   don't know what this is

from functools import partial

def thing(num):
    if num == 1:
        print('This is thing number 1.')
        print('\n--------------------\n')
         
    elif num == 2:
        print('This is thing number 2.')
        print('\n--------------------\n')
         
    elif num == 3:
        print('This is thing number 3.')
        print('\n--------------------\n')
         
    else:
        print('Thing does not exist.')
        print('\n--------------------\n')

def add(length):
    fileMenu = tk.Menu(menuBar)
    menuBar.add_cascade(label='Stuff', menu=fileMenu)
    for ctr in range(1, length+1):
        fileMenu.add_command(label='Thing '+str(ctr), command=partial(thing, ctr))
 
root = tk.Tk()
menuBar = tk.Menu(root)
 
fileMenu = tk.Menu(menuBar)
menuBar.add_cascade(label='Files', menu=fileMenu)
fileMenu.add_command(label='File 1', command=partial(add, 3))
 
root.config(menu=menuBar)
root.mainloop() 
(Feb-22-2018, 02:43 AM)woooee Wrote: [ -> ]You can use partial to send arguments to a function from a Button command. Also, it is considered bad form to use wildcard imports as you can not then tell which namespace a variable or function comes from. And please don't use i,l, or O as single character variable names as they can look like numbers.

import sys
if 3 == sys.version_info[0]:  ## 3.X is default if dual system
    import tkinter as tk     ## Python 3.x
else:
    import Tkinter as tk     ## Python 2.x
## do not use wildcard imports.  You do not know what
## is coming from which name-space
##from tkinter import *

##import fl1   don't know what this is

from functools import partial

def thing(num):
    if num == 1:
        print('This is thing number 1.')
        print('\n--------------------\n')
         
    elif num == 2:
        print('This is thing number 2.')
        print('\n--------------------\n')
         
    elif num == 3:
        print('This is thing number 3.')
        print('\n--------------------\n')
         
    else:
        print('Thing does not exist.')
        print('\n--------------------\n')

def add(length):
    fileMenu = tk.Menu(menuBar)
    menuBar.add_cascade(label='Stuff', menu=fileMenu)
    for ctr in range(1, length+1):
        fileMenu.add_command(label='Thing '+str(ctr), command=partial(thing, ctr))
 
root = tk.Tk()
menuBar = tk.Menu(root)
 
fileMenu = tk.Menu(menuBar)
menuBar.add_cascade(label='Files', menu=fileMenu)
fileMenu.add_command(label='File 1', command=partial(add, 3))
 
root.config(menu=menuBar)
root.mainloop() 

This doesn't actually solve my issue because I'm calling the thing function from another file (That's what fl1 is). Whenever I try to use partial in this way it gives the error below. I have changed the way I imported tkinter and changed the variable name. Thanks for your help.

Error:
TypeError: the first argument must be callable
(Feb-22-2018, 03:45 AM)StoopidChicken Wrote: [ -> ]This doesn't actually solve my issue because I'm calling the thing function from another file
Use fl1.thing instead of thing in Woooee's code.
(Feb-22-2018, 04:50 AM)Gribouillis Wrote: [ -> ]
(Feb-22-2018, 03:45 AM)StoopidChicken Wrote: [ -> ]This doesn't actually solve my issue because I'm calling the thing function from another file
Use fl1.thing instead of thing in Woooee's code.

Oh, it seems I was using partial wrong.

I was doing this:
fileMenu.add_command(label='File 1', command=partial(add(fl1, 3)))
Instead of this:
fileMenu.add_command(label='File 1', command=partial(add, fl1, 3))
Sorry about the confusion.