Allgemein/Tkinter - Input ignorieren - Rechengang fortsetzen? -mehrfache Berechnungen?

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
Ravenbino
User
Beiträge: 3
Registriert: Mittwoch 27. Februar 2019, 23:17

Hallo liebe Community,

Ich habe am Samstag begonnen mich mit Python zu beschäftigen und habe mir eine kleine Aufgabe gestellt. Ziel ist es für die Uni ein kurzes Programm zu schreiben, dass mir während meinen Arbeiten die ich zu erledigen habe hilft. Eigentlich ist es ganz einfach der Nutzer wird gebeten Zahlenwerte einzugeben und dann wird ein Rechenweg durchgearbeitet.

Jetzt stoße ich an ein Problem, bei dem ich nicht weiter weiß.

Code sieht wie folgt aus:

Code: Alles auswählen

from tkinter import*


root = Tk()
root.title("III",)
root.geometry("300x300")

labelwert = Label(root, text="Wert 1: ")
labelwert.place(x=10, y=10)
Wert = Entry(root, width=20, bg="lightgrey", font="arial")
Wert.place(x=70, y=10)

labelwert2 = Label(root, text="Wert 2: ")
labelwert2.place(x=10, y=30)
Wert2 = Entry(root, width=20, bg="lightgrey", font="arial")
Wert2.place(x=70, y=30)

labelwert3 = Label(root, text="Wert 3: ")
labelwert3.place(x=10, y=50)
Wert3 = Entry(root, width=20, bg="lightgrey", font="arial")
Wert3.place(x=70, y=50)



def rechnen():
    ergebnis.delete(1.0,END)
    try:
        zahlenwerte = Wert.get()
        zahlenwerte2 = Wert2.get()
        zahlenwerte3 = Wert3.get()
    except SyntaxError:
        output.insert(END, "This is not a valid input\n")
    if zahlenwerte == str(0):
        result = (eval(zahlenwerte2) * eval(zahlenwerte3))
        ergebnis.insert(END, str(result))
    elif zahlenwerte2 == str(0):
        result = (eval(zahlenwerte) * eval(zahlenwerte3))
        ergebnis.insert(END, str(result))
    elif zahlenwerte3 == str(0):
        result = (eval(zahlenwerte) * eval(zahlenwerte2))
        ergebnis.insert(END, str(result))
    else:
        result = (eval(zahlenwerte) * eval(zahlenwerte2) * eval(zahlenwerte3))
        ergebnis.insert(END, str(result))




berechne = Button(root, bg="lightgreen", font="TimesNewRoman", text="Berechne", command=rechnen)
berechne.place(x=10,y=90, height=30, width=100,)


labelergebnis = Label(root, text="Ergebnis: ")
labelergebnis.place(x=10, y=140)

ergebnis = Text(root, width=20, bg="lightgrey", height=1)
ergebnis.place(x=70, y=140)




root.mainloop()
Also im Prinzip habe ich dann ein Fenster in dem ich zwei Entry Boxen hab und dann werden die, per Knopfdruck, zusammen multipliziert werden und das Ergebnis wird in einem extra Feld angegeben. (Der Rechenweg wird noch komplexer aber das ist ja mal nebensächlich weil den kann ich ja dann im nachhinein reinpacken ... oder? :roll: )
Nun die Probleme:

1) Ich hätte gerne, dass Entry die ich leer lasse einfach ignoriert werden, ich dachte an etwas wie if .... okay, während ich das geschrieben habe ist mir eine Idee gekommen, die ich jetzt auch eingebaut habe, jeglicher Eintrag mit 0 führt jetzt schlichtweg zu einem Rechenweg der diesen Wert nicht mit einschließt. Aber bei vielen Einträgen wird das ein riesen aufwand + man darf nicht vergessen

2) Wäre gewesen (hihihi das hab ich auch schon gefixt, während dem schreiben), dass ich das Ergebnis lösche und ein neues berechnen kann, nachdem ich neue Zahlen eingegeben hab. Nachdem ich die verschiedenen if-statements hinzugefügt hab, konnte ich den Berechnen Button unendlich oft benutzen das neue Ergebnis wurde aber einfach nur hinten dran gehängt, deswegen hab ich noch ergebnis.delete(1.0,END) davor gestellt.

