Python Gtk Taschenrechner

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Donnerstag 15. Dezember 2016, 17:26

Hallo,

ich bin grade dabei mit Python Gtk einen Taschenrechner zu programmieren und hänge bei den Mathe Operationen fest. Ich habe den Großteil der Buttons fertig nur ich frage mich jetzt wie ich es schaffe den +*-/ und dem = Button die Signale zu übergeben die sie brauchen um z.B. zwei Zahlen zu addieren. Außerdem frage ich mich wie ich es schaffe die Zahlen sozusagen in den Zahlen zwischenzuspeichern die ich dann für die Rechnung benutze(also erste_zahl + zweite_zahl = Ergebnis oder so ähnlich). Ich habe schon das ganze Internet leer gesucht aber keinen guten Ansatzpunkt oder einen Tipp gefunden. Hier ist ein bisschen von meinem Code:

Code: Alles auswählen

class MainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title = "Calculator")
        self.set_size_request(100,100)

        vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        self.add(vbox)

        self.entry = Gtk.Entry()
        self.entry.set_text("")
        vbox.pack_start(self.entry, True, True, 0)

        vbox2 = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL, spacing= 6)
        self.add(vbox2)

        button1 = Gtk.Button(label="1")
        button1.connect("clicked", self.button1_clicked)
        vbox.pack_start(button1, True, True, 0)
        vbox.pack_end(button1, True, True, 0)
        self.add(button1)

        button2 = Gtk.Button(label="2")
        button2.connect("clicked", self.button2_clicked)
        vbox.pack_start(button2, True, True, 0)
        vbox.pack_end(button2, True, True, 0)
        self.add(button2)

        button3 = Gtk.Button(label="3")
        button3.connect("clicked", self.button3_clicked)
        vbox.pack_start(button3, True, True, 0)
        vbox.pack_end(button3, True, True, 0)
        self.add(button3)


        button4 = Gtk.Button(label="4")
        button4.connect("clicked", self.button4_clicked)
        vbox.pack_start(button4, True, True, 0)
        vbox.pack_end(button4, True, True, 0)
        self.add(button4)

        button3 = Gtk.Button(label="+")
        button3.connect("clicked", self.button3_clicked)
        vbox.pack_start(button3, True, True, 0)
        vbox.pack_end(button3, True, True, 0)
        self.add(button3)

        button4 = Gtk.Button(label="=")
        button4.connect("clicked", self.button4_clicked)
        vbox.pack_start(button4, True, True, 0)
        vbox.pack_end(button4, True, True, 0)
        self.add(button4)

        button7 = Gtk.Button(label="*")
        button7.connect("clicked", self.button7_clicked)
        vbox.pack_start(button7, True, True, 0)
        vbox.pack_end(button7, True, True, 0)
        self.add(button7)

        button8 = Gtk.Button(label="/")
        button8.connect("clicked", self.button8_clicked)
        vbox.pack_start(button8, True, True, 0)
        vbox.pack_end(button8, True, True, 0)
        self.add(button8)


    def button1_clicked(self, widget):
        self.entry.set_text(self.entry.get_text() + str(1))

    def button2_clicked(self, widget):
        self.entry.set_text(self.entry.get_text() + str(2))
Ich weiß ist nicht das Gelbe vom Ei, trotzdem würde ich mich über eine hilfreiche Antwort freuen.
Mfg
Rise Blink
Zuletzt geändert von Anonymous am Donnerstag 15. Dezember 2016, 17:28, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

Donnerstag 15. Dezember 2016, 17:43

@Rise Blink: Aus dem Ausschnitt sieht man jetzt nicht wie dieser Taschenrechner eigentlich funktionieren soll. Also insbesondere was da beispielsweise bei den Rechenoperationen passieren soll. Ein ”normaler” Taschenrecher würde dann ja intern eine Zustandsänderung vornehmen um ab da die nächste Zahl entgegen zu nehmen und sich die Operation zu merken, und falls schon Operationen vorher waren zu schauen wie es mit dem Operatorvorrang aussieht und ob da schon eine Teilauswertung vorgenommen werden soll und so weiter. So ein ganz normaler handelsüblicher Taschenrecher ist wesentlich komplexer als das für Anfänger geeignet wäre.

