3

I am currently working on a GUI (Tkinter) for my application. I am having problems with creating a couple of dropdown menus that should be used to choose a date. The application that I have written creates the desired menus with labels, however, by clicking any of the buttons only the value of the last menu entry gets passed to the tkinter mutable IntVar.

This is a portion of the code that emphasizes my problem. year should be the year that the user clicks upon, however, it is always 2011.

from Tkinter import *
import tkFileDialog as dialog
import datetime
import calendar

window = Tk()
text = Text(window)
text.pack()

year = IntVar()
list_of_years = [1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011]

def year_seter(value):
  year.set(value)

menubar = Menu(window)
yearmenu = Menu(menubar)
for the_year in list_of_years:
  yearmenu.add_command(label=str(the_year), command=lambda : year_seter(the_year))
menubar.add_cascade(label = 'Year', menu=yearmenu)
window.config(menu=menubar)

label = Label(window, textvariable=year)
label.pack()
window.mainloop()

Can somebody please explain to me, why is this happening? Thank you for your time!

Sasha
  • 1,338
  • 2
  • 13
  • 22

1 Answers1

4

Change the command to:

lambda the_year=the_year: year_seter(the_year)

The problem has to do with how Python looks up the value of the_year. When you use

lambda : year_seter(the_year)

the_year is not in the local scope of the lambda function, so Python goes looking for it in the extended, global, then builtin scopes. It finds it in the global scope. The for-loop uses the_year, and after the for-loop ends, the_year retains its last value, 2011. Since the lambda function is executed after the for-loop has ended, the value Python assigns to the_year is 2011.

In contrast, if you use a parameter with a default value, the default value is fixed at the time the function (lambda) is defined. Thus, each lambda gets a different value for the_year fixed as the default value.

Now when the lambda is called, again Python goes looking for the value of the_year, but this time finds it in the lambda's local scope. It binds the default value to the_year.


PS.

  1. You could also forgo defining year_seter and just do:

    lambda the_year=the_year: year.set(the_year)
    
  2. list_of_years = range(1995,2012) also works.
Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Wonderful answer and explanation. Thanks The year_seter and list_of_years have been presented here in simplified form. – Sasha Sep 24 '11 at 22:45