471,071 Members | 8,731 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 471,071 software developers and data experts.

Tkinter Puzzler

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?
TIA,
----------------------------------------------------------------------------
Tim Daneliuk tu****@tundraware.com
PGP Key: http://www.tundraware.com/PGP/
Jul 18 '05 #1
4 1184
Tim Daneliuk 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?


This has nothing to do with Tkinter, but only with the way nested scopes work:
to put it roughly, your "lambda: func(None)" only knows about the *name* func,
which is not actually evaluated until the button is pressed. And when the button
is pressed, the name func is bound to the last command in the loop.

To do what you want, change your code to:

for entry in (...):
UI.ShortBtn.menu.add_command(
label=entry[0],
command=lambda func=entry[1]: func(None)
)

This way, the value for the func parameter is evaluated when the function is
defined and not when it is called.

HTH
--
- Eric Brunel <eric (underscore) brunel (at) despammed (dot) com> -
PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com
Jul 18 '05 #2
Le 07 Jan 2005 05:28:31 EST, Tim Daneliuk a écrit :
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)) The problem is that you *call* the callback : the command parameter is
bound to the result of 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? I would simplify the code like ;

add_cmd = UI.ShortBtn.menu.add_command
for label, func in (("Up", KeyUpDir), .... ):
add_cmd(label=label, command=func)

And have
def KeyUpDir(arg=None):
# whatever


TIA,
----------------------------------------------------------------------------
Tim Daneliuk tu****@tundraware.com
PGP Key: http://www.tundraware.com/PGP/

Jul 18 '05 #3
Tim Daneliuk <tu****@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
Jul 18 '05 #4
Tim Daneliuk 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?
TIA,
----------------------------------------------------------------------------

Tim Daneliuk tu****@tundraware.com
PGP Key: http://www.tundraware.com/PGP/


Thanks All - great responses!

--
----------------------------------------------------------------------------
Tim Daneliuk tu****@tundraware.com
PGP Key: http://www.tundraware.com/PGP/
Jul 18 '05 #5

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

3 posts views Thread by srijit | last post: by
5 posts views Thread by Paul Rubin | last post: by
1 post views Thread by syed_saqib_ali | last post: by
reply views Thread by syed_saqib_ali | last post: by
1 post views Thread by Michael Yanowitz | last post: by
3 posts views Thread by dwelch91 | last post: by
8 posts views Thread by karthikbalaguru | last post: by
3 posts views Thread by joshdw4 | last post: by
reply views Thread by leo001 | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.