简体   繁体   中英

How is Tkinter handling lambda after binding event?

I am trying to write some code that will send the value of an Entry box to a function based on a binding. I can technically get the behavior I want in the code below, but I'm a) not sure why it works and b) am quite sure I am not doing this in the most pythonic way. I'm pretty sure I'm misunderstanding the event or lambda or both.

In the code below the Entry box called input_box1 when the binding triggers, the inp_var1.get() code only gets the default value, not anything that has been entered into the box. In other words, the test1 function will print...

Test1 Foo

... no matter what you type into the entry.

The binding on input_box2 works exactly as I expect. I type anything in there and click somewhere else and it prints the new entry. However I don't understand why my lambda doesn't want an event or why I need to repeat the inp_var2.get() call.

If anyone knows what's going under the hood I'd love to hear it! Here's the code:

from tkinter import *
from tkinter import ttk

def test1(event, i):
    print('Test1', i)

def test2(event, i):
    print('Test2', i)

root = Tk()
title_label = Label(root, text='This does not work!')
title_label.grid(column=0, row=0)

inp_var1 = StringVar(value='Foo')
input_box1 = Entry(root, textvariable=inp_var1)
input_box1.grid(column=0, row=1)

inp_var2 = StringVar(value='Bar')
input_box2 = Entry(root, textvariable=inp_var2)
input_box2.grid(column=0, row=2)

input_box1.bind('<FocusOut>', lambda event, i=inp_var1.get(): test1(event, i))
input_box2.bind('<FocusOut>', lambda i=inp_var2.get(): test2(i, inp_var2.get()))

root.mainloop()

This has very little to do with Tkinter itself. It's also not so much connected to lambda as it is to Python in general.

Take both of those out of the equation, and consider the following Python program:

def f(x=3, y=4):
    print('x =', x, 'y =', y)

f(0, 0)
f(0)
f()

Assuming Python 3 (or from __future__ import print_function ), when run, this prints:

x = 0 y = 0
x = 0 y = 4
x = 3 y = 4

That's because the first call to f passes 0, 0 , so x and y are both bound to zero. The second call to f passes just 0 so x is bound to 0 and y is bound to its default value of 4. The third call passes nothing at all and x and y are both bound to their default values.

(So far, this should all be clear enough.)

Now let's fuss with this a bit. I'll continue to assume Python 3 so that input means what in Python 2 we have to use raw_input to achieve:

def f(x=input('enter default x: '), y=input('enter default y: ')):
    print('x =', x, 'y =', y)

print('about to call f three times')
f(0, 0)
f(0)
f()

Before you run this sample program, think about what you expect it to do. Then run it—here's my result:

$ python3 t.py
enter default x: hello
enter default y: world
about to call f three times
x = 0 y = 0
x = 0 y = world
x = hello y = world

Why did this read the default values for x and y before we even called it the first time? Why didn't it read new default values for x and y on each call?

Think about that for a bit, then read on

Really, do that. Ask why the inputs happened at these odd times.

Now that you've thought about it...

It did do that, though. That's the key here. The default values for your arguments are captured at the time the def statement is executed . The def statement, which binds the name f to our function, is actually run when Python loads the file, after seeing the body of the function. The function itself is run later, after Python has gotten to the print call and then to the first f call.

A lambda in Python is just a sort of anonymous function definition. Instead of:

def square(x):
    return x * x

we can write:

square = lambda x: x * x

The lambda expression defines a new function-like item, a lambda function —you can't use if / else type statements inside one of these, just expressions, and they automatically return the value of their expression. In this case our lambda function has one argument named x . The function returns x * x .

The outer assignment, square = , binds this lambda function to the name square , just as if we'd done def square(x) instead. So it's mostly just a syntactic trick: we can have a real function, like square , with arguments, or a limited lambda function that can only use expressions, like this anonymous function that we almost immediately bind to the name square .

The arguments part works the same either way though. Just as with f and input , if we bind x :

square = lambda x=3: x * x

or:

square = lambda x=int(input('choose a default for x now> ')): x * x

that happens just once , when the lambda expression itself executes. The function now has a variable x with a default value.

When, later, we call the function, we can provide a value for x , or let it default. If we don't provide a value, Python uses the default that it captured earlier , when we executed the line with lambda in it, rather than now, when we call the lambda function.

This all holds for Tkinter as well. You wrote:

input_box2.bind('<FocusOut>', lambda i=inp_var2.get(): test2(i, inp_var2.get()))

but that's pretty much the same as writing:

def f(i=inp_var2.get()):
    test2(i, inp_var2.get())

input_box2.bind('<FocusOut>', f)

except that you don't have to come up with the function-name f here. The lambda variant of the function has no name, but it's still just a function. (For that matter, when we do square = lambda ... , the lambda function doesn't have a name. The name square is the name of a variable, not the name of the function. The variable is merely bound to the function, so that square(10) calls it.)

Anyway, later , when Tkinter has an event that matches <FocusOut> , Tkinter calls f ... or, if you used lambda , calls your unnamed lambda function. Tkinter provides one argument to that function; the one argument it provides is the event. So your default value for i in f above is irrelevant—you could do:

def f(i=None):
    test2(i, inp_var2.get())

or:

def f(i='hello world'):
    test2(i, inp_var2.get())

because when Tkinter calls f , it always provides an actual argument for i .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM