Gunner Stone
Gunner Stone

Reputation: 1005

Tkinter Python: How to pass more than one argument through a lambda function in an OptionMenu

So as the title suggests, I'm having trouble passing in 2 arguments to a function I want to call when an option menu is changed.

Here is the code below:

OPTIONS = [
        "Fire",
        "Ice",
        "Storm",
        "Life",
        "Myth",
        "Death",
        "Balance"
    ]

    var = StringVar(frame)
    var.set("Select School") # initial value

    option = OptionMenu(frame, var,*OPTIONS,command= lambda frame,var : changeSchool(frame,var))
    option.grid(row=0,column=0,sticky=(N,W))

I've done some research and I think I've done everything correctly but I get the following error when I select an option in the option menu:

Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Gunner\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:\Users\Gunner\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 3287, in __call__
self.__callback(self.__value, *args)
TypeError: <lambda>() missing 1 required positional argument: 'var'

I thought I've passed in var into the changeSchool function.

I appreciate any help!

EDIT: Here's my changeSchool function for those who wish to see it:

def changeSchool(frame,school):
    print (school)
    WizTool.schoolType = school
    self.initBT(frame,WizTool.schoolType)
    print (WizTool.schoolType)

EDIT: Here's the entire program (showing how frame is not in scope)

from tkinter import *
from tkinter import ttk
from functools import partial
import webbrowser
import pprint

class WizTool:
schoolType = "Fire"


#initialize the GUI
def __init__(self, master):
    content = ttk.Frame(master, padding=(3,3,12,12))
    frame = ttk.LabelFrame(content, borderwidth=5, relief="sunken", width=400, height=400,padding=(3,3,12,12),text = "Blade Tracker")
    content.grid(column=0,row=0,sticky=(N,S,E,W))
    frame.grid(column=0, row=0, columnspan=4, sticky=(N, S, E, W))
    self.master = master
    self.initUI(content)
    self.initBT(frame,WizTool.schoolType)


def initUI(self,frame):
    self.master.title("Bootstrapper")


def changeSchool(frame,school):
    print (school)
    WizTool.schoolType = school
    self.initBT(frame,WizTool.schoolType)
    print (WizTool.schoolType)

def initBT(self,frame,mySchool):
#option menu for selecting school

    OPTIONS = [
        "Fire",
        "Ice",
        "Storm",
        "Life",
        "Myth",
        "Death",
        "Balance"
    ]

    var = StringVar(frame)
    var.set("Select School") # initial value

    option = OptionMenu(frame, var,*OPTIONS,command= lambda frame,var : changeSchool(frame,var))
    option.grid(row=0,column=0,sticky=(N,W))





def main():

root = Tk()
root.geometry("800x500+300+300")
app = WizTool(root)
root.mainloop()

main()

Upvotes: 4

Views: 2383

Answers (1)

7stud
7stud

Reputation: 48599

The command function for an OptionMenu widget takes only one argument: the selected item.

  1. Are you calling the command function? Answer: No.
  2. Who is calling the command function? Answer: python.

Python stores the function you specify somewhere, then at some time in the future after you make a selection in the OptionsMenu, python calls the command function. So python gets to decide how many arguments it will pass to the command function, and it turns out that python calls the command function with one argument:

 command(selection)
  1. How do you know that python calls the command function with one argument? Answer: You check the docs.

  2. What happens if you can't find anything in the docs that describes the type of function you need to assign to command? Answer: You test it out.

First:

...
...

def do_stuff(x):
    print(x)

tk.OptionMenu(
    root, 
    str_var, 
    *OPTIONS,
    command=do_stuff).pack()

...
...

--output:--
Storm

Next:

...
...
def do_stuff(x, y):
    print(x, y)

tk.OptionMenu(
    root, 
    str_var, 
    *OPTIONS,
    command=do_stuff).pack()

...
...

--output:--
> TypeError: do_stuff() missing 1 required positional argument: 'y'

There are various ways to solve that problem...

Use python's scoping rules:

import tkinter as tk

OPTIONS = [
        "Fire",
        "Ice",
        "Storm",
        "Life",
        "Myth",
        "Death",
        "Balance"
]

root = tk.Tk()
root.title("Hello")
root.geometry("300x200+10+100")

frame = [1, 2, 3]

def do_stuff(selection):  
    print(selection, frame)  #frame is visible inside the def.

str_var = tk.StringVar(root)
str_var.set(OPTIONS[0]) # default value

tk.OptionMenu(
    root, 
    str_var, 
    *OPTIONS,
    command=do_stuff
).pack()

root.mainloop()

But it's not really a good idea to have functions manipulating global variables, so you can...

Use a wrapper function:

import tkinter as tk

OPTIONS = [
        "Fire",
        "Ice",
        "Storm",
        "Life",
        "Myth",
        "Death",
        "Balance"
]

root = tk.Tk()
root.title("Hello")
root.geometry("300x200+10+100")

def wrapper(other):
    def do_stuff(selection):
        print(selection, other)

    return do_stuff

str_var = tk.StringVar(root)
str_var.set(OPTIONS[0]) # default value

tk.OptionMenu(
    root, 
    str_var, 
    *OPTIONS,
    command=wrapper('xxxx')  #This actually executes wrapper(), and a
                             #function call in your code is replaced
                             #by its return value--which happens to 
                             #be the name of a function that takes one
                             #argument.  The one arg function name is
                             #then assigned to command.
).pack()

root.mainloop()

Use a default value for your extra parameter variable:

frame = [1, 2, 3]

def do_stuff(selection, other=frame):  #<****HERE****
    print(selection, other)

str_var = tk.StringVar(root)
str_var.set(OPTIONS[0]) # default value

tk.OptionMenu(
    root, 
    str_var, 
    *OPTIONS,
    command=do_stuff
).pack()

Warning: Default values for parameter variables have their own issues. The default value is assigned once when the function is defined. Subsequently, no matter how many times you execute the function the same value is used for the default. That means that if the default value is a list, and the first time you call the function you change that list, then the next time you call the function the default value will be the changed list.

Upvotes: 3

Related Questions