tkinter Eingabe von Parametern für code

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
ing_datascience
User
Beiträge: 11
Registriert: Sonntag 24. April 2022, 20:01

Hallo zusammen,

ich habe ein Dataframe mit Messwerten und den dazugehörigen Zeitstempeln. Beispielhaft sind erste 6 Zeilen des df nachfolgend dargestellt:

Datum_2021 Messwerte_2021
2021-01-01 00:15:00 224.0
2021-01-01 00:30:00 227.2
2021-01-01 00:45:00 222.4
2021-01-01 01:00:00 228.8
2021-01-01 01:15:00 222.4

Für die Filterung des df verwende ich nachfolgenden Befehl:

df_neu = df.loc[lambda x: (x.Datum_2021 > "2021-09-01") & (x.Datum_2021 < "2021-12-01")]

Jetzt möchte ich gerne eine GUI über tkinter aufbauen, welche es mir ermöglicht die Parameter "2021-09-01" und "2021-12-01" einzugeben, sodass diese in meinem Befehl verwendet werden kann.

Könnt ihr mir bitte hierzu eine Lösung nennen?

Viele Grüße
Benutzeravatar
Dennis89
User
Beiträge: 1154
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich verstehe dein Problem noch nicht ganz. Suchst du 'tkinter.entry'?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
ing_datascience
User
Beiträge: 11
Registriert: Sonntag 24. April 2022, 20:01

Ja das könnte durchaus sein, sorry ich habe bisher noch nicht so wirklich mit tkinter gearbeitet und bin da noch ein Neuling.

Ich möchte quasi, dass durch die Eingabe in einer GUI und nach Betätigung eines Buttons die Parameter in den entsprechenden Befehl übergeben wird, sodass mein df durch die eingegebenen Parameter gefiltert wird.
Benutzeravatar
Dennis89
User
Beiträge: 1154
Registriert: Freitag 11. Dezember 2020, 15:13

Okay, dann brauchst du noch 'tkinter.Button'.

Wenn du spezielle Probleme mit deinem Code hast, kann man dir gezielter helfen.
So ist es schwierig, es sei denn du willst hier einen fertigen Code.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
ing_datascience
User
Beiträge: 11
Registriert: Sonntag 24. April 2022, 20:01

Für die Eingabe von zwei beliebigen Werten innerhalb einer GUI habe ich folgenden code geschrieben:

from tkinter import *

def Start():
eingabewert1 = eingabe1.get()
eingabewert2 = eingabe2.get()
print(eingabewert1)
print(eingabewert2)

fenster = Tk()
fenster.title("Eingabe")

eingabe1 = Entry(fenster)
eingabe2 = Entry(fenster)
ok_button = Button(fenster, text="ok",command=Start)

eingabe1.pack()
eingabe2.pack()
ok_button.pack()

fenster.mainloop()


Durch betätigung des Buttons "ok" erhalte ich die eingegebenen Werte als Output. Ich möchte aber eine Anfangs- und eine Endzeit (Format 00:00) eingeben und diese Werte für meinen folgenden Befehl nutzen:

df.between_time("07:15", "08:45")

Sodass ich die "07:15" und "08:45" nicht mehr händisch in meinen code ändern muss, sondern über die GUI eingeben kann.
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

Vielleicht habe ich jetzt ein Brett vor dem Kopf, aber ist dein Problem dann nicht schon mit:

Code: Alles auswählen

df.between_time(eingabe1.get(), eingabe2.get())
gelöst?
:?
Benutzeravatar
Dennis89
User
Beiträge: 1154
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

verwende keine *-Importe, dadurch importierst du alle Namen die in 'tkinter' angelegt wurden in deinen Namensraum. Das kann zu Namenskollisionen und unübersichtlichen Code führen, da man nicht mehr nachvollziehen kann, wo welcher Name her kommt.
Auf Modulebene (der Code ohne Einrückungen) gehört nur Code, der Konstanten, Klassen und Funktionen definiert. Ausnahme ist der Einstiegspunkt in die 'main'-Funktion. Das ist auch übrigends die Funktion, aus der das Programm gesteuert wird.
Namen werden nicht durchnummeriert, es sei denn, die Nummer würde mal tatsächlich einen Mehrwert bieten.
Namen schreibt man in Python klein_mit_unterstrich, Ausnahmen sind Konstanten GANZ_GROSS und Klassen in PascalCase-Schreibweise.

Eine Funktion bekommt alles was sie braucht über Argumente. Dass sind die Namen in der Klammer hinter dem Funktionsnamen. Für deinen Fall musst du dich mit der Funktionsweise von 'partial' vertraut machen, damit du deiner aufrufenden Funktion Argumente übergeben kannst.

Fehlt dir für deinen Wunsch jetzt die Formatierung von zwei Strings?
Gibst du in ein Feld '7:15' und in das zweite '8:45' ein?

