Warum wird der Button-Parameter "Befehl" ausgeführt, wenn er deklariert wird?

74

Mein Code lautet:

from Tkinter import *

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

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

Die Taste funktioniert nicht, sie gibt einmal ohne meinen Befehl 'hey' und 'het' aus, und wenn ich die Taste drücke, passiert nichts.

salk
quelle
10
@ Mike-SMT Genau deshalb. Ich möchte Leute dafür belohnen, dass sie gute Antworten auf häufig gestellte Fragen veröffentlichen - insbesondere, wenn die Fragen einfach sind. Viele Leute geben halbherzige, unmotivierte Antworten auf einfache Fragen. Ich möchte, dass die Leute erkennen, dass Sie kein Programmierexperte sein müssen, um herausragende Antworten zu schreiben.
Aran-Fey

Antworten:

94

Betrachten Sie diesen Code:

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

Es macht genau das Gleiche wie folgt:

result = button('hey')
b = button(admin, text='as', command=result)

Die commandOption verweist auf eine Funktion. Dies ist eine ausgefallene Art zu sagen, dass Sie ihr den Namen der Funktion übergeben müssen. Um eine Referenz zu übergeben, dürfen Sie nur den Namen verwenden, ohne Klammern oder Argumente zu verwenden. Zum Beispiel:

b = Button(... command = button)

Wenn Sie einen Parameter wie "hey" übergeben möchten, müssen Sie einen kleinen zusätzlichen Code verwenden:

  • Sie können eine Zwischenfunktion erstellen, die ohne Ihr Argument aufgerufen werden kann und die dann Ihre buttonFunktion aufruft .
  • Sie können damit lambdaeine so genannte anonyme Funktion erstellen . In jeder Hinsicht ist es eine Funktion, außer dass sie keinen Namen hat. Wenn Sie den lambdaBefehl aufrufen, wird ein Verweis auf die erstellte Funktion zurückgegeben. Dies bedeutet, dass er für den Wert der commandOption für die Schaltfläche verwendet werden kann.
  • Sie können functools.partial verwenden

Für mich lambdaist es das einfachste, da es keine zusätzlichen Importe functools.partialerfordert, obwohl einige Leute denken, dass functools.partialdas leichter zu verstehen ist.

Um eine Lambda-Funktion zu erstellen, die Ihre buttonFunktion mit einem Argument aufruft, gehen Sie folgendermaßen vor :

lambda: button('hey')

Sie erhalten eine Funktion, die funktional äquivalent ist zu:

def some_name():
    button('hey')

Gibt, wie bereits erwähnt, lambdaeinen Verweis auf diese namenlose Funktion zurück. Da die Referenz eine Referenz commanderwartet, können Sie diese lambdadirekt beim Erstellen der Schaltfläche verwenden:

b = Button(... command = lambda: button('hey'))

Es gibt eine Frage auf dieser Seite, die viele interessante Kommentare zu Lambda im Allgemeinen enthält. Siehe die Frage Warum Python Lambdas nützlich sind? . Dieselbe Diskussion enthält eine Antwort, die zeigt, wie Lambdas in einer Schleife verwendet werden, wenn Sie eine Variable an den Rückruf übergeben müssen.

Schließlich finden Sie im Abschnitt mit dem Titel Tkinter Callbacks auf effbot.org ein nettes Tutorial. Die Abdeckung von Lambda ist ziemlich mager, aber die Informationen dort könnten immer noch nützlich sein.

Bryan Oakley
quelle
12

Sie müssen eine Funktion ohne Parameter erstellen, die Sie als Befehl verwenden können:

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

Weitere Informationen finden Sie im Abschnitt "Übergeben von Argumenten an Rückrufe" in diesem Dokument .

Lukáš Lalinský
quelle
7

Beispiel GUI:

Angenommen, ich habe die GUI:

import tkinter as tk

root = tk.Tk()

btn = tk.Button(root, text="Press")
btn.pack()

root.mainloop()

Was passiert, wenn eine Taste gedrückt wird?

Beachten Sie, dass beim btnDrücken eine eigene Funktion aufgerufen wird, die der button_press_handleim folgenden Beispiel sehr ähnlich ist :

