Tkinter.Button(... command) lambda and argument problem

J

Jay

I'm having a problem using lambda to use a command with an argument for
a button in Tkinter.

buttons = range(5)
for x in xrange(5):
buttons[x] = Button(frame, text=str(x+1), command=lambda:
self.highlight(x))
buttons[x].pack(side=LEFT)

The buttons are correctly numbered 1 through 5, but no matter which
button I click on, it sends the number 4 as an argument to the
highlight function. How can I correct this?

By the way, I've tried changing xrange to range with no success.
 
P

Paul Rubin

Jay said:
I'm having a problem using lambda to use a command with an argument for
a button in Tkinter.

buttons = range(5)
for x in xrange(5):
self.highlight(x))
buttons[x].pack(side=LEFT)

The buttons are correctly numbered 1 through 5, but no matter which
button I click on, it sends the number 4 as an argument to the
highlight function.

x is not bound by the lambda and so the lambda body gets it from the
outside environment at the time the body is executed. You have to
capture it at the time you create the lambda. There's an ugly but
idiomatic trick in Python usually used for that:

buttons[x] = Button(frame, text=str(x+1), \
command=lambda x=x: self.highlight(x))

See the "x=x" gives the lambda an arg whose default value is set to
x at the time the lambda is created, as opposed to when it's called.
 
J

Jay

Perfect. Thanks.


Paul said:
Jay said:
I'm having a problem using lambda to use a command with an argument for
a button in Tkinter.

buttons = range(5)
for x in xrange(5):
self.highlight(x))
buttons[x].pack(side=LEFT)

The buttons are correctly numbered 1 through 5, but no matter which
button I click on, it sends the number 4 as an argument to the
highlight function.

x is not bound by the lambda and so the lambda body gets it from the
outside environment at the time the body is executed. You have to
capture it at the time you create the lambda. There's an ugly but
idiomatic trick in Python usually used for that:

buttons[x] = Button(frame, text=str(x+1), \
command=lambda x=x: self.highlight(x))

See the "x=x" gives the lambda an arg whose default value is set to
x at the time the lambda is created, as opposed to when it's called.
 
B

bearophileHUGS

In that case you don't need a lambda:

import Tkinter as tk

class Test:
def __init__(self, parent):
buttons = [tk.Button(parent, text=str(x+1),
command=self.highlight(x)) for x in range(5)]
for button in buttons:
button.pack(side=tk.LEFT)

def highlight(self, x):
print "highlight", x

root = tk.Tk()
d = Test(root)
root.mainloop()

Bye,
bearophile
 
D

Dustan

Jay said:
Thanks for the tip, but that breaks things later for what I'm doing.

In that case you don't need a lambda:

import Tkinter as tk

class Test:
def __init__(self, parent):
buttons = [tk.Button(parent, text=str(x+1),
command=self.highlight(x)) for x in range(5)]
for button in buttons:
button.pack(side=tk.LEFT)

Well, actually, that's wrong. You obviously don't understand why lambda
is necessary for event binding; in this case (and many others, for that
matter), the button gets bound to the event *returned* by
self.highlight(x), which, since nothing gets returned, would be None.
Then when you click the button, Tkinter calls None(), and out of the
blue, an error is raised.

Lambda is the safe way around that error.
 
J

James Stroud

Dustan said:
Jay said:
Thanks for the tip, but that breaks things later for what I'm doing.

In that case you don't need a lambda:

import Tkinter as tk

class Test:
def __init__(self, parent):
buttons = [tk.Button(parent, text=str(x+1),
command=self.highlight(x)) for x in range(5)]
for button in buttons:
button.pack(side=tk.LEFT)


Well, actually, that's wrong. You obviously don't understand why lambda
is necessary for event binding; in this case (and many others, for that
matter), the button gets bound to the event *returned* by
self.highlight(x), which, since nothing gets returned, would be None.
Then when you click the button, Tkinter calls None(), and out of the
blue, an error is raised.

Lambda is the safe way around that error.

Actually, lambda is not necessary for event binding, but a closure (if I
have the vocab correct), is:

import Tkinter as tk

def make_it(x):
def highliter(x=x):
print "highlight", x
return highliter

class Test:
def __init__(self, parent):
buttons = [tk.Button(parent, text=str(x+1),
command=make_it(x)) for x in range(5)]
for button in buttons:
button.pack(side=tk.LEFT)

root = tk.Tk()
d = Test(root)
root.mainloop()


--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 
P

Paul Rubin

James Stroud said:
Actually, lambda is not necessary for event binding, but a closure (if
I have the vocab correct), is: ...

def make_it(x):
def highliter(x=x):
print "highlight", x
return highliter

For that version you shouldn't need the x=x:

def make_it(x):
def highliter():
print "highlight", x
return highliter

The reason is each time you call make_it, you're creating a new scope
where x has the correct value, and highliter keeps referring to that
scope even after make_it has returned.
 
D

Dustan

James said:
Dustan said:
Jay said:
Thanks for the tip, but that breaks things later for what I'm doing.

(e-mail address removed) wrote:

In that case you don't need a lambda:

import Tkinter as tk

class Test:
def __init__(self, parent):
buttons = [tk.Button(parent, text=str(x+1),
command=self.highlight(x)) for x in range(5)]
for button in buttons:
button.pack(side=tk.LEFT)


Well, actually, that's wrong. You obviously don't understand why lambda
is necessary for event binding; in this case (and many others, for that
matter), the button gets bound to the event *returned* by
self.highlight(x), which, since nothing gets returned, would be None.
Then when you click the button, Tkinter calls None(), and out of the
blue, an error is raised.

Lambda is the safe way around that error.

def highlight(self, x):
print "highlight", x

root = tk.Tk()
d = Test(root)
root.mainloop()

Bye,
bearophile

Actually, lambda is not necessary for event binding, but a closure (if I
have the vocab correct), is:

Of course. What I should have said was "lambda is the quickest and
easiest way". However, if you want your intent to be clear, lambda
isn't always the best option, but it is quite often the quickest.
import Tkinter as tk

def make_it(x):
def highliter(x=x):
print "highlight", x
return highliter

class Test:
def __init__(self, parent):
buttons = [tk.Button(parent, text=str(x+1),
command=make_it(x)) for x in range(5)]
for button in buttons:
button.pack(side=tk.LEFT)

root = tk.Tk()
d = Test(root)
root.mainloop()


--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 

jgm

Joined
Sep 24, 2006
Messages
5
Reaction score
0
Hi,
For me it worked like this:
(with help from Mark Lutz's "programming Python 2nd ed")
Code:
#test
from Tkinter import *
root=Tk()
def highlight(b):
    print 'boton=',b
but=range(5)
for x in xrange(5):
    but[x]=Button(root,text=str(x+1),command=(lambda v=x+1:highlight(v)))
    but[x].pack(side=LEFT)
root.mainloop()

bye,
Jorge
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top