Jetzt hab ich gar keine Probleme mehr sondern viel mehr fragen: wie kann ich das Entry Feld denn leer lassen (0 tuts zur Zeit auch, leer lassen ist aber schöner)
und weiters diese Zeile ergebnis.delete(1.0,END); ich hatte sie im Kopf ich dachte nur die Parameter sind (0, END) bis ich im Netz auf (1.0, END) stieß. Wieso denn bitte 1.0?
Vielleicht noch eine Frage: ergebnis.insert(END, str(result)) … dass man Information wo hineinspielen muss ist mit klar also ich füge Ergebnis, der Text Box, einen string zu dens mir dann anzeigen kann, aber wofür brauch ich den Parameter END? Ich versteh endlich den Ausdruck never touch a running system :lol:

LG

PS: haut mich nicht weil ich das .place verwende, das hab ich lieber als das grid :D
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ Ravenbino:
1. bin auch Anfänger ...
2. lernt man in der Uni heutzutage nicht mehr, was SATZZEICHEN sind? Nun, gut.
3. die Methode *place()* empfinde ich auch als sehr angenehm.
4. warum schreibst du Wert, Wert2, etc. groß? Macht man nicht.
5. die Breiten- und Höhenangaben in den Widgets würde ich in die Methode place() *auslagern*
6. die Klammern jeweils vor eval() kannst du weglassen. Ich würde dort auch nicht 'eval', sondern *float* verwenden.
7. Die Funktion würde ich direkt (Leerzeile) unterhalb von import setzen
8. was machst du, wenn 2 Zahlenwerte = 0 sind ? Vielleicht solltest du alle 3 Werte als Tupel oder Liste übergeben (Iteration).
9. was passiert, wenn ich Buchstaben oder andere Zeichen eingebe? Du solltest die Eingaben auf ihre Richtigkeit hin überprüfen.
10 leere Entry-Einträge auf Null setzen, wäre auch gut.

EDIT:
11. Bezeichner wie labelwert1, labelwert2, etc. ist aussagslos und nicht zielführend. Hier wäre multiplikator1, multiplikator2 zielführender.

Damit, denke ich, bist du jetzt einmal etwas beschäftigt ... viel Erfolg !
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Ravenbino: Vergiss das es `eval()` gibt. Du willst eine Zeichenkette in eine Zahl wandeln, dann nimm dafür `float()` oder `int()`, je nachdem ob es Gleitkommazahlen oder ganze Zahlen sein sollen. `eval()` führt nahezu beliebigen Python-Code aus solange er als Ausdruck angegeben wird. Das kann zum einen gefährlich werden und man muss auch mit allen möglichen Ausnahmen rechnen, während sich das bei `float()`/`int()` im Grunde auf `ValueError` beschränkt.

Sternchen-Importe sind Böse™. Damit holst Du Dir bei `tkinter` über 100 Namen in Dein Modul von denen Du nur einen ganz kleinen Bruchteil verwendest. Es wird schwieriger nachzuvollziehen wo Namen her kommen und es besteht die Gefahr von Namenskollisionen.

Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Ob Du `place()` lieber magst ist im Grunde keine Geschmacksfrage. Es funktioniert halt nicht wirklich. Bei meinen Systemeinstellungen verschwindet beispielsweise der Doppelpunkt vom 'Ergebnis:'-Label hinter dem `Text`-Widget. GUIs mit absolut platzierten Elementen können je nach Bildschirmauflösung und Schriftgrössen komisch aussehen bis hin zu unbenutzbar werden wenn Elemente hinter anderen verschwinden.

Man muss auch nicht alles in *ein* Container-Widget stecken, sondern kann die GUI auch in mehrere `Frame`\s unterteilen und `grid()` und `pack()` verwenden. Allerdings immer nur eine der beiden Methoden je Container-Widget.

Warum wird für das Ergebnis ein `Text`-Widget verwendet wenn das auf eine Zeile beschränkt wird? Dann kann man da auch ein `Entry` für nehmen.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Ausserdem sollten Funktionen und Methoden alles was sie ausser Konstanten benötigen, als Argument(e) übergeben bekommen.

Daraus folgt bei GUIs letztendlich das man da für jede nicht-triviale GUI nicht ohne objektorientierte Programmierung (OOP) auskommt.

Wenn man anfängt Namen zu nummerieren will man in der Regel bessere Namen oder aber gar keine einzelnen Namen sondern eine Datenstruktur. Oft eine Liste. So auch hier bei den Wert-Eingabefeldern und den Werten selbst. Man spart sich dann auch gleich Code weil man die Eingabefelder und die Werte in Schleifen verarbeiten kann. Die `Label`-Objekte braucht man überhaupt nicht an Namen binden.