Code: Alles auswählen

import tkinter as tk
from functools import partial


def get_entry(start, end):
    print(f"{end.get()},{start.get()}")


def main():
    window = tk.Tk()
    window.title("Eingabe")
    start = tk.Entry(window)
    start.pack()
    end = tk.Entry(window)
    end.pack()
    ok_button = tk.Button(window, text="ok", command=partial(get_entry, start, end))
    ok_button.pack()
    window.mainloop()


if __name__ == "__main__":
    main()
"When I got the music, I got a place to go" [Rancid, 1993]
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

@Dennis89: Warum partial() nutzen? Hat das besondere Vorteile? Man könnte die Argumente doch einfach per lambda-Funktion einfügen, dann fällt ein import weg und ing_datascience muss sich nicht über partial() den Kopf zerbrechen (falls lambda-Funktionen bekannt sind).

Code: Alles auswählen

ok_button = tk.Button(window, text="ok", command=lambda: get_entry(start, end))
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

In diesem Fall geht lambda auch, das hat aber den Nachteil, dass der Wert der Variablen nicht zum Zeitpunkt der Definition, sondern zum Zeitpunkt des Aufrufst ausgewertet werden, das ist insbesondere bei for-Schleifen ein beliebter Fehler. Deshalb ist `partial` vorzuziehen, um Fehlerquellen zu vermeiden.
derElch
User
Beiträge: 33
Registriert: Sonntag 25. Februar 2018, 13:14

Onomatopoesie hat geschrieben: Mittwoch 4. Mai 2022, 09:12 @Dennis89: Warum partial() nutzen? Hat das besondere Vorteile? Man könnte die Argumente doch einfach per lambda-Funktion einfügen, dann fällt ein import weg und ing_datascience muss sich nicht über partial() den Kopf zerbrechen (falls lambda-Funktionen bekannt sind).

Code: Alles auswählen

ok_button = tk.Button(window, text="ok", command=lambda: get_entry(start, end))
Diese Frage habe ich auch schon gestellt. Siehe hier: viewtopic.php?p=401788#p401788
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

Ich experimentiere gerade mit partial(), da ich ja nun gerade gelernt habe, dass lambda-Funktionen offenbar nicht der Königsweg sind im Zusammenspiel mit command in tkinter. Nun spiele ich gerade herum und stelle fest, dass ein Code, der mit lambda problemlos läuft, mit partial() nicht klappt. Das deutet darauf hin, dass ich das Konzept nicht kapiert habe ... :roll:

Code: Alles auswählen

import tkinter as tk
from functools import partial

def einfache_funktion(int_var):
    print(int_var)

def test_cases():
    int_liste = [1, 2, 3, 4, 5, 6]
    for i in int_liste:
        yield i

root = tk.Tk()
argument = test_cases()
button = tk.Button(root, text="Klick mich", command=partial(einfache_funktion, next(argument)))
button.pack()
root.mainloop()

Ich bekomme nur 1en geliefert - der Generator tut offenbar nichts. Mit lambda hat das Beispiel funktioniert.
Hat jemand ein Werkzeug, um das Brett vor meinem Kopf zu entfernen? :oops:
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

Den Unterschied habe ich versucht zu erklären: bei partial werden die Argumente bei der Definition des Callbacks evaluiert, bei lambda bei jedem Aufruf.
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Onomatopoesie: Die `einfache_funktion()` kann man einfacher definieren: ``einfache_funktion = print`` hätte es auch getan. Oder einfach gleich `print()` verwenden.

Namen sollten weder Grunddatentypen enthalten wie `int` oder `liste` noch nichtssagende Zusätze wie `var`, denn dass das eine Variable ist, sieht man ja alleine schon daran, dass man dort einen Namen vergeben hat.

Die `test_cases()`-Funktion kann man auch vereinfachen. Die Liste muss man nicht an einen Namen binden:

Code: Alles auswählen

def iter_test_cases():
    for i in [1, 2, 3, 4, 5, 6]:
        yield i
Statt einer ``for``-Schleife könnte man auch ``yield from`` verwenden:

Code: Alles auswählen

def iter_test_cases():
    yield from [1, 2, 3, 4, 5, 6]
Und da die Zahlen aufsteigend und zusammenhängend, beziehungsweise in einer einheitlichen Schrittweite sind, könnte man auch `range()` verwenden. Und das dann statt mit ``yield``/``yield from`` auch mit ``return`` und `iter()`:

Code: Alles auswählen

def iter_test_cases():
    return iter(range(1, 7))
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@Onomatopoesie: die einfache Funktion ist etwas zu einfach, denn ein Teil der Funktionalität hast Du in das partial auszulagern versucht.

Code: Alles auswählen

import tkinter as tk
from functools import partial

def einfache_funktion(test_cases):
    next_testcase = next(test_cases)
    print(next_testcase)

def iter_test_cases():
    return iter(range(1,7))

