[Tkinter] messed callbacks

Diez B. Roggisch deets at nospam.web.de
Wed Sep 9 06:27:27 EDT 2009


Giacomo Boffi wrote:

> i have this test program (that i already posted on it.comp.lang.python)
> 
> ----[ test.py ]
> from Tkinter import *
> 
> def output(s):
>   print s
> 
> def doit(fr,lst):
>   for c1,c2 in zip(lst[::2], lst[1::2]):
>     subframe=Frame(fr)
>     Label(subframe,text=c1+' <->
>     '+c2).pack(side='left',expand=1,fill='both')
>     Button(subframe,text='>',command=lambda: output(c1+'->'+c2)).pack()
>     Button(subframe,text='<',command=lambda: output(c2+'->'+c1)).pack()
>     subframe.pack(fill='x',expand=1)
> 
> root=Tk()
> fr=Frame(root,relief='raised',borderwidth=5).pack(fill='both',expand=1)
> doit(fr,['pippo','pluto','paperino','zio_paperone'])
> Button(root,text='Q',command=root.destroy).pack(expand=1,fill='x')
> root.mainloop()
> ----
> 
> when i execute it from the command line, click on the 4 buttons from
> top to bottom and quit (you can do the same), this is what i get
> 
> aiuole: python test.py
> gray->black
> black->gray
> gray->black
> black->gray
> aiuole:
> 
> as you see, the button 0 and 1 have callbacks different from my
> expectations, as my script was intended to give
> 
> cyan->blue
> blue->cyan
> gray->black
> black->gray
> 
> why the messed callbacks? what's the right thing to do?

Closures in python contain names, not the objects they refer to. So when you
rebind that name (as you do above in your loop), the created callbacks will
only refer to the last bound value of a name.

Create new closures, or bind arguments as defaults:

funcs = []

def create_func(i):
    return lambda: i

for i in xrange(10):
    funcs.append(lambda i=i: i)
    funcs.append(create_func(i))

for f in funcs:
    print f()

Diez



More information about the Python-list mailing list