Почему параметр Button "команда" выполняется при объявлении?

Мой код:

from Tkinter import *

admin = Tk()
def button(an):
    print an
    print 'het'

b = Button(admin, text='as', command=button('hey'))
b.pack()
mainloop()

Кнопка не работает, она печатает "эй" и "хет" один раз без моей команды, а затем, когда я нажимаю кнопку, ничего не происходит.

6 ответов

command Опция принимает ссылку на функцию, которая является причудливым способом сказать, что вам нужно передать ей имя функции. Когда вы делаете button('hey') вы вызываете функцию button и результат этого дается 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 для хорошего руководства. Охват лямбды довольно скудный, но информация там может быть полезной.

Вам нужно создать функцию без параметров, которые вы можете использовать в качестве команды:

b = Button(admin, text='as', command=lambda: button('hey'))

См. Раздел "Передача аргумента обратным вызовам" этого документа.

Пример 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 опция должна быть установлена ​​как, ссылка на метод, который мы хотим вызвать, аналогично 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 с 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()

Зеркало

Движок оценивает результат функции, когда он присваивает значение в строке "... 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()

Я думаю, что лучший способ решить эту проблему - использовать лямбда-функцию.

from tkinter import *
admin= Tk()
def button(an):
    print(an)
    print("het")
b = Button(admin, text="as", command=lambda: button("hey"))
b.pack()
mainloop()

Если вы не хотите использовать ключевое слово command, вы можете использовать вместо него метод.bind():

from tkinter import *
admin= Tk()
def button(an):
    print(an)
    print("het")
b = Button(admin, text="as")
b.pack()
b.bind("<Button-1>", lambda bb: button("hey"))
mainloop()

Использование материнской функции (без параметров), которая владеет дочерней функцией (по крайней мере, 1 параметром), которую вы хотите вызвать, глупо.

Чтобы поделиться с вами, это одна из моих программ:

import tkinter
window = tkinter.Tk()

def plus_them(field_1, field_2, field_3):
    field_3.delete(0, 'end')
    num1 = 0
    num2 = 0
    try:
        num1 = int(field_1.get())
        num2 = int(field_2.get())
    except:
        print("Exception occurs")
    else:
        print("Continue")
    result = num1 + num2
    field_3.insert(tkinter.END, str(result))
    return result
def minus_them(field_1, field_2, field_3):
    field_3.delete(0, 'end')
    num1 = 0
    num2 = 0
    try:
        num1 = int(field_1.get())
        num2 = int(field_2.get())
    except:
        print("Exception occurs")
    else:
        print("Continue")
    result = num1 - num2
    field_3.insert(tkinter.END, str(result))
    return result

#Input Panel:
label_1 = tkinter.Label(window, text="First Number:")
label_1.grid(row=0, column=0)
label_2 = tkinter.Label(window, text="Second Number:")
label_2.grid(row=1, column=0)
entry_1 = tkinter.Entry(window)
entry_1.grid(row=0, column=1)
entry_2 = tkinter.Entry(window)
entry_2.grid(row=1, column=1)

#Button Panel:
button_1 = tkinter.Button(window, text="Plus")
button_1.grid(row=2, column=0)
button_2 = tkinter.Button(window, text="Minus")
button_2.grid(row=2, column=1)

#Answer Panel:
label_3 = tkinter.Label(window, text="The Answer:")
label_3.grid(row=3, column=0)
entry_3 = tkinter.Entry(window)
entry_3.grid(row=3, column=1)

#Event Handling:
button_1.bind("<Button-1>", lambda p: plus_them(entry_1, entry_2, entry_3))
button_2.bind("<Button-1>", lambda m: minus_them(entry_1, entry_2, entry_3))

#Window Stuff:
window.title("Plus and Minus Calculator")
window.mainloop()

Вот и все.

Не используйте ключевое слово или аргумент в качестве входных данных или скобок для вашей функции. Это очень простое решение:)

Это мое решение:

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

По сути, мы определяем функцию с параметром, а затем вызываем ее внутри функции без параметров.

button('hey') вызывает функцию, а не устанавливает ее в качестве обратного вызова.

Другие вопросы по тегам