Das die Eingabefelder `wert` heissen und ein einzelner Wert daraus dann `zahlenwerte` (Mehrzahl) ist verwirrend. Genau so `ergebnis` und `result` für Ergebnis und Ergebnisanzeige. Das ist schwer auseinander zu halten. An den Namen für die Anzeige-/Eingabeelemente sollte man erkennen können um was es sich da handelt.

Innerhalb des ``try``-Blocks kann gar kein `SyntaxError` auftreten. Diese Ausnahmebehandlung macht also gar keinen Sinn. Zudem würde sie versuchen auf ein nicht vorhandenes `output` zuzugreifen und damit gleich die nächste Ausnahme auslösen.

Wenn man etwas in *jedem* Zweig von einem ``if``/``elif``/``else``-Konstrukt am Anfang und/oder am Ende macht, dann kann man das auch nur *einmal* davor/danach machen.

str(0) ist eine etwas umständliche Schreibweise für '0'. Ist aber an der Stelle auch gar nicht das mit dem Du da vergleichen möchtest, denn wenn da '0' stehen würde hätte der Benutzer ja etwas eingegeben.

Die eigentliche Berechnung möchte in der Regel auch noch mal vom GUI-Code trennen, damit sich das leicht testen lässt.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial, reduce
from operator import mul
import tkinter as tk


def calculate(values):
    return reduce(mul, values, 1)


def do_calculate(input_entries, result_entry):
    values = list()
    for entry in input_entries:
        try:
            value = float(entry.get())
        except ValueError:
            pass  # Ignore invalid inputs.
        else:
            values.append(value)
    
    result = calculate(values)
    
    result_entry.delete(0, tk.END)
    result_entry.insert(tk.END, str(result))


def main():
    root = tk.Tk()
    root.title('III')

    frame = tk.Frame(root)
    input_entries = list()
    for i in range(3):
        tk.Label(frame, text=f'Wert {i + 1}: ').grid(row=i, column=0)
        entry = tk.Entry(frame, width=20)
        entry.grid(row=i, column=1)
        input_entries.append(entry)
    frame.pack()

    calculate_button = tk.Button(root, text='Berechne')
    calculate_button.pack()

    frame = tk.Frame(root)
    tk.Label(frame, text='Ergebnis: ').pack(side=tk.LEFT)
    result_entry = tk.Entry(frame, width=20)
    result_entry.pack(side=tk.LEFT)
    frame.pack()

    calculate_button['command'] = partial(
        do_calculate, input_entries, result_entry
    )
    root.mainloop()


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Ravenbino
User
Beiträge: 3
Registriert: Mittwoch 27. Februar 2019, 23:17

@__blackjack__ Bist du narrisch, das ging amal schnell.
Man möchte ja nicht undankbar wirken bei dem Arbeitsaufwand den du hattest (wobei es für dich womöglich ja leicht war) aber ich schau mal ob ich alles richtig verstanden habe:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial, reduce
from operator import mul
import tkinter as tk
du lädts functools sowie operator aber lediglich die Funktionen partial, reduce und mul
außerdem tkinter als tk, sodass man alle Funktionen mit tkinter Funktionen mit tk. beginnen muss (zwecks Wortwiederholung)

Code: Alles auswählen

def calculate(values):
    return reduce(mul, values, 1)
calculate bezogen auf values soll folgende Werte zurückgeben: eine Multiplikation, der Werte aus Values und das immer um 1 weiter. Also die liste wird angegeben (values) und es soll sich immer um eine Wert weiter vorgearbeitet werden


Code: Alles auswählen

def do_calculate(input_entries, result_entry):
    values = list()
    for entry in input_entries:
        try:
            value = float(entry.get())
        except ValueError:
            pass  # Ignore invalid inputs.
        else:
            values.append(value)
    
    result = calculate(values)
    
    result_entry.delete(0, tk.END)
    result_entry.insert(tk.END, str(result))
do_calculate greift auf die werte input_entries sowie reslut_entry zu? oder.. arbeitet mit ihnen?
Erstellt die liste values für die gilt, dass value gleich einer Gleitkommazahl ist die aus entry bezogen werden soll. Ausgenommen wenn ein Wertefehler kommt, dann soll dieser übersprungen werden. else: die Liste values soll erweitert werden um das was value ausgibt. So habs halt ich verstanden, ich versteh nur nicht ganz warum ich da jetzt else: schreiben muss, ist es nicht ein vorgang der so und so nach try passieren soll?

