web-dev-qa-db-fra.com

Pourquoi le paramètre Button "commande" est-il exécuté lorsqu'il est déclaré?

Mon code est:

from Tkinter import *

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

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

Le bouton ne fonctionne pas, il affiche 'hey' et 'het' une fois sans ma commande, puis lorsque j'appuie sur le bouton, rien ne se passe.

50
salk

L'option command prend une référence à une fonction, ce qui est une manière élégante de dire que vous devez lui transmettre le nom de la fonction. Lorsque vous exécutez command=button('hey'), vous appelez la fonction button, et le résultat de cette opération est attribué à l'option command

Pour transmettre une référence, vous devez utiliser le nom uniquement, sans parenthèses ni arguments. Par exemple:

b = Button(... command = button)

Si vous voulez passer un paramètre tel que "hé", vous devez utiliser un petit code supplémentaire:

  • Vous pouvez créer une fonction intermédiaire qui peut être appelée sans votre argument et qui appelle ensuite votre fonction button
  • Vous pouvez utiliser lambda pour créer une fonction appelée anonymous. Dans tous les cas, c'est une fonction sauf qu'elle n'a pas de nom. Lorsque vous appelez la commande lambda, elle renvoie reference à la fonction créée, ce qui signifie qu'elle peut être utilisée pour la valeur de l'option command du bouton.
  • Vous pouvez utiliser functools.partial

Pour moi, lambda est la plus simple car elle ne nécessite aucune importation supplémentaire comme le fait functools.partial, bien que certaines personnes pensent que functools.partial est plus facile à comprendre.

Pour créer une fonction lambda qui appelle votre fonction button avec un argument, procédez comme suit:

lambda: button('hey')

Vous vous retrouvez avec une fonction fonctionnellement équivalente à:

def some_name():
    button('hey')

Comme je l'ai dit précédemment, lambda renvoie une référence à cette fonction sans nom. Puisqu'une référence correspond aux attentes de l'option command, vous pouvez utiliser lambda directement dans la création du bouton:

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

Il y a une question sur ce site qui a beaucoup de commentaires intéressants sur lambda, en général. Voir la question Pourquoi les lambdas de Python sont utiles? . Cette même discussion a une réponse qui montre comment utiliser lambdas dans une boucle lorsque vous devez transmettre une variable au rappel. 

Pour finir, voir la section intitulée Tkinter Callbacks sur effbot.org pour un tutoriel de Nice. La couverture de lambda est assez maigre, mais les informations qui y figurent pourraient encore être utiles.

56
Bryan Oakley

Vous devez créer une fonction sans paramètres que vous pouvez utiliser en tant que commande:

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

Voir la section "Passer un argument à des rappels" de ce document .

8
Lukáš Lalinský

Exemple d'interface graphique:

Disons que j'ai l'interface graphique:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

Que se passe-t-il lorsqu'un bouton est enfoncé?

Voir que lorsque btn est pressé, il appelle sa propre fonction qui est très similaire à button_press_handle dans l'exemple suivant:

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

avec:

button_press_handle(btn['command'])

Vous pouvez simplement penser que l'option command devrait être définie comme étant la référence à la méthode que nous voulons appeler, similaire à callback dans button_press_handle.


Appeler une méthode ( Callback ) lorsque le bouton est enfoncé

Sans arguments 

Donc, si je voulais print quelque chose lorsque le bouton est enfoncé, il me faudrait définir:

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

Portez une attention particulière au manque de () avec la méthode print qui est omise, ce qui signifie que: "Ceci est le nom de la méthode que je souhaite que vous appeliez lorsque vous appuierez sur mais don ' ne l’appelez pas à cet instant précis. " Cependant, je n’ai passé aucun argument pour la variable print, de sorte qu’elle affiche ce qu’elle imprime lorsqu’elle est appelée sans arguments.

With Argument (s)

Maintenant, si je voulais aussi passer des arguments à la méthode que je veux appeler lorsque le bouton est enfoncé, je pourrais utiliser les fonctions anonymes, qui peuvent être créées avec lambda statement, dans cet exemple. case pour la méthode intégrée print, comme suit:

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

Appel de méthodes multiples lorsque le bouton est enfoncé

Sans Arguments

Vous pouvez également atteindre cet objectif en utilisant l'instruction lambda, mais cela est considéré comme une mauvaise pratique et je ne l'inclurai donc pas ici. La bonne pratique consiste à définir une méthode distincte, multiple_methods, qui appelle les méthodes souhaitées, puis définissez-la comme rappel à la touche.

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

With Argument (s)

Pour passer un ou des arguments à une méthode qui appelle d'autres méthodes, utilisez à nouveau l'instruction lambda, mais en premier lieu:

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

puis définissez:

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

Renvoi d'objet (s) du rappel

De plus, notez que callback ne peut pas vraiment return car il est appelé uniquement dans button_press_handle avec callback() au lieu de return callback(). Il fait return mais pas n'importe où en dehors de cette fonction. Vous devriez donc plutôt modifier objet (s) accessible (s) dans la portée actuelle.


Exemple complet avec global modification (s) d'objet

L'exemple ci-dessous appelle une méthode qui modifie le texte de btn chaque fois que le bouton est enfoncé:

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

Miroir

4
Nae

Le moteur évalue le résultat de la fonction lorsqu'il assigne la valeur à la ligne "... command = ..."

La "commande" s'attend à ce qu'une fonction soit renvoyée. C'est pourquoi l'utilisation d'un lambda peut effectuer le travail car elle crée une fonction anonyme qui est renvoyée à la "commande" lors de l'évaluation. Vous pouvez aussi coder votre propre fonction, cela fera aussi le travail.

c'est un exemple avec lambda et sans 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()
2
C.Vergnaud

N'utilisez pas de mot clé ni d'argument comme entrée ou parenthèse pour votre fonction ..__ C'est une solution très simple :)

0
novo nimo