Почему параметр Button "команда" выполняется при объявлении?
Мой код:
from Tkinter import *
admin = Tk()
def button(an):
print an
print 'het'
b = Button(admin, text='as', command=button('hey'))
b.pack()
mainloop()
Кнопка не работает, она печатает "hey" и "het" один раз без моей команды, а затем, когда я нажимаю кнопку, ничего не происходит.
Ответы
Ответ 1
Рассмотрим этот код:
b = Button(admin, text='as', command=button('hey'))
Это точно так же, как это:
result = button('hey')
b = button(admin, text='as', command=result)
Опция command
берет ссылку на функцию, которая является причудливым способом сказать, что вам нужно передать ей имя функции. Чтобы передать ссылку, вы должны использовать только имя, без использования скобок или аргументов. Например:
b = Button(... command = button)
Если вы хотите передать параметр, такой как "эй", вы должны использовать немного дополнительного кода:
- Вы можете создать промежуточную функцию, которая может быть вызвана без вашего аргумента и которая затем вызывает вашу функцию
button
, - Вы можете использовать
lambda
для создания так называемой анонимной функции. Во всех отношениях это функция, кроме как без имени. Когда вы вызываете команду lambda
она возвращает ссылку на созданную функцию, что означает, что она может использоваться для значения параметра command
для кнопки. - Вы можете использовать functools.partial
Для меня lambda
- это самое простое, поскольку она не требует дополнительного импорта, как это делает functools.partial
, хотя некоторые люди считают, что functools.partial
легче понять.
Чтобы создать лямбда-функцию, которая вызывает вашу функцию button
с аргументом, вы должны сделать что-то вроде этого:
lambda: button('hey')
В итоге вы получите функцию, которая функционально эквивалентна:
def some_name():
button('hey')
Как я уже говорил ранее, lambda
возвращает ссылку на эту безымянную функцию. Поскольку ссылка - это то, что ожидает параметр command
вы можете использовать lambda
непосредственно при создании кнопки:
b = Button(... command = lambda: button('hey'))
На этом сайте есть вопрос, который имеет много интересных комментариев о лямбде в целом. Посмотрите вопрос, почему Python лямбды полезны? , В том же обсуждении есть ответ, который показывает, как использовать лямбда-выражения в цикле, когда вам нужно передать переменную в обратный вызов.
Наконец, обратитесь к разделу " Обратные вызовы Tkinter" на effbot.org для хорошего руководства. Охват лямбды довольно скудный, но информация там может быть полезной.
Ответ 2
Вам нужно создать функцию без параметров, которые вы можете использовать в качестве команды:
b = Button(admin, text='as', command=lambda: button('hey'))
См. раздел "Передача аргументов в обратные вызовы" этот документ.
Ответ 3
Пример GUI:
Допустим, у меня есть графический интерфейс:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
Что происходит при нажатии кнопки
Обратите внимание, что при нажатии btn
он вызывает свою собственную функцию, которая очень похожа на button_press_handle
в следующем примере:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
с:
button_press_handle(btn['command'])
Вы можете просто подумать, что параметр command
должен быть установлен как ссылка на метод, который мы хотим вызвать, аналогично button_press_handle
callback
в button_press_handle
.
Вызов метода (обратный вызов) при нажатии кнопки
Без аргументов
Поэтому, если бы я хотел print
что-то, когда кнопка нажата, мне нужно было бы установить:
btn['command'] = print # default to print is new line
Обратите особое внимание на отсутствие ()
в методе print
который опущен в следующем значении: "Это имя метода, которое я хочу, чтобы вы вызывали при нажатии, но не вызывайте его только в этот момент". Однако я не передавал никаких аргументов для print
поэтому она печатала все, что печатает, когда вызывается без аргументов.
С аргументом (ами)
Теперь, если бы я хотел также передать аргументы методу, который я хочу вызывать при нажатии кнопки, я мог бы использовать анонимные функции, которые могут быть созданы с помощью оператора lambda, в данном случае для встроенного метода print
, такого как следующий:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Вызов нескольких методов при нажатии кнопки
Без аргументов
Вы также можете добиться этого, используя lambda
оператор, но это считается плохой практикой, и поэтому я не буду его здесь включать. Хорошей практикой является определение отдельного метода multiple_methods
методов, который вызывает требуемые методы, а затем установка его в качестве обратного вызова для нажатия кнопки:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
С аргументом (ами)
Чтобы передать аргумент методу, который вызывает другие методы, снова используйте lambda
оператор, но сначала:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
и затем установите:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Возврат объекта (ов) из обратного вызова
Также обратите внимание, что callback
не может действительно return
потому что он button_press_handle
только внутри button_press_handle
с callback()
а не return callback()
. Он return
но не за пределами этой функции. Таким образом, вы должны скорее изменить объект (ы), которые доступны в текущей области.
Завершите пример с глобальной модификацией объектов
Ниже приведен пример вызова метода, который изменяет текст btn
каждом нажатии кнопки:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
Зеркало
Ответ 4
Движок оценивает результат функции, когда он присваивает значение в строке "... command =..."
"Команда" ожидает, что функция будет возвращена, поэтому использование лямбды может сделать эту работу, потому что она создает аномимную функцию, которая возвращается "команде" во время оценки. Вы также можете написать свою собственную функцию, она тоже сделает свою работу.
это пример с лямбдой и без лямбды:
#!/usr/bin/python
# coding=utf-8
from Tkinter import *
# Creation de la fenêtre principale (main window)
Mafenetre = Tk()
res1 = StringVar()
res2 = StringVar()
def isValidInput(obj):
if hasattr(obj, 'get') and callable(getattr(obj, 'get')):
return TRUE
return FALSE
# stupid action 2 (return 12 on purpose to show potential mistake)
def action1(*arguments):
print "action1 running"
for arg in arguments:
if isValidInput(arg):
print "input value: ", arg.get()
res1.set(arg.get())
else:
print "other value:", arg
print "\n"
return 12
# stupid action 2
def action2(*arguments):
print "action2 running"
a = arguments[0]
b = arguments[1]
if isValidInput(a) and isValidInput(b):
c = a.get() + b.get()
res2.set(c)
print c
print "\n"
# a stupid workflow manager ordered by name
def start_tasks(*arguments, **keywords):
keys = sorted(keywords.keys())
for kw in keys:
print kw, "plugged "
keywords[kw](*arguments)
# valid callback wrapper with lambda
def action1_callback(my_input):
return lambda args=[my_input]: action1(*args)
# valid callback wrapper without lambda
def action1_callback_nolambda(*args, **kw):
def anon():
action1(*args)
return anon
# first input string
input1 = StringVar()
input1.set("delete me...")
f1 = Entry(Mafenetre, textvariable=input1, bg='bisque', fg='maroon')
f1.focus_set()
f1.pack(fill="both", expand="yes", padx="5", pady=5)
# failed callback because the action1 function is evaluated, it will return 12.
# in this case the button won't work at all, because the assignement expect a function
# in order to have the button command to execute something
ba1 = Button(Mafenetre)
ba1['text'] = "show input 1 (ko)"
ba1['command'] = action1(input1)
ba1.pack(fill="both", expand="yes", padx="5", pady=5)
# working button using a wrapper
ba3 = Button(Mafenetre)
ba3['text'] = "show input 1 (ok)"
# without a lambda it is also working if the assignment is a function
#ba1['command'] = action1_callback_nolambda(input1)
ba3['command'] = action1_callback(input1)
ba3.pack(fill="both", expand="yes", padx="5", pady=5)
# display result label
Label1 = Label(Mafenetre, text="Action 1 result:")
Label1.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl1 = Label(Mafenetre, textvariable=res1)
resl1.pack(fill="both", expand="yes", padx="5", pady=5)
# second input string
input2 = StringVar()
f2 = Entry(Mafenetre, textvariable=input2, bg='bisque', fg='maroon')
f2.focus_set()
f2.pack(fill="both", expand="yes", padx="5", pady=5)
# third test without wrapper, but making sure that several arguments are well handled by a lambda function
ba2 = Button(Mafenetre)
ba2['text'] = "execute action 2"
ba2['command'] = lambda args=[input1, input2], action=action2: start_tasks(*args, do=action)
ba2.pack(fill="both", expand="yes", padx="5", pady=5)
# display result label
Label2 = Label(Mafenetre, text="Action 2 result:")
Label2.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl2 = Label(Mafenetre, textvariable=res2)
resl2.pack(fill="both", expand="yes", padx="5", pady=5)
Mafenetre.mainloop()
Ответ 5
Не используйте ключевое слово или аргумент в качестве входных данных или скобок для вашей функции. Это очень простое решение.
Ответ 6
button('hey')
вызывает функцию, а не устанавливает ее как обратный вызов.
Ответ 7
Это мое решение:
from tkinter import *
admin = Tk()
def button(an):
print(an)
print("het")
def param():
button("hey")
button1 = Button(admin, text = "press", command = param)
button1.pack()
По сути, мы определяем функцию с параметром, а затем вызываем ее внутри функции без параметров.