Das funktioniert nämlich auch:

Code: Alles auswählen

def do_calculate(input_entries, result_entry):
    values = list()
    for entry in input_entries:
        try:
            value = float(entry.get())
        except ValueError:
            pass  # Ignore invalid inputs.       
        values.append(value)
    
    result = calculate(values)
    
    result_entry.delete(0, tk.END)
    result_entry.insert(tk.END, str(result))
weiters:

Code: Alles auswählen

def main():
    root = tk.Tk()
    root.title('III')

    frame = tk.Frame(root)
    input_entries = list()
    for i in range(3):
        tk.Label(frame, text=f'Wert {i + 1}: ').grid(row=i, column=0)
        entry = tk.Entry(frame, width=20)
        entry.grid(row=i, column=1)
        input_entries.append(entry)
    frame.pack()

    calculate_button = tk.Button(root, text='Berechne')
    calculate_button.pack()

    frame = tk.Frame(root)
    tk.Label(frame, text='Ergebnis: ').pack(side=tk.LEFT)
    result_entry = tk.Entry(frame, width=20)
    result_entry.pack(side=tk.LEFT)
    frame.pack()

    calculate_button['command'] = partial(
        do_calculate, input_entries, result_entry
    )
    root.mainloop()
Ein Frame wird erstellt, dann eine Liste. für die Liste gilt i in der Reihenfolge(3), dass ein Label erstellt werden soll, wobei der Text immer "Wert i+1" ist. Platziert im Gitter in der Reihe i (also reihe 1, 2, 3 da i im Umfang von 3, also 3 Stellen, dargestellt werden soll; etwas sehr umständlich ausgedrückt).
Dann kommt der Entry in den Frame und wird am Grid platziert, wobei die Reihe wieder i entspricht, wir wollen ja, dass es auf der gleichen Höhe ist und um es nach rechts einzurücken setzen wir die Spalte auf 1. Dann setzt man input_entries.append(entry) also die Eingaben von entry sollen als Liste auf input_entries gespeichert werden? Ist das korrekt?

Dann kommt der Button, der einfach mal hingepackt wird, Funktion kommt nachher.

Wieder der frame wobei ich jetzt nicht verstehe warum wir nochmal frame = tk.Frame(root) hinschreiben müssen, wir haben den frame doch schon erstellt? Auf jeden Fall kommt jetzt wieder ein Label für das Ergebnis, das wird einfach nach links gepackt, nachdem die oberen Reihen schon besetzt sind kommt es halt ganz unten links hin.
Jetzt noch der Entry der ebenfalls links platziert wird, somit neben dem Label Ergebnis

Fehlt noch die Funktion für den Button, wobei die Funktion wäre, dass do_calculate ausgeführt wird, wobei diese Funktion 2 parameter erfordert, input_entries und result_entry, die wir noch dazu angeben müssen.

Code: Alles auswählen

if __name__ == '__main__':
    main()
Ich nix verstehen das. Nein aber wirklich das verstehe ich gar nicht und ich konnte mir auch keinen Reim machen wieso das so ist oder was das bedeutet wieso brauch ich hier denn ein if? womit setz ich das gleich? warum passiert nichts wenn ich einfach main() benutze.

Vielen Lieben Danke für die Unterstützung, auf die weiter Kritik geh ich dann nachher auch gleich ein :lol: :lol: :lol: :mrgreen:
__deets__
User
Beiträge: 14535
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es klingt nicht so, als ob deine Erlkaerung zu reduce korrekt ist. Darum das nochmal beschrieben:

reduce ist eine "higher order function", sprich eine Funktion, die ihrerseits eine Funktion als Argument bekommt. Es gibt ein paar davon, map, filter und reduce sind aber die ganz grossen Klassiker.

Und was macht reduce nun? Es wendet die uebergebene Funktion (in deinem Fall mul fuer Multiplikation aus dem operator-Modul) die *immer* zwei Argumente bekommen akzeptieren muss sukzessive auf die Elemente der Liste an. Das Ergebnis wird dann wieder eine Eingabe fuer den naechsten Aufruf. Und wo dein Verstaendnis dann denke ich komplett daneben liegt ist bei der 1. Das ist *nicht* eine Schrittweite. Das ist der Startwert. Denn reduce muss *immer* zwei Argumente uebergeben an die mul-Funktion. Fuer den ersten Wert aus der Liste hat es aber keinen Wert vorher berechnet. Den musst du also angeben. Das Ergebnis von

