'command' zwei Argumente übergeben?

Fragen zu Tkinter.
joerg123
User
Beiträge: 13
Registriert: Samstag 27. Juni 2015, 10:47

Hallo,

ich komme gerade einfach nicht weiter trotz der Suche im Forum und im Netz.
Ich habe ein OptionMenu erstellt und nun wollte ich über command eine Funktion aufrufen und dieser zwei Argument übergeben. Geht das eigentlich? Ich fand bis jetzt einfach nicht heraus wie und ob ich überhaupt mehrere Argumente mit übergeben darf/kann.

Code: Alles auswählen

tk.OptionMenu(rahmen_soll, var, *datarh, command=dateiSchreiben)
Grüße
Jörg
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi joerg123

Ja dies sollte gehen:

Code: Alles auswählen

from functools import partial

tk.OptionMenu(rahmen_soll, var, *datarh, command=partial(
    datei_schreiben, argument1, argument2))
    
def datei_schreiben(parameter1, parameter2):
    print(parameter1, parameter2)
(Nicht getestet!)

Gruss wuf :wink:
Take it easy Mates!
joerg123
User
Beiträge: 13
Registriert: Samstag 27. Juni 2015, 10:47

Hallo wuf,

hab vielen lieben Dank für deine Hilfe. Nach längerem hin und her habe ich es geschafft die Werte richtig zu übergeben. Ich werde aber nicht recht schlau daraus. Folgendes habe ich programmiert:

Der Aufruf:

Code: Alles auswählen

    datarh = ('70', '68', '65')
    var = tk.StringVar(rahmen_soll)
    menu = tk.OptionMenu(rahmen_soll, var, *datarh,
                         command=partial(dateiSchreiben, var, rhuhr))
und die Funktion:

Code: Alles auswählen

def dateiSchreiben(value, rhuhr, var):
    print ("value", (value))
    print ("rhur", (rhuhr))
    print ("var", (var))
    indat(var)  # ruft indat auf zum Schreiben in eine Datei
    var = int(var)
    print ("var int", (var))
    rhuhr.setSollRh(var)
Die Ausgabe über Konsole sieht so aus:

value PY_VAR0 # ich nehme an das kommt von dateiSchreiben
rhur .36090736.36090832.36970704
var 70
var int 70
Zuletzt geändert von joerg123 am Montag 29. Juni 2015, 19:34, insgesamt 1-mal geändert.
joerg123
User
Beiträge: 13
Registriert: Samstag 27. Juni 2015, 10:47

Ich denke, das das irgendwie mit der Reihenfolge zu tun hat wie die Argumente übernommen werden. Klar wird es mir aber nicht so recht. :shock:

Kannst du mir erklären was hier passiert?
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Im grunde passiert das hier:

Code: Alles auswählen

def my_partial(function, value, ruhr):
    def new_function(var):
        return function(value, ruhr, var)
    return new_function
`partial` erzeugt eine neue funktion `new_function` und gibt diese zurück (die Funktion, die funktion wird nicht von partial ausgewertet!). Die innere Funktion `new_function` hat auch nachdem sie von `my_partial` zurückgegeben wurde noch zugriff auf die Argumente `function`, `value` und `ruhr`, um es zu vereinfachen können wir sagen, sie hat sich die einfach gemerkt, wobei `function` die Funktion ist, die du eigentlich aufrufen willst, nochmals das ist die Funktion selber, die wurde noch nicht aufgerufen. Tkinter ruft nun `new_function` mit einem Argument `var` auf. `new_function` nimmt jetzt die "gemerkte" Funktion `function` (in deinem Fall `datei_schreiben`) und ruft die mit den anderen gemerkten Argumenten und dem neuen Argument `var` von Tkinter auf.

Kurz gesagt, `partial` macht eine neue funktion die deine Funktion mit gemerkten Argumenten aufruft.
the more they change the more they stay the same
joerg123
User
Beiträge: 13
Registriert: Samstag 27. Juni 2015, 10:47

Hallo Dav1d,

zuerst einmal auch dir einen herzlichen Dank für die Erklärung. :D
So in etwa habe ich es auch verstanden. Obwohl es sich für mich recht kompliziert anhört. Ich nehme mal an das ‚partial‘ auch für andere Dinge gebraucht wird, wo sich das einleuchtender darstellt.
Nur so interessehalber, gibt es noch eine andere (einfachere) Möglichkeit mehrere Argumente an ‚command‘ zu übergeben oder ist das die übliche Vorgehensweise? Es verwirrt mich nur ein wenig, da es bei einem „normalen Funktionsaufruf wie: dateiSchreiben(var, rhuhr) mir der Vorgang nachvollziehbarer erscheint.

Grüße
Jörg
BlackJack

@joerg123: Das ist (fast) die übliche Vorgehensweise. Bei dem direkten Aufruf passiert genau das: die Funktion wird direkt aufgerufen und der Rückgabewert von dem Aufruf wird als `command`-Argument übergeben. Du willst ja aber 1. einen Aufruf wenn das `command` ausgeführt wird und 2. da dann eine Funktion übergeben die *keine* Argumente erwartet. Also muss man sich so etwas erstellen. Beispielsweise mit `partial()`. Oder einer Funktion wie Dav1d sie zeigt. Vor `partial()` hat man das auch oft mit einer anonymen Funktion (``lambda``-Ausdruck) gemacht.

Das ”(fast)” im ersten Satz deshalb, weil hier natürlich auch objektorientierte Programmierung in Frage kommt, beziehungsweise eigentlich sogar ”natürlicher” wäre. Was die Komplexität angeht sind aber all diese Lösungen im Grunde gleichwertig.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi joerg123

Habe noch ein wenig mit dem OptionMenu Widget herumgespielt. Folgendes kam dabei heraus.

Als erstes:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk

def option_menu_callback(value):
    print("Der selektierte Wert ist: {}".format(value))
        
app_win = tk.Tk()
app_win.geometry("100x100")

# Hauptrahmen
main_frame = tk.Frame(app_win, bg='steelblue')
main_frame.pack(fill='both', expand=True)

# Die im Menu selektierbaren Werte
menu_values = ('70', '68', '65')

# Steuervariable für den auf dem Menubutton angezeigten Wert
value = tk.StringVar()
# Initialisierung des auf dem Menubutton angezeigten Wertes
value.set(menu_values[0]) 

# Das OptionMenu-Widget
menu = tk.OptionMenu(main_frame, value, *menu_values,
    command=option_menu_callback)
menu.pack(expand=True)

# Konfigurierbare Widgeteigenschaften
menu.configure(highlightthickness=0, font=('helvetica', 16, 'bold'), 
    fg='darkolivegreen', width=3)

app_win.mainloop()
Hier wird ohne den Einsatz der 'partial'-Methode automatisch der dem 'command' zugewissenen Funktion das selektierte Element als Argument übergeben.

Als zweites:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

from functools import partial

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk

def option_menu_callback(parameter1, parameter2, parameter3, value):
    print(parameter1)
    print(parameter2)
    print(parameter3)
    print(value)
    save_value(value)

def save_value(value):
    print("Speichere den selektierten Wert: {}".format(value))
        
app_win = tk.Tk()
app_win.geometry("100x100")

# Hauptrahmen
main_frame = tk.Frame(app_win, bg='steelblue')
main_frame.pack(fill='both', expand=True)

# Die im Menu selektierbaren Werte
menu_values = ('70', '68', '65')

# Steuervariable für den auf dem Menubutton angezeigten Wert
value = tk.StringVar()
# Initialisierung des auf dem Menubutton angezeigten Wertes
value.set(menu_values[0]) 

argument1 = "Argument-1"
argument2 = "Argument-2"
argument3 = "Argument-3"

# Das OptionMenu-Widget
menu = tk.OptionMenu(main_frame, value, *menu_values,
    command=partial(option_menu_callback, argument1, argument2, argument3))
menu.pack(expand=True)