def main():
    root = tk.Tk()
    testcases = iter_test_cases()
    tk.Button(root, text="Klick mich", command=partial(einfache_funktion, testcases)).pack()
    root.mainloop()

if __name__ == "__main__":
    main()
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

Ja, ich habe es jetzt (hoffe ich) verstanden. Partial wird letztlich nur einmal ausgeführt und die Argumente "eingefroren", während die lambda-Funktion immer wieder neu ausgeführt (erstellt) wird und neue Argumente übergebene werden können. So in etwa?
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Onomatopoesie: Die Erklärung stimmt so nicht ganz. Vielleicht wird es deutlicher, wenn du `partial` und `lambda` durch einfachere Sprachmittel ausdrückst:

Code: Alles auswählen

class partial:
    def __init__(self, f, *args, **kwargs):
        self.f = f
        self.args = args
        self.kwargs = kwargs

    def __call__(self, *args, **kwargs):
        return self.f(*self.args, *args, **self.kwargs, **kwargs)
Eine `partial`-Instanz ist also nichts anderes als ein Objekt, das eine Funktion `f` und eine Menge an Parametern `args` und `kwargs` speichert. Wenn sie aufgerufen wird, werden einfach nur die gespeicherten Parameter zusätzlich übergeben. Und deswegen wird das `next(argument)` nur einmal beim Erstellen der `partial`-Instanz ausgewertet und nicht bei jedem Aufruf.

`lambda` ist einfach nur eine Kurzschreibweise, um eine neue Funktion zu erstellen:

Code: Alles auswählen

f = lambda: einfache_funktion(next(args))
ist das gleiche wie

Code: Alles auswählen

def f():
    return einfache_funktion(next(args))
Die Funktion wird nur einmal erstellt, aber bei jedem Aufruf wird `next(args)` ausgeführt, weil das im Körper der Funktion steht.

Und der von Sirius3 erwähne Fall: Was gibt dieses Programm aus? Und warum?

Code: Alles auswählen

from functools import partial

def main():
    functions = []

    for i in range(5):
        functions.append(partial(print, i))

    for i in range(5):
        functions.append(lambda: print(i))

    for f in functions:
        f()


if __name__ == "__main__":
    main()
Onomatopoesie
User
Beiträge: 41
Registriert: Montag 12. August 2019, 07:52

Ausgabe ist: 0123444444
Ich interpretiere es so (nach deiner ausführlichen Erklärung):
Es wird von 0 bis 4 iteriert und in der Zählschleife "i" gespeichert. Partial gibt die Funktion "print" mit dem aktuellen Argument "i" zurück und das Objekt wird in der Liste gespeichert, so dass in der Liste die Objekte mit der Funktion print samt dem aktuellen Zähler liegen (print(0), print(1), print(2), print(3), print(4)).
Die zweite Schleife tut (augenscheinlich) das Gleiche. Allerdings wird hier lediglich die Funktion erstellt - das Argument wird nicht ausgewertet, es wird nur die Referenz zu "i" übergeben. Hier werden also die Funktionen (print(i), print(i), print(i), print(i), print(i)) in der Liste gespeichert.
Beim Funktionsaufruf in der dritten Schleife erhalte ich also erwartungsgemäß die 01234, weil die Argumente bereits durch Partial gespeichert wurden und der Funktion print übergeben werden. Die Lambda-Funktionen aber erhalten einfach die Referenz zu "i", weil die Funktionen ja nicht in der Schleife ausgeführt worden sind, sondern erst durch den Aufruf in der dritten Schleife. Die for-Schleife ist aber schon durchgelaufen (das ist ja nicht Teil der lambda-Funktion), sodass i == 4 im Speicher liegt. Folglich werden 5x die 4 ausgegeben.
So etwa?
:oops:
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Onomatopoesie: Ja. Mit der Ausnahme, dass es im Fall von `lambda` keine Referenz ist, die gespeichert wird, sondern der Name `i`.

Je nach dem, was man mit „Referenz“ meint, gibt es entweder in Python keine Referenzen, oder alles ist eine Referenz. Deswegen ist es IMHO kontraproduktiv, in Python von Referenzen zu sprechen.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

narpfel hat geschrieben: Donnerstag 5. Mai 2022, 13:30 @Onomatopoesie: Ja. Mit der Ausnahme, dass es im Fall von `lambda` keine Referenz ist, die gespeichert wird, sondern der Name `i`.
Alles natürlich richtig. Aber man könnte das auch lösen, indem man der Lambda-Funktion den Wert explizit übergibt:

Code: Alles auswählen

#!/usr/bin/env python
from functools import partial

def main():
    functions = []

    for i in range(5):
        functions.append(partial(print, i))

    for i in range(5):
        functions.append(lambda i=i: print(i))

    for f in functions:
        f()


if __name__ == "__main__":
    main()
Antworten