Lambdas and variables

J

John Fouhy

So I'm trying to generate Tkinter callback functions on the fly, but
it's not working, and I don't understand what's going on.

Here is an example program:

--------------------------
from Tkinter import *

def printSomething(x):
print x

tk = Tk()
stuff = ['foo', 'bar', 'baz', 'zif', 'zaf', 'zof']

for x in stuff:
l = Label(tk, text=x)
l.pack()
l.bind('<Enter>', lambda e: printSomething(x))

tk.mainloop()
--------------------------

The desired behaviour is that the program should print to stdout the
text on the label whenever the mouse enters that widget.

However, what actually happens is that it prints out 'zof' whenever
the mouse enters any of the widgets.

To further confuse me, if I add a bit of indirection:

--------------------------
from Tkinter import *

def makeFunction(x):
return lambda e: printSomething(x)

def printSomething(x):
print x

tk = Tk()
stuff = ['foo', 'bar', 'baz', 'zif', 'zaf', 'zof']

for x in stuff:
l = Lable(tk, text=x)
l.pack()
l.bind('<Enter>', makeFunction(x))

tk.mainloop()
--------------------------

....then everything works as it should.

I am running Python 2.3.4.

Can anyone explain this to me?
 
D

Derek Thomson

So I'm trying to generate Tkinter callback functions on the fly, but
it's not working, and I don't understand what's going on.

[snip examples]

Can anyone explain this to me?

I'll have a go.

It's to do with the environment to which a function, or a lambda, is
bound. "x" in the lambda will always refer to the x defined in the
enclosing scope, and you last left that set to "zof".

The next case with an explicit function makes it all better because
now the printSomething function has a reference to the actual element
you wish to use via it's argument.

Hope that helps!

dt.
 
J

James Henderson

John said:
So I'm trying to generate Tkinter callback functions on the fly, but
it's not working, and I don't understand what's going on.

Here is an example program:

--------------------------
from Tkinter import *

def printSomething(x):
print x

tk = Tk()
stuff = ['foo', 'bar', 'baz', 'zif', 'zaf', 'zof']

for x in stuff:
l = Label(tk, text=x)
l.pack()
l.bind('<Enter>', lambda e: printSomething(x))

tk.mainloop()
--------------------------

The desired behaviour is that the program should print to stdout the
text on the label whenever the mouse enters that widget.

However, what actually happens is that it prints out 'zof' whenever
the mouse enters any of the widgets.

To further confuse me, if I add a bit of indirection:

--------------------------
from Tkinter import *

def makeFunction(x):
return lambda e: printSomething(x)

def printSomething(x):
print x

tk = Tk()
stuff = ['foo', 'bar', 'baz', 'zif', 'zaf', 'zof']

for x in stuff:
l = Lable(tk, text=x)
l.pack()
l.bind('<Enter>', makeFunction(x))

tk.mainloop()
--------------------------

...then everything works as it should.

I am running Python 2.3.4.

Can anyone explain this to me?

Derek has answered your question, but you might also be interested to
know that your first example would work with one small change:

for x in stuff:
l = Label(tk, text=x)
l.pack()
l.bind('<Enter>', lambda e, x=x: printSomething(x))

That is, "x" is passed as a default parameter to the lambda, which is
slightly more concise that your defining of an extra function. This
works because default parameters are evaluated once and for all at the
time of a function's definition.

James
 
J

John Fouhy

[original post: http://groups.google.co.nz/[email protected]
]

Derek Thomson said:
It's to do with the environment to which a function, or a lambda, is
bound. "x" in the lambda will always refer to the x defined in the
enclosing scope, and you last left that set to "zof".

Ok, so when I write 'lambda e: printSomething(x)', that creates a
function which, when called, will look up the value of the variable
'x', and then call printSomething on that value. And that value will
be whatever x was last set to. Is that correct?

What if I do 'lambda e: printSomething(copy.deepcopy(x))'? That
creates a function which, when called, will look up the value of 'x',
make a deep copy, and then call printSomething on it?

('cos that doesn't work either)

If what I wrote is correct, then I think I understand now. And I will
use the change James Henderson suggested. But it definitely feels
like a gotcha if I can't replace a function that just does 'return X'
with 'X' itself.

(thanks for your help, guys)
 
J

James Henderson

John said:
[original post: http://groups.google.co.nz/[email protected]
]

Derek Thomson said:
It's to do with the environment to which a function, or a lambda, is
bound. "x" in the lambda will always refer to the x defined in the
enclosing scope, and you last left that set to "zof".

Ok, so when I write 'lambda e: printSomething(x)', that creates a
function which, when called, will look up the value of the variable
'x', and then call printSomething on that value. And that value will
be whatever x was last set to. Is that correct?

That's right, and it's an example of "Python's general late-binding
semantics", which came under so much discussion recently with respect to
generator expressions. See:

http://mail.python.org/pipermail/python-dev/2004-April/044555.html

and the surrounding threads.
What if I do 'lambda e: printSomething(copy.deepcopy(x))'? That
creates a function which, when called, will look up the value of 'x',
make a deep copy, and then call printSomething on it?

('cos that doesn't work either)

The copying won't be done till the function is called and you will still
get whatever x was last set to in the lexical scope. But I see you're
trying to associate a piece of data with a function - to create a sort
of closure - and that's exactly where the default argument trick comes
in. Default arguments can be used for this effect since they freeze the
value at the time of the function's definition.
If what I wrote is correct, then I think I understand now. And I will
use the change James Henderson suggested. But it definitely feels
like a gotcha if I can't replace a function that just does 'return X'
with 'X' itself.

I agree it's a gotcha. Interestingly you would have been forced to pass
x in as a default argument and avoided your problem until Python
introduced nested scopes in versions 2.1 and 2.2. You may find PEP 227
on statically nested scopes illuminating:

http://www.python.org/peps/pep-0227.html

James
 
T

Terry Reedy

John Fouhy said:
[original post: http://groups.google.co.nz/[email protected]
]

Derek Thomson <[email protected]> wrote in message
It's to do with the environment to which a function, or a lambda, is
bound. "x" in the lambda will always refer to the x defined in the
enclosing scope, and you last left that set to "zof".

Ok, so when I write 'lambda e: printSomething(x)', that creates a
function which, when called, will look up the value of the variable
'x', and then call printSomething on that value. And that value will
be whatever x was last set to. Is that correct?

To the best of my knowledge,

func = lambda <params>: <expression>

is almost exactly equivalent to and an abbreviation for

def func(<params>): return <expression>

(The main difference that I know of is in func.__name__ and the consequent
difference in repr(func). If someone knows of something more of
significance, please tell me.) So, if you understand simple functions, you
can understand lambdas -- without asking ;-).

Terry J. Reedy
 
J

John Fouhy

James Henderson said:
I agree it's a gotcha. Interestingly you would have been forced to pass
x in as a default argument and avoided your problem until Python
introduced nested scopes in versions 2.1 and 2.2. You may find PEP 227
on statically nested scopes illuminating:

http://www.python.org/peps/pep-0227.html

Heh.

""" The proposed
solution, in crude terms, implements the default argument approach
automatically. The "root=root" argument can be omitted. """

Ok, I've got it now (famous last words).

Thanks for your help :)
 

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

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,902
Latest member
Elena68X5

Latest Threads

Top