# Konfigurierbare Widgeteigenschaften
menu.configure(highlightthickness=0, font=('helvetica', 16, 'bold'), 
    fg='darkolivegreen', width=3)

app_win.mainloop()
Hier kommt die 'partial'-Methode zum Einsatz. Der 'partial'-Methode werden drei Argumente mitgegeben. Als viertes Argument fügt das OptionMenu automatisch das selektierte Element dazu ohne es explizit angeben zu müssen.

Gruss wuf :wink:
Take it easy Mates!
joerg123
User
Beiträge: 13
Registriert: Samstag 27. Juni 2015, 10:47

Hallo BlackJack, wuf,

habt vielen Dank für eure Mühe mir dies verständlich rüberzubringen!!! Ich glaube jetzt habe ich es auch verstanden wie partial funktioniert.

@wuf:
Indem ich mir die Ausgaben über print anzeigen lies, kam ich erst darauf, das die Werte vertauscht waren.

@BlackJack:
alternative eine Klasse zu nehmen, das habe ich mir fast schon gedacht. Mit Klassen fange ich gerade erst an. Damit muss ich mich unbedingt noch mehr befassen.

Viele Grüße :D :D
Jörg
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Bietet partial Vorteile gegenüber lambda? lambda braucht man nicht erst importieren, etwa:

Code: Alles auswählen

a = 5
b = 6

button=Button(text="Hello")
button.pack()
button.config(command=(lambda: print(a,b)))
Außerdem geht auch noch wesentlich mehr, nämlich mit beliebig vielen Funktionsaufrufen : (Python3 Beispiel)

Code: Alles auswählen

a = 5
b = 6

button=Button(text="Hello")
button.pack()
button.config(command=(lambda par = compile("""
print(a,b)
print("Geht doch prima")
print("kann man beliebige viele Funktionsaufrufe tun")
print("könnte man sogar viele Files öffnen, einlesen und mit dazu kompilieren")
""",'<string>', 'exec'): eval(par)))
Übrigens, wenn man auch die events so behandelt und dann dazu noch messages implementiert mit den callbacks auf dieselbe Weise, kann man ganz auf Funktionen verzichten.
Und wenn man noch auf Variablen verzichtet, hat man eine ganz dynamische und hochflexible Programmstruktur. Man kann dann zur Laufzeit beliebig Programmteile dazuladen und wieder rauswerfen.
Auch das ganze Programm völlig.
Zuletzt geändert von Alfons Mittelmeyer am Samstag 1. August 2015, 12:22, insgesamt 2-mal geändert.
BlackJack

@Alfons Mittelmeyer: Guido van Rossum wollte die ``lambda``-Ausdrücke ursprünglich mal aus der Sprache rauswerfen seit es `functools.partial()` gibt. Weil er meinte damit ist ``lambda`` so gut wie überflüssig und an den Stellen wo `partial()` ein ``lambda`` ersetzen kann ist `partial()` verständlicher weil die Leute ja schon mit normalen Funktionen oft über die Semantik von Defaultwerten stolpern und man diese Semantik hier explizit ausnutzt. Ich weiss jetzt nicht ob's Dir bewusst ist/war, aber ``lambda: print(a, b)`` ist nicht das gleiche wie ``partial(print, a, b)``. Das wäre ``lambda a=a, b=b: print(a, b)``.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Black Jack

Habe mich mit partial noch nicht befasst.
Aber lambda herauswerfen wäre wohl eine schlechte Idee. Oder kann man bei partial auch unterscheiden zwischen Variablenwerten zur Definitionszeit und solchen zur Ausführungszeit?
BlackJack

@Alfons Mittelmeyer: Nein das kann man nicht unterscheiden, aber der Fall ist eher selten und dafür gäbe es ja immer noch ganz normale Funktionsdefinitionen. Beziehungsweise eine OOP-Lösung.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack: vielleicht benützt man diesen Fall wenig. Aber genau auf Funktionen und OOP Lösungen verzichte ich gerne.
Stattdessen compiliere ich Code zur Definitionszeit, den ich dann zur Ausführungszeit ausführe.

