Forum Programmation.python Problème de callback avec Tkinter

Posté par . Licence CC by-sa
Tags : aucun
3
20
fév.
2015

Bonjour,

J'essaie de générer une barre d'outils à la volée. Pour celà je boucle sur une liste contenant les libellés des boutons et je créé mes boutons.

# code simplifié pour l'exemple :
list = ["but1", "but2", "but3", "but4", "but5", "but6"]
for key in list:
    ui.key = Button(ui, text=key, command=lambda:print(key))
    ui.key.pack(side=LEFT, padx=2, pady=2)

Dans mon programme j'ai bien la barre d'outils suivante :

+——————————————————————————————————————————+
| [but1] [but2] [but3] [but4] [but5] [but6]|
+——————————————————————————————————————————+

Par contre lorsque je clique sur un bouton j'ai toujours la même action qui correspond à la dernière occurence de la liste (donc l'action de but6) :

click sur but1 affiche but6
click sur but2 affiche but6
click sur but3 affiche but6
click sur but4 affiche but6
click sur but5 affiche but6
click sur but6 affiche but6

Je suppose que c'est la fonction lambda: qui pose problème… Et j'aimerais savoir comment procéder pour définir mon callback correctement.

Merci.

  • # oubli paramètre lambda

    Posté par (page perso) . Évalué à 2.

    Peut-être que tu as oublié le paramètre à la fonction lambda. print(key) est simplifier par l'interpréteur au premier passage et le résultat simplifié est utilisé pour les suivants.

    Essaye donc pour corriger :

    ui.key = Button(ui, text=key, command=lambda key: print(key))
  • # Namespace et closure.

    Posté par (page perso) . Évalué à 6.

    C'est un problème de python et pas de tkinter.

    Lorsque tu parcours ta list et crées tes lambdas, tu crées n fonctions (6 dans ton cas) qui sont toutes liées au même namespace (dans lequel elles ont été créées).

    Quand Tkinter appelle ces callbacks, elles vont toutes aller chercher la valeur de key dans le namespace les contenant. Comme elles partagent le même namespace, elle trouve la même valeur. Et la valeur de key, c'est "but6", la dernière valeur attribuer à key.

    La solution est de forcer la "résolution" de key au moment où la lambda est créée et pas quand elle est exécutée.

    Pour ce faire, l'idiome courant en python est d'utiliser un argument ayant une valeur par défaut:

    # code simplifié pour l'exemple :
    list = ["but1", "but2", "but3", "but4", "but5", "but6"]
    for key in list:
        ui.key = Button(ui, text=key, command=lambda k=key:print(k))
        ui.key.pack(side=LEFT, padx=2, pady=2)

    Ainsi, lorsque la lambda est exécutée, elle utilise la valeur de k (son argument) qui à pour valeur la valeur de key (variable global) au moment de sa définition.

    Il existe aussi une autre façon de faire, créer un namespace par fonction, dans lequel la closure ira chercher:

    def create_callback(key):
        """Cet fonction va créer une callback et la retourner à l'appelant
        Comme chaque appel de fonction crée un nouveau namespace et que la callback
        est créée dans ce namespace, chaque callback a son namespace.
        """
        def _callback():
            print(key)
        return _callback
    
    list = ["but1", "but2", "but3", "but4", "but5", "but6"]
    for key in list:
        ui.key = Button(ui, text=key, command=create_callback(key))
        ui.key.pack(side=LEFT, padx=2, pady=2)

    Matthieu Gautier|irc:starmad

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.