Tkinter Button Command Taking Only Newest Reference In Callback Parameter

Calvin Spealman cspealma at redhat.com
Mon Jul 8 09:40:09 EDT 2019


This is a really common scoping issue to get tripped up on. I'll try to
help you understand and work around it.

for i, icon in enumerate(self.icons):
    print('// entering', icon.program)
    print('icons ... ... ... ...')
    pretty(icon.info, icon.program)
    x = Button(root, text=icon.info['name'] +'\n' + icon.info['version'],
       image=icon.photo,
        compound=LEFT,
        command=lambda: self.button_exec(icon))

On the first line you enter a for loop where each iteration assigns a
different pair of values to the names "i" and "icon".
On the last line you create a lambda, an anonymous function, which you pass
as the command callback for the button. Of course, no code inside the
lambda is actually executed at this point. It is only after the entire for
loop has completed, later when the user sees the buttons and clicks on one
of them, that one of the lambda callbacks for the appropriate button will
be executed.
Now, when this happens, what is the value of "icon"? This involves a
closure, which is how we describe the effect of the lambda (or any
function) being created inside the scope of another function. The scope
here which contains the "icon" variable is kept after the for loop and its
container function is complete, because the lambda you create holds on to
it in order to resolve variable names from. So all the lambda functions
read the "icon" variable from this same scope, and at the time the loop
completes that variable is assigned to the LAST value from the loop. That's
why all the buttons get the same icon.
There is a simple work around, however, called "rebinding". It is a trick
you can do with default values for lambda or any function. Simply pass the
icon as a default value for a parameter to the lambda, and the value at
that time for one iteration of the loop will be bound to the callback you
create.

Just change this

    lambda: self.button_exec(icon)

to this

    lambda icon=icon: self.button_exec(icon)

I hope both the explanation and the example help!


On Mon, Jul 8, 2019 at 1:59 AM Abdur-Rahmaan Janhangeer <
arj.python at gmail.com> wrote:

> Greetings,
>
> I'm doing this project:
>
>
> https://github.com/Abdur-rahmaanJ/cmdlaunch/blob/master/cmdlaunch/cmdlaunch.py
>
> Everything works fine, except for one thing.
>
> On line 60, the button is taking an object in it's command parameter
>
> If you run the file, you'll see that all the different names are written on
> the gui just the callback is takest the newest icon object for all three
> buttons
>
> I know just a reference issue but it's been **bugging** me.
>
> Thanks All
>
> --
> Abdur-Rahmaan Janhangeer
> Mauritius
> --
> https://mail.python.org/mailman/listinfo/python-list
>


-- 

CALVIN SPEALMAN

SENIOR QUALITY ENGINEER

cspealma at redhat.com  M: +1.336.210.5107
[image: https://red.ht/sig] <https://red.ht/sig>
TRIED. TESTED. TRUSTED. <https://redhat.com/trusted>



More information about the Python-list mailing list