Как Tkinter обрабатывает лямбду после события привязки? - PullRequest
0 голосов
/ 11 марта 2019

Я пытаюсь написать код, который будет отправлять значение поля Entry в функцию, основанную на привязке. Технически я могу получить поведение, которое я хочу, в приведенном ниже коде, но я а) не уверен, почему это работает, и б) совершенно уверен, что я делаю это не самым питоническим образом. Я почти уверен, что неправильно понимаю event или lambda или оба.

В коде под окном Entry, называемом input_box1, когда срабатывает привязка, код inp_var1.get() получает только значение по умолчанию, а не все, что было введено в поле. Другими словами, функция test1 напечатает ...

Test1 Foo

... независимо от того, что вы вводите в запись.

Привязка к input_box2 работает именно так, как я ожидаю. Я набираю что-нибудь там и щелкаю где-то еще, и это печатает новую запись. Однако я не понимаю, почему мой lambda не хочет event или почему мне нужно повторить вызов inp_var2.get().

Если кто-нибудь знает, что происходит под капотом, я хотел бы услышать это! Вот код:

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()

1 Ответ

6 голосов
/ 11 марта 2019

Это имеет мало общего с самим Tkinter. Он также не столько связан с lambda, сколько с Python в целом.

Возьмите оба из уравнения и рассмотрите следующую программу на Python:

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

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

Если предположить, что Python 3 (или from __future__ import print_function) при запуске выдает:

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

Это потому, что первый вызов f проходит 0, 0, поэтому x и y оба связаны с нулем. второй вызов f передает только 0, поэтому x связан с 0, а y связан со значением по умолчанию, равным 4. Вызов третий вообще ничего не передает, а x и y оба связаны со значениями по умолчанию.

(Пока все должно быть достаточно ясно.)

Теперь давайте немного с этим суетимся. Я продолжу предполагать Python 3, так что input означает, что в Python 2 мы должны использовать raw_input для достижения:

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()

Прежде чем запускать этот пример программы, подумайте, что вы ожидаете от него. Затем запустите его - вот мой результат:

$ 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

Почему это считывало значения по умолчанию для x и y до того, как мы даже назвали его в первый раз? Почему не считывает новые значения по умолчанию для x и y при каждом вызове?

Подумайте об этом немного, затем прочитайте

Действительно, сделай это. Спросите , почему входы происходили в эти нечетные времена.

Теперь, когда вы об этом подумали ...

Это сделал сделал это, хотя. Это ключ здесь. значения по умолчанию для ваших аргументов фиксируются во время выполнения оператора def . Оператор def, который связывает имя f с нашей функцией, на самом деле является run , когда Python загружает файл, увидев тело функции. Сама функция запускается позже, после того, как Python получил вызов print, а затем первый вызов f.

A lambda в Python - это просто своего рода определение анонимной функции. Вместо:

def square(x):
    return x * x

мы можем написать:

square = lambda x: x * x

Выражение lambda определяет новый функциональный элемент, лямбда-функцию - вы не можете использовать операторы типа if / else внутри одного из них, просто выражения, и они автоматически возвращать значение их выражения. В этом случае наша лямбда-функция имеет один аргумент с именем x. Функция возвращает x * x.

Внешнее присваивание, square =, связывает эту лямбда-функцию с именем square, как если бы мы вместо этого сделали def square(x). Так что это в основном просто синтаксический трюк: у нас может быть реальная функция, такая как square, с аргументами, или ограниченная лямбда-функция, которая может использовать только выражения, как эта анонимная функция, которую мы почти сразу связываем с именем square.

Хотя часть аргументов работает одинаково. Так же, как с f и input, если мы связываем x:

square = lambda x=3: x * x

или

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

это происходит просто один раз , когда выполняется само выражение lambda. Функция теперь содержит переменную x со значением по умолчанию.

Когда позже мы вызовем функцию, мы можем предоставить значение для x или оставить его по умолчанию. Если мы не предоставляем значение, Python использует значение по умолчанию, которое он захватил ранее , когда мы выполняли строку с lambda в нем, а не сейчас, когда мы вызываем лямбда-функцию.

Это относится и к Ткинтеру. Вы написали:

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

но это почти то же самое, что писать:

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

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

за исключением того, что вам не нужно придумывать здесь имя функции f. Лямбда-вариант функции не имеет имени, но это все же просто функция. (В этом отношении, когда мы делаем square = lambda ..., лямбда-функция не имеет имени. Имя square - это имя переменной, а не имя функции. Переменная просто связана с функция, так что square(10) вызывает ее.)

В любом случае, позже , когда Tkinter имеет событие, которое соответствует <FocusOut>, Tkinter вызывает f ... или, если вы использовали lambda, вызывает вашу безымянную лямбда-функцию.Tkinter предоставляет один аргумент этой функции;единственный аргумент, который он предоставляет - это событие.Таким образом, значение по умолчанию для i в f выше не имеет значения - вы можете сделать:

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

или:

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

, потому что когда Tkinter вызывает f, оно всегдапредоставляет фактический аргумент для i.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...