Buttons in Schleife mit eigener Funktion

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
peter123
User
Beiträge: 2
Registriert: Samstag 17. September 2011, 18:41

Hallo Leute!
Ich möchte in meinem Programm eine Art Schachfeld aus Buttons darstellen. Dazu habe ich zwei Schleifen:

Code: Alles auswählen

columns=5
rows=5
self.x_and_y_list = []
for y_range in range(0,rows):
     self.x_list = []
     for x_range in range(0,columns):
           bu=Button(fenster,text="", command=lambda:self.buttonClick(x_range,y_range))
           bu.place(x=x_range*20+20, y=y_range*20+20, width=20, height=20)
           bu ["bg"] = "lightblue"
           self.x_list.append(bu)
      self.x_and_y_list.append(self.x_list)

def buttonClick(self,x,y):
     self.la4text.set(str(x) + " - " + str(y))
Das funktioniert auch soweit, jedoch wird la4text IMMER mit "4 - 4" ausgegeben, egal auf welchen Button ich klicke. Ich vermute, dass die Funktion buttonClick immer mit den Parametern des zuletzt erstellten Buttons aufgerufen wird.

Wie schaffe ich es, dass die Funktion buttonClick erkennt von welchem Button sie aufgerufen wurde?
Ich bin schon ziemlich am verzweifeln, also vielen Dank für Hilfe!
BlackJack

@peter123: Die Namen werden erst zu Werten aufgelöst, wenn die Funktion aufgerufen wird und nicht wenn sie definiert wird. Darum hast Du zwar für jede Schaltfläche eine eigene Funktion, aber die beiden Namen werden halt zum letzten Wert aufgelöst, der ihnen zugewiesen wurde.

Erstelle die Rückruffunktion mit `functools.partial()`. Denn Parameter werden beim Aufruf zu Werten aufgelöst und `partial()` bindet die dann in der neuen Funktion.
lunar

@peter123: Um BlackJacks Erklärungen zu vervollständigen:

In Python werden Namen nur durch Zuweisung oder durch Funktionsparameter im aktuellen Namensraum gebunden. Fehlt eine Zuweisung, und taucht der Name nicht als Funktionsparameter auf, so liegt er nicht im aktuellen Namensraum, sondern wird im umgebenden Namensraum nachgeschlagen. Das nennt sich dann Closure.

In "lambda:self.buttonClick(x_range,y_range)" werden "x_range" und "y_range" weder innerhalb der Lambda-Funktion zugewiesen (was syntaktisch eh nicht möglich wäre), noch tauchen sie als Argument dieser Lambda-Funktion auf. Folglich sind die Namen innerhalb der Lambda-Funktion diesselben wie außerhalb, der Zugriff auf "x_range" innerhalb der Lambda-Funktion endet beim "x_range" außerhalb der Lambda-Funktion. Dort werden beide Namen schlussendlich an 4 gebunden, so dass jede Lambda-Funktion "4 - 4" ausgibt.

Umgehen kannst Du das entweder, indem Du diese Namen explizit als Parameter übergibst ("lambda x=x_range,y=y_range: self.buttonClick(x, y)"), oder besser, indem Du mittels "functools.partial" eine partielle Funktion erstellst ("partial(self.buttonClick, x_range, y_range)").
peter123
User
Beiträge: 2
Registriert: Samstag 17. September 2011, 18:41

Super! Hat funktioniert. Vielen Dank!

Code: Alles auswählen

import functools
bu=Button(fenster,text="", command=functools.partial(self.buttonClick, x_range, y_range))
Antworten