def button_press_handle(callback=None):
    if callback:
        callback() # Where exactly the method assigned to btn['command'] is being callled

mit:

button_press_handle(btn['command'])

Sie können sich einfach vorstellen, dass diese commandOption als Verweis auf die Methode festgelegt werden sollte, die aufgerufen werden soll, ähnlich wie callbackin button_press_handle.


Aufrufen einer Methode ( Rückruf ) Wenn die Taste gedrückt wird

Ohne Argumente

Wenn ich also printetwas wollte, wenn die Taste gedrückt wird, müsste ich Folgendes einstellen:

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

Achten Sie auf das Fehlen von ()mit dem printVerfahren , das im Sinne weggelassen wird , dass: „Das ist der Name der Methode, die ich Sie wollen , wenn sie gedrückt nennen , aber . Sie es nicht nur diesem Augenblick nennen“ Ich habe jedoch keine Argumente für das übergeben, printsodass gedruckt wurde, was auch immer gedruckt wird, wenn es ohne Argumente aufgerufen wird.

Mit Argument (en)

Wenn ich nun auch Argumente an die Methode übergeben möchte, die beim Drücken der Schaltfläche aufgerufen werden soll, kann ich die anonymen Funktionen verwenden, die mit der Lambda- Anweisung erstellt werden können, in diesem Fall für die printintegrierte Methode wie die folgende ::

btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)

Aufrufen mehrerer Methoden, wenn die Taste gedrückt wird

Ohne Argumente

Sie können dies auch mit der lambdaAnweisung erreichen, aber es wird als schlechte Praxis angesehen und daher werde ich es hier nicht aufnehmen. Es empfiehlt sich, eine separate Methode zu definieren multiple_methods, die die gewünschten Methoden aufruft und diese dann als Rückruf für den Tastendruck festlegt:

def multiple_methods():
    print("Vicariously") # the first inner callback
    print("I") # another inner callback

Mit Argument (en)

Um Argumente an Methoden zu übergeben, die andere Methoden aufrufen, verwenden Sie erneut die lambdaAnweisung, aber zuerst:

def multiple_methods(*args, **kwargs):
    print(args[0]) # the first inner callback
    print(kwargs['opt1']) # another inner callback

und dann setzen:

btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)

Rückgabe von Objekten aus dem Rückruf

Beachten Sie auch, dass callbackdies nicht wirklich möglich ist, returnda es nur von innen button_press_handlemit callback()im Gegensatz zu aufgerufen wird return callback(). Es tut returnaber nicht überall außerhalb dieser Funktion. Daher sollten Sie lieber Objekte ändern , auf die im aktuellen Bereich zugegriffen werden kann.


Vollständiges Beispiel mit globalen Objektmodifikationen.

Das folgende Beispiel ruft eine Methode auf, die btnden Text jedes Mal ändert , wenn die Taste gedrückt wird:

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

Spiegel

Nae
quelle
1

Die Engine wertet das Ergebnis der Funktion aus, wenn sie den Wert in der Zeile "... command = ..." zuweist.

Der "Befehl" erwartet die Rückgabe einer Funktion. Aus diesem Grund kann die Verwendung eines Lambda die Aufgabe erfüllen, da eine anomymische Funktion erstellt wird, die während der Auswertung an den "Befehl" zurückgegeben wird. Sie können auch Ihre eigene Funktion codieren, sie erledigt auch die Arbeit.

Dies ist ein Beispiel mit Lambda und ohne Lambda:

#!/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()
C.Vergnaud
quelle
0

Ich denke, der beste Weg, um dieses Problem zu lösen, ist die Verwendung einer Lambda-Funktion.

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

Wenn Sie das Befehlsschlüsselwort nicht verwenden möchten, können Sie stattdessen die Methode .bind () verwenden:

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

Die Verwendung einer Mutterfunktion (kein Parameter), die die untergeordnete Funktion (mindestens 1 Parameter) besitzt, die Sie aufrufen möchten, ist dumm.

Nur um mit Ihnen zu teilen, dies ist eines meiner Programme:

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

Das ist es.

Deer Lawson
quelle