Darum bleiben meistens zwei andere Varianten: Man sammelt erst einmal alles als Zeichenkette, inklusive Rechenoperationen und wertet das dann aus wenn ``=`` gedrückt wurde, oder man implementiert einen RPN-Taschenrechner letzteres ist eigentlich einfacher, wenn man bei ersterem die Finger von `eval()` lässt, was man IMHO Anfängern gar nicht erst zeigen sollte, weil die damit nur Unsinn machen und die sinnvollen Einsätze davon doch eher eingeschränkt sind.

Namen sollte man nicht durchnummerieren. An dem Methodennamen `button8_clicked()` sollte man beispielsweise erkennen können *was* diese Methode macht, nicht das sie mit der 8. Schaltfläche verbunden ist, die erstellt wurde. Die auch nicht `button8` heissen sollte, denn auch hier möchte man ja wissen was die Schaltfläche macht, nicht das sie die 8. ist. Oder man nennt alle eindfach nur `button` wenn der Wert später sowieso nirgends mehr benötigt wird.

Funktionen oder Methoden die nahezu das gleiche machen, sollte man nicht im Quelltext immer wieder kopieren und leicht anpassen, sondern die Unterschiede als Parameter herausziehen und als Argument(e) übergeben. Das betrifft beispielsweise die Ziffern, aber auch die binären Operationen. Zur Übergabe dieser Argumente bei Rückrufen von Schaltflächen ist `functools.partial()` dann sehr nützlich.
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Freitag 16. Dezember 2016, 18:13

Hi,

also der Taschenrechner soll nur die Grundrechenarten können und floats braucht der nicht können. Der Code ist nur zum verdeutlichen wie ich das programmiert habe. Mein "richtiger" Code hat auch noch die ganzen anderen Buttons und so weiter. Ich muss nur irgendwie herausfinden wie ich diese Zustandsänderung hinkriege. Mit eval() hab ich mich auch ein bisschen beschäftigt aber nichts programmiert. Zudem weiß ich nicht ob das mit dem vorliegenden Code überhaupt lösbar ist. Bei einer Antwort, bitte so einfach wie möglich erklären, weil ich noch nicht lange mit Python programmiere.

Grüße
BlackJack

Samstag 17. Dezember 2016, 01:12

@Rise Blink: Du musst Dir halt überlegen welche Ereignisse von aussen, also vom Anwender herein kommen können, und wie Dein Taschenrechner da jeweils dann drauf reagieren soll. Was passiert wenn die Ziffern gedrückt werden, was wenn eine Operationstaste gedrückt wird, was wenn Operationstasten hintereinander gedrückt werden, ohne das Ziffern gedrückt werden, und so weiter. Und wie ”normal” der Rechner sein soll. Was soll beispielsweise bei der Tastenfolge [4] [2] [+] [2] [3] [*] [2] [=] als Ergebnis angezeigt werden? 88 oder 130? Beim mathematisch korrekten Ergebnis (88) reicht es nicht sich zwei Zahlen zu merken, da muss man mit Stapelstrukturen für Zahlen und Operationen arbeiten und muss beim Auswerten den Operatorvorrang berücksichtigen.