Wie bereits gesagt, wenn man keinen Master angeben braucht, keine Variablen benützt und auf Funktionen verzichtet, werden Programme hochdynmisch
BlackJack

@Alfons Mittelmeyer: Ich kann Dir glaube ich nicht ganz folgen. Was ist denn hier Definitionszeit und Ausführungszeit? Definitionen passsieren doch bei Python alle zur Laufzeit durch Code der ausgeführt wird. Und Code wird compiliert (wobei das ein Implementierungsdetail ist) bevor er ausgeführt wird. Oder verwendest Du wirklich regelmässig `compile()` & Co zur Laufzeit?

Wenn man keine Variablen benutzt und auf Funktionen verzichtet, werden Programme nicht hochdynamisch sondern „nichtexistent“ oder zumindest sehr beschränkt. ;-) Der ``lambda``-Ausdruck wird zu einer Funktion ausgewertet, also würde auf Funktionen verzichten auch bedeuten auf ``lambda`` zu verzichten.

Und was ist in diesem Zusammenhang jetzt ein „Master“?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack:

Also zu Definitionszeit und Ausführungszeit ein Beispiel:

Code: Alles auswählen

a = 5
b = 6

button.config(command=(lambda par1=a, par2=b: print(par1,par2)))
#button.config(command=(lambda: print(a,b)))

a="Hello"
b="all"
Der erste command gibt die Werte aus, die a und b bei der Definition des commands hatten (5,6) - also Werte zur Defintionszeit
Der zweite (auskommentierte) command gibt die Werte aus, die a und b zur Zeit des Buttondrucks haben. Das wäre also dann (Hello,all) - also Werte zur Ausführungszeit

Dann war natürlich missverständlich ausgedrückt, dass ich auf Funktionen verzichte. Auf Funktionsaufrufe verzichte ich natürlich nicht, sondern auf Funktionsdefinitionen.
Also Funktionen, die woanders definiert sind benütze ich selbstversändlich. And da benütze ich ein Modul von mir namens DynTkInter.

Und wenn man statt: from tkinter import *
folgendes schreibt: from DynTkInter import *

dann braucht man keinen Master oder auch Parent für ein widget angeben.
und dann bekommen die widgets auch Namen, sodass man sie mit Namen adressieren kann anstatt Variablen zu benutzen.
außerdem kann man manche Funktionen , wie pack, grid, place, config etc. auch ohne Referenzangabe benützen, da diese sich auf das gerade ausgewählte widget beziehen.
das gerade ausgewählte widget kan man auch mit der Funktion this() referenzieren.

Man kann dann zur Laufzeit des Programmes in der Gui desselben dann entweder mit Funktionsaufrufen in einem Parallelthread spazierengehen und die Widgets verändern.
Oder man kann das besser mit einem grafischen GuiCreator tun, der in einem Toplevel Window läuft.

Das sollte auch bei herkömmlichen tkinter Programmen funktionieren. Leider kann man nach GUI Veränderung bei denen dann aber nur die Gui ohne Funktion abspeichern.

Wenn man aber den neuen DynTkInter Programmierstil benützt, kann man zur Laufzeit auch den Code verändern und testen
Und dieser Code wird zur Laufzeit compiliert.

Der ganze GuiCreator hat nur zur Laufzeit compilerten Code. Und das sind bindings von Objekten an commands events oder messages. Geht also alles wieder verloren wenn man die dazugehörigen Widgets löscht.
BlackJack

@Alfons Mittelmeyer: Klingt irgendwie nicht als würdest Du in Python programmieren (wollen). :-)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack

Wieso? Bist du etwa der Ansicht, dass Code so aussehen muss, damit es Python ist, wie hier: http://www.python-course.eu/tkinter_buttons.php

Dasselbe schaut bei mir wesentlich aufgeräumter aus:

Code: Alles auswählen

from DynTkInter import *

Tk()

Frame('Frame').pack()

goIn()

Button(text="QUIT",fg='red').pack(side='left')
evcommand("quit()")

