Почему параметр 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()

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