Почему параметр Button «команда» выполняется при объявлении? - PullRequest
57 голосов
/ 24 апреля 2011

Мой код:

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» один раз без моей команды, а затем, когда я нажимаю кнопку, ничего не происходит.

Ответы [ 7 ]

67 голосов
/ 24 апреля 2011

Рассмотрим этот код:

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 . Охват лямбды довольно скудный, но информация там может быть полезной.

11 голосов
/ 24 апреля 2011

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

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

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

5 голосов
/ 26 декабря 2017

Пример графического интерфейса пользователя:

Допустим, у меня есть графический интерфейс пользователя:

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.


Вызов метода ( Callback ) Когдакнопка нажата

без аргументов

Так что, если бы я хотел print что-то при нажатии кнопки, мне нужно было бы установить:

btn['command'] = print # default to print is new line

Обратите пристальное внимание на отсутствие из () с методом print, который опущен в значении, что: "Это имя метода, которое яхочу, чтобы вы звонили при нажатии , но не вызывайте его только в этот самый момент. " Однако я не передал никаких аргументов для print, поэтому он печатаетсяd все, что он печатает при вызове без аргументов.

с Аргумент (ы)

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


Завершить пример с global Модификация (и) объекта

В следующем примере будет вызываться метод, который изменяет текст 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()

Зеркало

2 голосов
/ 01 ноября 2018

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

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

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

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

0 голосов
/ 27 июля 2017

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

0 голосов
/ 24 апреля 2011

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

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