Button(text="Hello").pack(side='left')
evcommand("print('DynTkinter is easy to use!')")

goOut()

mainloop()
BlackJack

@Alfons Mittelmeyer: Der Code auf der Website hat IMHO diverse Probleme so dass er nicht als wirklich ”pythonisch” durchgeht, aber Deiner ist in der Tat deutlich weiter von Python entfernt. Und von guter Programmierung. Das sieht nach viel Magie und globalen Zuständen aus, und wenn man anfängt Python-Quelltext in Zeichenketten irgendwo zu übergeben, dann macht man etwas falsch. Das ist ein Nogo, insbesondere bei einer so dynamischen Sprache wie Python bei der so etwas nicht nötig sein sollte. Es gibt einige wenige Ausnahmen, aber für Rückruffunktionen sicher nicht. Das erinnert an gruselige alte JavaScript-Zeiten.

*Das* Programm kann man auch noch ohne OOP schreiben denn es gibt ja keinen Zustand den man über Aktionen hinweg speichern müsste:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import Tkinter as tk
from functools import partial


def main():
    root = tk.Tk()
    tk.Button(root, text='QUIT', foreground='red', command=root.quit).pack(
        side=tk.LEFT
    )
    tk.Button(
        root, text='Hello', command=partial(print, 'Tkinter is easy to use!')
    ).pack(side=tk.LEFT)
    root.mainloop()


if __name__ == '__main__':
    main()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Der Code auf der Website hat IMHO diverse Probleme so dass er nicht als wirklich ”pythonisch” durchgeht, aber Deiner ist in der Tat deutlich weiter von Python entfernt. Und von guter Programmierung. Das sieht nach viel Magie und globalen Zuständen aus
Bei Magie gebe ich Dir recht, denn was man damit anstellen kann ist irre, direkt wie Magie.
Aber sind globale Zustände etwas schlechtes?. Auf Deiner Festplatte hast Du ein Filedirectory. Das ist ein globaler Zustand oder? ist das etwas schlechtes, sodaß man auf das Filedirectory verzichten sollte, am besten löschen?
Bei mir sind die Widgets mit Namen in einer solchen Verzeichnisstruktur erfasst, mit dem kleinen Unterschied, dass auch mehrere Einträge mit gleichem Namen existieren dürfen.
Und beim Anlegen einer Datei in einem Verzeichnis, werde ich auch nicht erst gefragt, in welchem Verzeichnis ich diese anlegen will. Was also das Parentverzeichnis ist und für das ich dann eine Referenz in einer Variablen brauche.

Schlecht ist da eher, dass man sich darüber keine Gedanken gemacht hatte, sodass man zur Programmierung Verrenkungen machen muss mit unnötigen Variablen oder gar Klassen nur dazu, damit man die Funktionen verbergen kann.

Und was ist gegen Strings einzuwenden? Erinnert zwar an Javascript, das sehr langsam ist, weil die Strings nicht kompilert sondern interpretativ ausführt werden. Nur hier handelt es sich bei mir um Code in Strings der kompiliert wird. Also dann dann schnell ausgeführt wird.

Und was hast Du gegen Compilieren zur Laufzeit?
Schon mal imort benutzt?
Das ist auch Compilieren zur Laufzeit. Nur erspare ich mir, Quelldateien zu kompilieren, wenn es nur kurzer Code ist. Da finde ich einen String mehr angebracht.

Und dann gibt es einen triftigen Grund solche Strings zu verwenden. Denke einmal an einen Webbrowser, da kann man viele Webseiten damit aufrufen, geradezu im Web surfen.
Und jetzt denke mal an einen Python Frame dessen Inhalt statt HTML Seiten Python GUI Seiten sind. Wenn man da richtig braust, werden die definierten Klassen und Funktionen immer mehr und mehr. Und einen unimport gibt es dafür wohl noch nicht.


Ach so, global ist das Verzeichnis auch nicht. So ein Subverzeichnis ist etwa in einem Frame Widget gespeichert, und wenn man den Frame löscht, dann ist es auch wieder weg.
Gesperrt