0

I have a list of names in a cascading menu bar and when you click a name, I want the associated ID to be passed to a function. For example if you click 'Test1' it passes the id 4556767 to a function.

I can't figure out how to do it. I have tried using enumerate to pass an integer to a lambda function as the 'command' argument and then setting up a list of ids that corresponds to the indexes of the menu items. This however didn't work as the enumerate variable of course changes with each iteration so in the end every item returns the same value.

Is there a way to make that variable stay permanently and not change as if it was hard coded in and not a variable?

Here is my current code:

blogs = database_handler.get_blogs()                                    
self.blog_menu_ids = []                                                 

for i, blog in enumerate(blogs):                                        
    self.blog_menu_ids.append(blog[0])                                  
    self.menu_blogs.add_command(label=blog[1],                          
                                command=lambda: self.load_blog(i)) 

Although if I could do that, I could just pass the id right away like so:

blogs = database_handler.get_blogs()                                               

for blog in blogs:                                                      
    self.menu_blogs.add_command(label=blog[1],                          
                                command=lambda: self.load_blog(blog[0])) 
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
Jamal Moir
  • 66
  • 9

2 Answers2

3

You want "early binding" which we can do using "partial function application":

from functools import partial

for i, blog in enumerate(blogs):
    self.blog_menu_ids.append(blog[0])
    self.menu_blogs.add_command(label=blog[1],
                                command=partial(self.load_blog, i))

Or following your second example:

for blog in blogs:
    self.menu_blogs.add_command(label=blog[1],   
                                command=partial(self.load_blog, blog[0]))
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Perfect, works a charm. Thank you. – Jamal Moir May 11 '16 at 12:53
  • @JamalMoir this should do the same thing as the `lambda` expression you are already using, how is this different? – Tadhg McDonald-Jensen May 11 '16 at 12:54
  • 1
    It doesn't do the same, with the previous one the lambda function passed the value of i, which at the end of the loop once the GUI had been generated was x. So every button you clicked brought up the id x. – Jamal Moir May 11 '16 at 12:57
  • 1
    @TadhgMcDonald-Jensen: For why it's not the same, see here: http://stackoverflow.com/a/3252364/4323 – John Zwinck May 11 '16 at 12:57
  • 1
    You don't need to use `partial` to do this. See my comment under the question. – martineau May 11 '16 at 13:31
  • 1
    @martineau: That's true, you can use the lambda default argument hack. I just prefer `partial`. – John Zwinck May 11 '16 at 13:41
  • @JohnZwinck: I don't consider using a default argument any more of a hack than using closures on arguments to `partial`. It's essentially the same thing with less overhead. – martineau May 11 '16 at 15:06
  • 1
    @martineau `partial` is optimized when wrapping another partial and optimizes partials of other partials and the C implementation doesn't need an extra frame in the stack when calling. Lambda might be less overhead when creating the wrapper but it does add more overhead when using it. (which usually happens a lot more btw) – Tadhg McDonald-Jensen May 11 '16 at 17:20
  • @TadhgMcDonald-Jensen: It's unclear why you go on and on about optimizing "partials of partials", since that doesn't apply — there's only one level — here. However you're indeed correct that calling the partial object executes faster than calling the lambda equivalent. Whether that's significant or not depends, of course, on how many times it's called as well as on how long the target function itself takes to run. – martineau May 12 '16 at 06:51
  • @martineau sorry the repeat of partials of partials was because of bad editing on my part, but it happen where `self.load_blog` is a partial of a more generalized `self.load_resource` with some options already specified. I won't go into detail about my cascading tkinter code that had more `lambda` wrapper entries in the error messages then actual function calls but needless to say I have been a big fan of partials ever since. ;) – Tadhg McDonald-Jensen May 12 '16 at 15:09
0

I don't understand your question completely it seems, but aren't you already very close?

for blog in blogs:                                                                         
    self.menu_blogs.add_command(label=blog[1], command=lambda: self.load_blog(blog[0]))

I assumed blog[0] holds the ID and blog[1] is the name of it.

EDIT: I see you edited so we now have the same answer.

avanwieringen
  • 2,404
  • 3
  • 21
  • 28