Hallo,
ich bin mit Python recht neu unterwegs. Habe mich auch schon ca. 2 Wochen durch diverse Schulungsunterlagen gekämpft und versuche mich jetzt über ein paar Beispiele an ein Praxisprojekt ranzuarbeiten.
Aktuell hänge ich bei der Menüabfrage. Den grundsätzlichen Aufbau meine ich verstanden zu haben. Das Menü steht weitestgehend. Allerdings finde ich keine Lösung für ein spezielles Problem und hoffe, dass mir hier jemand helfen kann.
Ziel beim aktuellen Programmteil ist es, über Menü ein Team auswählen zu können. Mit der Auswahl sollen dann ein paar Parameter (Farbe, Teamzugehörigkeit, usw.) für den aktuellen Spieler gesetzt werden.
Hinweis: Um die Oberfläche schlank zu halten und nicht zu überfrachten, werden auch noch einige anderen Auswahlpunkte über das selbe Menü gesteuert, so dass separate List- oder Kombiboxen nicht wirklich gewünscht sind.
Mit jedem Submenüpunkt kann ja über Command eine Funktion aufgerufen werden. Ich möchte im Menü jedoch wie beschrieben eine Auswahl gleichartiger Auswahlpunkte (z.B. Teams) anbieten. Dies habe ich erstmal durch eine For-Schleife gelöst, welche zum Menüpunkt den Zähler mit anhängt. Später wird hier ggf. der Zähler einfach nur noch als Index für Listeneinträge genutzt. Die Anzahl der Menüpunkte soll dynamisch erzeugt werden können. Ich könnte jetzt für jeden einzelnen Auswahlpunkt eine eigene Funktion erstellen. Dies macht jedoch keinen Sinn, da der Zähler auch der einzige Parameter ist, den ich in der aufzurufenden Funktion wieder nutzen möchte bzw. danach auswerte. Ich habe bisher nichts gefunden, um den ausgewählten Menüpunkt als Parameter an eine Funktion mit übergeben zu können bzw. diesen Auswahlpunkt überhaupt ermitteln zu können.
Lösungsansätze über Optionsbutton finde ich optisch nicht besonders schön. Ich gehe auch davon aus, dass man den ausgewählten Menüpunkt irgendwie ermitteln und als Parameter mit übergeben kann.
Hat vielleicht jemand eine Idee, wie ich das anstellen könnte?
Schon mal im Voraus vielen Dank für mögliche Lösungsansätze.
Menüauswahl abfragen
Erstmal besten Dank an deets ...
Lösung war dann doch einfacher als ich dachte mit lambda-Funktion umzusetzen. Relevant ist die Zeile mit der lambda-Funktion. Der entscheidende Haken lag hier noch an der fehlenden Zuweisung i=i.
In der Funktion auswahl könnte man nun die Auswahl auswerten oder mit dem Index Aktionen durchführen.
Für alle, die es interessiert das Testlisting ... (allerdings nur in einer Testversion ohne offiziellen Pythonstyle).
Hinweis: Die Maus müsste noch an einen konkreten Menübutton gebunden werden, zum Testen reicht es aber.
Lösung war dann doch einfacher als ich dachte mit lambda-Funktion umzusetzen. Relevant ist die Zeile mit der lambda-Funktion. Der entscheidende Haken lag hier noch an der fehlenden Zuweisung i=i.
In der Funktion auswahl könnte man nun die Auswahl auswerten oder mit dem Index Aktionen durchführen.
Für alle, die es interessiert das Testlisting ... (allerdings nur in einer Testversion ohne offiziellen Pythonstyle).
Hinweis: Die Maus müsste noch an einen konkreten Menübutton gebunden werden, zum Testen reicht es aber.
Code: Alles auswählen
#from tkinter import *
from tkinter import Canvas
import tkinter as tk
root = tk.Tk()
obj_canvas_master = Canvas(root, width=500, height=300, relief="sunken", bd=3)
obj_canvas_master.pack()
obj_frame = tk.Frame(obj_canvas_master, background="blue", bd=6, relief="groove", width=300, height=200, padx=3, pady=3)
obj_canvas_master.create_window(0, 0, window=obj_frame, anchor="nw", tags="self.frame")
def popup(event):
menu.post(event.x_root, event.y_root)
# menu.post(100, 100)
# attach popup to canvas
obj_frame.bind("<Button-1>", popup)
def hello():
print("hello!")
def auswahl(number):
print("Dies ist Team " + number)
# create a popup menu
mainmenu = tk.Menu(obj_frame)
menu = tk.Menu(mainmenu, tearoff=0, relief="raised", bd=3, activeforeground="red", activebackground="yellow")
mainmenu.add_command(label="Spieler neu", command=hello)
submenu_player_edit = tk.Menu(menu, tearoff=0)
submenu_player_edit.add_command(label="Name ändern", command=hello)
submenu_team_choice = tk.Menu(submenu_player_edit, tearoff=0)
for i in range(1, 17):
# submenu_team_choice.add_command(label="Team " + str(i), command=hello)
submenu_team_choice.add_command(label="Team " + str(i), command = lambda i=i: auswahl(str(i)))
submenu_player_edit.add_cascade(label="Team wählen", menu=submenu_team_choice)
submenu_player_edit.add_command(label="Handycap wählen", command=hello)
menu.add_cascade(label="Spieler anpassen", menu=submenu_player_edit)
submenu_player_move = tk.Menu(menu, tearoff=0)
submenu_player_move.add_command(label="hoch", command=hello)
submenu_player_move.add_command(label="runter", command=hello)
menu.add_cascade(label="Spieler verschieben", menu=submenu_player_move, state=tk.DISABLED)
submenu_player_delete = tk.Menu(menu, tearoff=0)
submenu_player_delete.add_command(label="ja", command=hello)
submenu_player_delete.add_command(label="nein", command=hello)
menu.add_cascade(label="Spieler löschen", menu=submenu_player_delete)
menu.add_separator()
menu.add_command(label="Statistik", command=hello)
menu.add_separator()
menu.add_command(label="abbrechen")
Ich würde das so machen:
Wobei auswahl() ja hoffentlich mehr macht und das print() nur ein Platzhalter ist...
Code: Alles auswählen
def auswahl(number):
print("Dies ist Team", number)
# ...
for i in range(1, 17):
submenu_team_choice.add_command(label="Team {}".format(i), command=lambda: auswahl(i))
@snafu: und das ist genau der Weg, der nicht funktioniert.Die Variablen, die in einem Lamda-Ausdruck verwendet werden, gehören zum umbegenden Namespace. Alle command-Aufrufe verwenden also den Wert den das `i` zuletzt hat, 17. Besser `partial` verwenden.
Danke für die Tipps.
Mal unabhängig von den Hinweisen ... Mit der Variablenzuweisung in der Lambda-Anweisung funktioniert der Aufruf der Funktion mit dem entsprechenden Index.
Gibt es einen Vorteil für die Nutzung von partial bzw. gibt es einen Grund, warum man partial statt lambda nehmen sollte?
Mal unabhängig von den Hinweisen ... Mit der Variablenzuweisung in der Lambda-Anweisung funktioniert der Aufruf der Funktion mit dem entsprechenden Index.
Gibt es einen Vorteil für die Nutzung von partial bzw. gibt es einen Grund, warum man partial statt lambda nehmen sollte?
partial Objekte erlauben einfachere Introspektion: https://docs.python.org/2/library/functools.html
Und du kannst es auch mit positionalen Argumenten aufrufen: partial(f,i) statt lambda: i=i: f(i). Finde ich partial auch ästethischer.
Und du kannst es auch mit positionalen Argumenten aufrufen: partial(f,i) statt lambda: i=i: f(i). Finde ich partial auch ästethischer.
In welchem Zusammenhang tritt das Problem mit lambda auf? Habe es mal unter Python 3 getestet:
Hier wird erwartungsgemäß ausgegeben:
Code: Alles auswählen
def call_func(f):
return f()
def main():
for i in range(5):
call_func(lambda: print(i))
if __name__ == '__main__':
main()
Code: Alles auswählen
0
1
2
3
4
Nein, die 5 callables. Das Problem ist, das der Closure/ABschluss über die Namen passiert. Nicht über die Werte. Da du aber nicht erst alle callables erzeugst, sondern immer nur einen, und den dann gleich aufrufst, sieht der auch den Wert, den du erwartest. Denn der ist ja noch nicht neu gebunden.
Ja richtig, das macht der OP doch auch, oder übersehe ich was? Wenn man die Lambdas in eine Liste steckt, dann ist doch klar, dass sie sich den Wert holen, der gerade aktuell ist. Ich habe das Gefühl, wir reden über ein Problem, das gar nicht existiert...
Nochmal anders gesagt: Lambdas arbeiten mit den Werten, die zum Zeitpunkt ihres Aufrufs aktuell sind. Dies ist nicht zwingend der Wert, wenn der Bezeichner übergeben wird, denn der Wert kann sich zwischenzeitlich geändert haben. Wenn ich also i an ein Lambda binde und das Lambda erst später aufrufe, nachdem ich i verändert habe, dann wird das Lambda mit dem dann aktuellen i aufgerufen, so wie jede andere Funktion es zum Zeitpunkt ihres Aufrufs ebenfalls tun würde. Kann sein, dass das für manche verwirrend ist, aber ich finde es konsequent.
Nochmal anders gesagt: Lambdas arbeiten mit den Werten, die zum Zeitpunkt ihres Aufrufs aktuell sind. Dies ist nicht zwingend der Wert, wenn der Bezeichner übergeben wird, denn der Wert kann sich zwischenzeitlich geändert haben. Wenn ich also i an ein Lambda binde und das Lambda erst später aufrufe, nachdem ich i verändert habe, dann wird das Lambda mit dem dann aktuellen i aufgerufen, so wie jede andere Funktion es zum Zeitpunkt ihres Aufrufs ebenfalls tun würde. Kann sein, dass das für manche verwirrend ist, aber ich finde es konsequent.
Hier eine etwas entflochtene und ausführbare Variante des Beitrages:
Gruss wuf
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from functools import partial
try:
# Tkinter for Python 2.xx
import Tkinter as tk
except ImportError:
# Tkinter for Python 3.xx
import tkinter as tk
APP_TITLE = "Menu Abfragen"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 400
APP_HEIGHT = 300
class Application(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.obj_canvas_master = tk.Canvas(master, width=500, height=300,
relief="sunken", bd=3)
self.obj_canvas_master.pack()
self.obj_frame = tk.Frame(self.obj_canvas_master, background="blue",
bd=6, relief="groove", width=300, height=200, padx=3, pady=3)
self.obj_canvas_master.create_window(0, 0, window=self.obj_frame,
anchor="nw", tags="self.frame")
# attach popup to canvas
self.obj_frame.bind("<Button-1>", self.popup)
# create a popup menu
mainmenu = tk.Menu(self.obj_frame)
menu = tk.Menu(mainmenu, tearoff=0, relief="raised", bd=3,
activeforeground="red", activebackground="yellow")
mainmenu.add_command(label="Spieler neu", command=self.hello)
submenu_player_edit = tk.Menu(menu, tearoff=0)
submenu_player_edit.add_command(label="Name ändern", command=self.hello)
submenu_team_choice = tk.Menu(submenu_player_edit, tearoff=0)
for i in range(1, 17):
'''
#~~ Variante OP
# Funktioniert: Typischer gewöhnungsbedürftige 'lambda' Syntax
submenu_team_choice.add_command(label="Team " + str(i),
command=lambda i=i: self.auswahl(str(i)))
#~~ Variante sanfu
# Funktioniert nicht: Hier wird immer der letzte Team-Index
# als Argument übergeben
submenu_team_choice.add_command(label="Team {}".format(i),
command=lambda: self.auswahl(i))
'''
#~~ Variante Sirius3, __deets__ (und wuf)
# Funktioniert: Ein etwas mehr verständlicherer 'partial' Syntax
submenu_team_choice.add_command(label="Team {}".format(i),
command=partial(self.auswahl, i))
submenu_player_edit.add_cascade(label="Team wählen", menu=submenu_team_choice)
submenu_player_edit.add_command(label="Handycap wählen", command=self.hello)
menu.add_cascade(label="Spieler anpassen", menu=submenu_player_edit)
submenu_player_move = tk.Menu(menu, tearoff=0)
submenu_player_move.add_command(label="hoch", command=self.hello)
submenu_player_move.add_command(label="runter", command=self.hello)
menu.add_cascade(label="Spieler verschieben", menu=submenu_player_move,
state=tk.DISABLED)
submenu_player_delete = tk.Menu(menu, tearoff=0)
submenu_player_delete.add_command(label="ja", command=self.hello)
submenu_player_delete.add_command(label="nein", command=self.hello)
menu.add_cascade(label="Spieler löschen", menu=submenu_player_delete)
menu.add_separator()
menu.add_command(label="Statistik", command=self.hello)
menu.add_separator()
menu.add_command(label="abbrechen")
self.menu = menu
def popup(self, event):
self.menu.post(event.x_root, event.y_root)
#self.menu.post(100, 100)
def hello(self):
print("hello!")
def auswahl(self, number):
print("Dies ist Team {}".format(number))
def main():
app_win = tk.Tk()
app_win.title(APP_TITLE)
app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
app_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
Application(app_win).pack(fill='both', expand=True)
app_win.mainloop()
if __name__ == '__main__':
main()
Take it easy Mates!
@snafu: hier noch mal das Problem als Programm, mit dem es hoffentlich klar wird:
und der Ausgabe:
[codebox=text file=Unbenannt.txt]
Ergebnis 1:
4
4
4
4
4
Ergebnis 2:
0
1
2
3
4
[/code]
Code: Alles auswählen
from functools import partial
def define_functions1():
result = []
for i in range(5):
result.append(lambda: print(i))
return result
def define_functions2():
result = []
for i in range(5):
result.append(partial(print, i))
return result
def main():
func1 = define_functions1()
func2 = define_functions2()
print("Ergebnis 1: ")
for f in func1: f()
print("Ergebnis 2: ")
for f in func2: f()
if __name__ == '__main__':
main()
[codebox=text file=Unbenannt.txt]
Ergebnis 1:
4
4
4
4
4
Ergebnis 2:
0
1
2
3
4
[/code]
Habe es jetzt begriffen, denke ich. Die GUI führt die Lambdas nicht unmittelbar aus. Dadurch gilt bei einem nachträglichen Aufruf nur der letzte Wert von i. Ähnlich wie es wäre, wenn man die Lambdas zuvor in eine Liste ablegt und die Liste anschließend durchläuft.
Jetzt nochmal gelesen und nun auch verstanden. Hatte etwas länger gedauert bei mir.__deets__ hat geschrieben:Das Problem ist, das der Closure/ABschluss über die Namen passiert. Nicht über die Werte.
Das war bis hier recht ausführlich. Danke, konnte einiges Neues mitnehmen.
Als Fazit für mich ... beide Varianten sind möglich.
Vorteil lambda: direkter Zusammenbau der erforderlichen Funktion
Vorteil partial: deutlich einfacher zu verstehen, jedoch Import einer zusätzlichen Funktion erforderlich
Habe allerdings noch ein zweites Problem. Wie bekomme ich es hin, dass das Menü nicht gleich bei Auswahl geschlossen wird. Die Menüpunkte hoch und runter sollen ggf. mehrmals hintereinander ausgeführt werden. Da wäre es nervig, jedesmal den Menüpunkt neu aufrufen zu müssen.
Mein Ziel wäre es bei konsequenter Menüführung, das Menü bei einigen Auswahlpunkten offen zu halten oder nach Ausführung genau so wieder aufzumachen, dass der Benutzer sofort auf dem selben Menüpunkt steht.
Die Alternative wäre natürlich, die Mehrfachwiederholung in der Funktion selber einzubauen.
Hätte jemand eine Idee?
Als Fazit für mich ... beide Varianten sind möglich.
Vorteil lambda: direkter Zusammenbau der erforderlichen Funktion
Vorteil partial: deutlich einfacher zu verstehen, jedoch Import einer zusätzlichen Funktion erforderlich
Habe allerdings noch ein zweites Problem. Wie bekomme ich es hin, dass das Menü nicht gleich bei Auswahl geschlossen wird. Die Menüpunkte hoch und runter sollen ggf. mehrmals hintereinander ausgeführt werden. Da wäre es nervig, jedesmal den Menüpunkt neu aufrufen zu müssen.
Mein Ziel wäre es bei konsequenter Menüführung, das Menü bei einigen Auswahlpunkten offen zu halten oder nach Ausführung genau so wieder aufzumachen, dass der Benutzer sofort auf dem selben Menüpunkt steht.
Die Alternative wäre natürlich, die Mehrfachwiederholung in der Funktion selber einzubauen.
Hätte jemand eine Idee?