Tkinter Puzzler

Alex Martelli aleaxit at yahoo.com
Fri Jan 7 06:15:29 EST 2005


Tim Daneliuk <tundra at tundraware.com> wrote:

> I am trying to initialize a menu in the following manner:
> 
> for entry in [("Up", KeyUpDir), ("Back", KeyBackDir), ("Home",
> KeyHomeDir), ("Startdir", KeyStartDir), ("Root", KeyRootDir)]:
> 
>      func = entry[1]
>      UI.ShortBtn.menu.add_command(label=entry[0], command=lambda: func(None))
> 
> However, at runtime, each of the menu options binds to the *last* function
> named in the list (KeyStartDir).
> 
> Explicitly loading each entry on its own line works fine:
> 
> UI........command=lambda:KeyWHATEVERDir(None)
> 
> Any ideas why the first form does not fly?

One word: late binding.  Well, two, pedantically speaking;-).

The lambda you're passing as the value for 'command' is a closure: it
knows it will have to look up name 'func' in the environment in which
it's embedded -- but also that it's meant to do that lookup as late as
possible, each time it's called.

If you wanted to do the lookup just once, at the time lambda executes
and created an anonymous function rather than each time said anonymous
function is called, you could have expressed that...:
    command=lambda func=func: func(None)
Here, func is a local variable (argument) of the anonymous function, and
its "default value" is set ONCE, when the anon function is created.

Back to your code, when your anon function is called, it looks up name
'func' in the surrounding environment... and there it finds it bound to
whatever it was RE-bound to the LAST time...


Point to remember: a closure looks up free-variable names in its
surrounding environment *as late as possible*, i.e., when the function
object is called; while default argument values are evaluated *at
function creation time* (when lambda or def executes, not when the
resulting function object gets called).


Alex



More information about the Python-list mailing list