Oder Du verwendest `eval()`, hast dann aber eher gelernt was man nicht machen sollte und eine umständliche GUI für etwas was man wesentlich einfacher und mächtiger haben kann wenn man einfach eine Python-Shell (IDLE, Jupyter's Qt-Konsole, Python in einem Terminal/der Eingabeaufforderung) startet und seine Rechnungen dort durchführt.

In beiden Fällen fehlt mir persönlich so ein bisschen die *konkrete* Frage die Du über das Forum beantwortet haben möchtest. Das erste ist recht umfangreich und zumindest ein recht ambitioniertes Anfängerprojekt, das zweite ist im Grunde keine Herausforderung. Oder falls doch, müsstest Du mal ganz konkret beschreiben wo da das Problem liegt.
BlackJack

Samstag 17. Dezember 2016, 11:50

@Rise Blink: Vielleicht nochmal ein paar Optionen zum Auswählen:

Man kann für einen Rechner gar nichts selber implementieren, und einfach Python oder eine andere Python-Shell (IPython, BPython, …) in der Konsole starten oder IDLE wenn es eine grafische Oberfläche sein soll, oder Jupyter's Qt-Console. Das wäre eine Lösung die am wenigsten Arbeit macht und deutlich mehr kann als ganze Zahlen und vier Grundrechenarten. Bei Jupyter, wenn man Numpy, Matplotlib, Sympy & Co dazu nimmt, sogar noch mehr.

Falls man eine interaktive Python-Shell unbedingt mit einer Gtk-Oberfläche haben möchte, könnte man das auf `code.InteractiveConsole` aus der Standardbibliothek aufbauen. Hier muss man Code schreiben der `sys.stdout` und `sys.stderr` ersetzt und dafür sorgt, dass die Ausgaben in der GUI landen. Oder man erstellt eine einfache GUI, die aus einem Textfeld für die Ausgabe(n) und einem Eingabefeld für die Ausdrücke besteht und arbeitet mit `compile()` und ``exec``.

Falls man das Problem auf ganze Zahlen und die vier Grundrechenarten beschränken möchte, kann man entweder das `ast`-Modul aus der Standardbibliothek verwenden um die Eingabe auf diese Einschränkungen zu prüfen, oder man schreibt sich selber einen Parser für solche einfachen Rechenausdrücke. Wenn man keine Klammern und keinen Operatorvorrang berücksichtig ist das sehr einfach, ansonsten muss man sich ein bisschen mit Parsern und Auswertungsstrategien von solchen Ausdrücken beschäftigen. Also beispielsweise die Eingabe in einen Abstrakten Syntaxbaum überführen und Code schreiben, der den dann ausführt, also die Berechnung durchführt. Oder man überlegt sich wie man die Eingabe auswertet wenn man ein Token nach dem anderen geliefert bekommt. Das ist etwas was man für einen ”normalen” Taschenrechner letztendlich auch machen würde, denn dort steht nicht eine Zeichenkette von Anwender am Anfang, die in Token zerlegt wird, sondern der Anwender liefert die Token der Reihe nach durch Eingabe über die Taschenrechner-Tastatur. Ist vom Prinzip her also das gleiche.

Sollte es gar ausreichen das man nur zwei ganze Zahlen mit einer der vier Grundrechenarten verknüpft, kann man einfach eine GUI schreiben die entsprechende Widgets für die Eingabe beiden Zahlen und der Grundrechenart zur Verfügung stellt und eine Schaltfläche um die Rechnung zu starten, deren Ergebnis dann in einem Label angezeigt wird. Simpler geht's kaum. Und das wäre auch der Ansatz den ich einem absoluten Anfänger empfehlen würde.

Braucht die GUI unbedingt eine Taschenrechner-Tastatur, kann man entweder den `compile()` und ``exec``-Ansatz verwenden und hat statt der Rechnertastatur nur die virtuelle Taschenrechner-Tasten auf dem Bildschirm zur Eingabe. Oder man nimmt die Rechnerlogik die Token entgegen nimmt und passt die ein wenig an. Zum Beispiel lassen einen ”normale” Taschenrechner die Rechenart wechseln in dem man die entsprechende Taste drückt, obwohl man vorher schon eine andere Rechenart-Taste betätigt hat. Das ist etwas was man bei einem Rechner der einen Text entgegen nimmt und auswertet, eher als Fehler quittieren würde.
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Samstag 17. Dezember 2016, 15:54

[Sollte es gar ausreichen das man nur zwei ganze Zahlen mit einer der vier Grundrechenarten verknüpft, kann man einfach eine GUI schreiben die entsprechende Widgets für die Eingabe beiden Zahlen und der Grundrechenart zur Verfügung stellt und eine Schaltfläche um die Rechnung zu starten, deren Ergebnis dann in einem Label angezeigt wird. Simpler geht's kaum. Und das wäre auch der Ansatz den ich einem absoluten Anfänger empfehlen würde.]

Hi nochmal,

also was du da geschrieben hast versuche ich eigentlich "nur" herauszufinden. Aber ich kriege es irgendwie nicht hin das zwei Zahlen z.B, addiert werden, weil diese müssten ja dann in zwei Variablen gespeichert werden und das Ergebnis auch. Nur wie bringe ich das dem Programm zu verstehen. Mit einer simplen if-Anweisung wo dann gefragt wird ob "+" gedrückt wurde? Und wenn ja wie müsste ich dann die Grundrechenarten definieren? Ich hoffe du kannst mir helfen.

Grüße
BlackJack

Sonntag 18. Dezember 2016, 11:57

@Rise Blink: Du müsstest die beiden Zahlen und die Operation halt aus den Widgets abfragen. Je nach dem welche Widgets Du für die Zahleingabe und die Operation verwendest, sieht das leicht anders aus. Wenn Du `Gtk.Entry` verwendest, dann muss die Zeichenkette die du von dort bekommst, noch in eine Zahl umgewandelt werden.

Die Operation kannst Du mit ``if/``elif``\s prüfen und dann die Rechnung entsprechend durchführen. Oder man könnte ein Wörterbuch anlegen das Zeichenketten mit den Operatoren auf eine Funktion abbildet, die jeweils zwei Argumente entgegen nimmt und die entsprechende Rechnung durchführt. Die muss man nicht einmal selber schreiben, denn das `operator`-Modul in der Standardbibliothek hat bereits eine Funktion für jeden Operator den Python kennt.
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Sonntag 18. Dezember 2016, 12:42

Also ich benutze ganz normal die Buttons als Widgets. Sowohl für die Zahlen als auch für die Rechenoperationen und deren Werte werden dann im Entry angezeigt. Also muss ich irgendwie bei Klicken des Buttons die Zahlen in zuvor definierte Variablen speichern und die jeweiligen Rechnungen mit einer if elif Anweisung durchführen. Das habe ich auch schon versucht aber die Variablen(z.B. self.erste_zahl und self.zweite_zahl) kriegen den Wert von dem Button den ich klicke nicht übertragen. Das ist eigentlich mein Hauptproblem. Wenn ich das gelöst kriege dürfte ich den Rest auch auf die Kette kriegen.


Danke im Voraus
BlackJack

Sonntag 18. Dezember 2016, 14:29

@Rise Blink: Ich glaube wir reden ein wenig aneinander vorbei. Ich rede von einer GUI die zwei Texteingabefelder für die beiden Zahlen hat, entweder eine Dropdown-Auswahl oder Radiobuttons für die Wahl der Rechenoperation, ein Label für das Ergebnis, und eine Schaltfläche um die Berechnung anzustossen. Du scheinst immer noch von einer UI auszugehen die wie ein Taschenrechner aussieht.
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Sonntag 18. Dezember 2016, 16:03

http://image.slidesharecdn.com/pythonpr ... 1394450046

Also so oder so ähnlich muss ich den Taschenrechner am Ende haben nur ich bin mir nicht sicher ob ich wenn ich deinen Anweisungen folge ein derartiges Ergebnis habe. Ich hab zwar viel Spielraum bei dem Projekt aber am Ende sollte der schon so oder so ähnlich aussehen. Und da kann man ja ganz normal wie bei einem Taschenrechner auf dem Handy die Tasten klicken die werden dann im Eingabefeld angezeigt und bei Klicken von + * etc. wird dann eine Rechnung im Hintergrund ausgeführt und bei Klicken von = wird das Ergebnis angezeigt. Und das muss ich so irgendwie hinkriegen. Das ist auf jeden Fall die Vorgabe. Vielleicht ist auch schon in einer deiner Antworten der Ansatzpunkt den ich brauche und ich hab den einfach übersehen.

Grüße
BlackJack

Sonntag 18. Dezember 2016, 17:19

@Rise Blink: Also wenn da nicht erst die komplette Eingabe gesammelt und dann mit `compile()`/`exec()` ausgewertet werden soll, dann musst Du Dir tatsächlich die Zustände überlegen die so ein Taschenrechner haben kann und wie dann jeweils auf die Tasten reagiert werden muss/soll. Da ist dann als erstes mal die Frage zu klären was bei [4] [2] [+] [2] [3] [*] [2] [=] heraus kommen soll. Einfacher wäre es wenn 130, korrekter wenn 88 das Ergebnis sein soll. Man kann aber auch erst einmal 130 anvisieren und dann überlegen was man an der Implementierung ändern muss um auf 88 zu kommen.

Und bei dem Druck auf eine Rechenoperation kann nicht immer eine Rechnung durchgeführt werden, ausser man implementiert einen RPN-Rechner (und der Benutzer verwendet den richtig). Die Grundrechenarten sind ja alles binäre Operationen, man kann aber immer nur eine Zahl im Display eingeben. Wenn man sich das Beispiel vom letzten Absatz mal anschaut, dann hat der Benutzer ja erst die 42 eingegeben wenn er die [+]-Taste drückt. Den zweiten Operanden hat man da ja noch nicht. Also musst Du Dir den ersten Operanden und die Operation merken, und erst wenn der Benutzer die zweite Zahl und die zweite Operation oder die [=]-Taste gedrückt hat, darfst Du auswerten. Und wenn das Ergebnis 88 sein soll, dann darf man auch nicht bei jeder Operation gleich Auswerten was man bisher schon an Zahlen und Operationen hat. Spätestens dann braucht man eine Speichermöglichkeit für mehr als zwei Operanden und eine Operation, also eine Datenstruktur wie eine Liste. Eigentlich einen Stapel, aber Python's Listen haben Operationen mit denen man sie auch als Stapel verwenden kann.

Ich würde an Deiner Stelle von der GUI erst einmal Abstand nehmen und die Programmlogik des Taschenrechners komplett ohne GUI implementieren. und auch mit den Datentypen mit denen ein Taschenrechner arbeitet, also Zahlen, statt die Eingaben in Zeichenketten zu sammeln. Du hast also ein Objekt das man nach der Displayanzeige als Zeichenkette fragen kann. Das kann eine Darstellung einer Zahl liefern, aber auch eine Fehlermeldung falls der Benutzer eine illegale Eingabe gemacht hat, oder beispielsweise auch wenn versucht wurde durch 0 zu teilen.

Und es muss eine Methode geben mit der man einen Ziffernwert hinzufügen kann. Und eine für Rechenoperationen. Und eine für die endgültige Auswertung (wird später mit der [=]-Taste verbunden). Und vielleicht noch eine die den Rechner wieder in den Grundzustand versetzt. Die würde man von der `__init__()` aus aufrufen, denn so ein Reset muss ja auch am Anfang einmal passieren.

Jetzt musst Du Dir überlegen was bei einer Rechnung in welcher Reihenfolge aufgerufen werden würde und was daraufhin ”im innern” des Taschenrechners passieren muss. Was muss man sich merken, wie muss bei welchem Ereignis von aussen und unter Berücksichtigung vom inneren Zustand darauf reagiert werden, so dass die Methode die den Displaywert abruft, das erwartete Ergebnis liefert. Spiel das am besten mit Papier und Bleistift mal für ein paar Rechnungen durch. Und dann auch für Falscheingaben und überlege Dir wie der Rechner darauf reagieren soll.
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Montag 19. Dezember 2016, 09:12

Okay danke für deine Hilfe. Ich probier es mal.


Grüße
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Mittwoch 21. Dezember 2016, 10:51

Also ich habe jetzt ein bisschen rumprobiert und habe glaube ich einen ganz guten Ansatz der mich mehr oder weniger zum Ziel bringen wird:

Code: Alles auswählen

        self.erste_zahl = 0
        self.zweite_zahl = 0
        self.ergebnis = 0
        self.rechnung = ""


    def button1_clicked(self, button1):
        self.entry.set_text(self.entry.get_text() + str(1))

    def button2_clicked(self, button2):
        self.entry.set_text(self.entry.get_text() + str(2))

    def button3_clicked(self, button3):
        self.entry.set_text(self.entry.get_text() + str(3))

    def button4_clicked(self, button4):
        self.entry.set_text(self.entry.get_text() + str(4))

    def button5_clicked(self, button5):
        self.entry.set_text(self.entry.get_text() + str(5))

    def button6_clicked(self, button6):
        self.entry.set_text(self.entry.get_text() + str(6))

    def button7_clicked(self,button7):
        self.entry.set_text(self.entry.get_text() + str(7))

    def button8_clicked(self,button8):
        self.entry.set_text(self.entry.get_text() + str(8))

    def button9_clicked(self,button9):
        self.entry.set_text(self.entry.get_text() + str(9))

    def button0_clicked(self,button10):
        self.entry.set_text(self.entry.get_text() + str(0))

    def add_clicked(self,button11, data):
        self.entry.set_text(self.entry.get_text() + str("+"))
        self.equal_clicked(self.ergebnis)
        self.rechnung = data

    def minus_clicked(self,button12, data):
        self.entry.set_text(self.entry.get_text() + str("-"))
        self.equal_clicked(self)
        self.rechnung = data

    def times_clicked(self,button13, data):
        self.entry.set_text(self.entry.get_text() + str("*"))
        self.equal_clicked(self)
        self.rechnung = data

    def div_clicked(self, button14, data):
        self.entry.set_text(self.entry.get_text() + str("/"))
        self.equal_clicked(self)
        self.rechnung = data

    def equal(self, button15):
        self.equal_clicked(self.ergebnis)
        self.entry.set_text(self.ergebnis)

    def equal_clicked(self, ergebnis):
        if self.rechnung == "plus":
            self.ergebnis = self.erste_zahl + self.zweite_zahl
            self.entry.set_text(str(self.ergebnis))
        elif self.rechnung == "minus":
            self.ergebnis = self.erste_zahl - self.zweite_zahl
            self.entry.set_text(str(self.ergebnis))
        elif self.rechnung == "times":
            self.ergebnis = self.erste_zahl * self.zweite_zahl
            self.entry.set_text(str(self.ergebnis))
        elif self.rechnung == "division":
            try:
                self.ergebnis = self.erste_zahl / self.zweite_zahl
                self.entry.set_text(str(self.ergebnis))
            except ZeroDivisionError:
                self.erste_zahl = 0
                self.zweite_zahl = 0
                self.entry.set_text("error")
                self.rechnung = ""
                return

Aber der Teil hier läuft nicht so wie er laufen soll, ich weiß aber nicht was ich da verändern könnte:

Code: Alles auswählen

            self.ergebnis = self.erste_zahl + self.zweite_zahl
            self.entry.set_text(str(self.ergebnis))

Kannst du mir da weiterhelfen? Danke im Voraus
Zuletzt geändert von Anonymous am Mittwoch 21. Dezember 2016, 11:54, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 8297
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 21. Dezember 2016, 11:45

@Rise Blink: wo setzt Du self.rechnung, self.erste_zahl oder self.zweite_zahl? Welche Werte haben die Attribute, wenn Du die Rechnung durchführst?
Rise Blink
User
Beiträge: 9
Registriert: Mittwoch 7. Dezember 2016, 16:01

Mittwoch 21. Dezember 2016, 11:59

Die habe ich in in den ersten Zeilen gesetzt und die haben alle den Wert 0. Das Problem ist jetzt das wenn ich zwei Werte z.B. addiere 0 rauskommt. Weil self.ergebnis halt 0 ist. Also sind die Werte mit denen ich rechne nicht in self.erste_zahl und self.zweite_zahl gespeichert worden. Ich weiß mein Code ist schrottig aber wäre super wenn du mir einen Tipp oder so geben könntest mit dem ich meinen Code ans Laufen kriege.
Antworten