Code: Alles auswählen

reduce(mul, [4, 5, 6], 10)
ist also konzeptionell eine Abfolge von mul-Aufrufen:

Code: Alles auswählen

mul(6, mul(5, mul(4, 10)))
Oder (naeher dran an der wirklichen Implementierung):

Code: Alles auswählen

def mein_reduce(func, values, start):
       current = start
       for value in values:
             current = func(current, value)
       return current
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Ravenbino: Das dritte Argument von `reduce()` ist der Startwert. Bei `add()` statt `mul()` würde man hier beispielsweise 0 wählen (und hätte das was man schon als `sum()`-Funktion hat). Das entscheidet letztendlich nur darüber was passiert wenn `values` leer ist. Ohne die 1 würde dann nämlich eine Ausnahme ausgelöst werden:

Code: Alles auswählen

In [9]: reduce(mul, [42, 23], 1)
Out[9]: 966

In [10]: reduce(mul, [42], 1)
Out[10]: 42

In [11]: reduce(mul, [], 1)
Out[11]: 1

In [12]: reduce(mul, [42, 23])
Out[12]: 966

In [13]: reduce(mul, [42])
Out[13]: 42

In [14]: reduce(mul, [])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-afb5e523a920> in <module>()
----> 1 reduce(mul, [])

TypeError: reduce() of empty sequence with no initial value
Das ``else`` beim ``try``/``except`` soll nicht sowieso passieren sondern nur wenn es keine Ausnahme gab. Denn wenn es eine Ausnahme gab, dann gibt es ja auch keinen gültigen Wert für `value` den man an die Liste anhängen könnte.

Deine Alternative funktioniert nicht. Wenn die erste Eingabe einen ungültigen Wert enthält, ist `value` gar nicht definiert und es kommt zu einer Ausnahme an der Stelle wo versucht wird `value` zu verwenden. Ansonsten wird der noch vorhandene Wert aus einem vorherigen gültigen Eingabe *noch mal* an die Liste angehängt. Weil bei einer ungültigen Eingabe im ``try``-Block kein neuer Wert an `value` gebunden wird. Welcher sollte das auch sein.

In `input_entries` werden nicht Eingaben gespeichert sondern die Eingabefelder. Eingaben vom Benutzer gibt es zu diesem Zeitpunkt ja noch nicht. Und `i` läuft von 0 bis 2 und nicht von 1 bis 3. Informatiker fangen beim zählen gerne mit 0 an. :-)

Beim Layout stimmt Deine Beschreibung nicht so ganz. Es gibt im Fenster drei Elemente die untereinander gesetzt werden: Einen Frame mit den Beschriftungen/Eingabefeldern, eine Schaltfläche, und dann noch einen Frame mit Beschriftung und Ergebnis-”Eingabefeld”. Der Frame oben und der letzte unten sind zwei verschiedene. Im oberen wird ein Grid-Layout verwendet, im unteren habe ich Label und Entry einfach von Links nach Rechts ge-pack-t. Wenn man das Ergebnis auch in den oberen Frame per Grid gesetzt hätte, wäre es ja *über* der Schaltfläche, denn der Frame ist ja über der Schaltfläche im Fenster.

`__name__` enthält den Modulnamen, also in der Regel den Dateinamen ohne die *.py-Endung. Ausser wenn man ein Modul als Programm startet. Dann ist `__name__` an den Wert '__main__' gebunden. Man kann das Modul mit diesem ``if`` also als Programm ausführen, aber auch in einer Python-Shell oder einem anderen Python-Programm/Modul importieren, und zwar ohne das automatisch gleich die `main()`-Funktion aufgerufen wird.

Das ist praktisch zur Fehlersuche, weil man das Modul importieren und einzelne Funktionen live ausprobieren kann, oder wenn man Funktionen in anderen Modulen wiederverwenden möchte, oder für automatisierte Tests, oder Dokumentationswerkzeuge die erwarten das man Module importieren kann ohne das die irgend etwas machen ausser den Inhalt des Moduls zu definieren.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Ravenbino
User
Beiträge: 3
Registriert: Mittwoch 27. Februar 2019, 23:17

Vielen Dank für die ganzen Erklärungen, jetzt wird ich mal schaun ob ich sie auch verstehen und irgendwo anders neue umsetzen kann :lol:
Antworten