@Commander: Noch ein paar Anmerkungen:
Sternchenimporte sollte man vermeiden. Insbesondere wenn der gar nicht benötigt wird, macht es keinen Sinn sich die ca. 190 Namen ins Modul zu kippen. Das importierte `math`-Modul wird ebenfalls nicht benutzt.
Auf Modulebene sollte man nur Konstanten, Funktion, und Klassen definieren. Das Hauptprogramm sollte dort nicht direkt stehen. Denn dann kann man das Modul nicht importieren ohne dass das Hauptprogramm abläuft, und man läuft Gefahr das in Funktionen und Klassen auf Variablen zugegriffen wird die nicht als Argumente übergeben wurden. In dem Zusammenhang: Vergiss ``global``. Das hat in ordentlichen Programmen nichts zu suchen. Wenn man Zustand über Funktionsaufrufe hinweg benötigt, verwendet man objektorientierte Programmierung. Da kommt man bei GUI-Programmierung sowieso nicht sinnvoll drum herum.
In Namen sollten keine konkreten Typen verwendet werden. Wenn man den Typen mal ändert, muss man sonst überall den Namen ändern, oder man hat falsche und damit irreführende Namen im Programm.
Abkürzungen und einbuchstabige Namen sollte man nur verwenden wenn sie allgemein bekannt sind, oder in sehr eng begrenzten Gültigkeitsbereichen wie Lambda-Definitionen, „list comprehensions”, oder Generatorausdrücken. Ansonsten sind ausgeschriebene Namen lesbarer und zwingen den Leser nicht zu rätseln. So etwas wie `i` in einer Schleife für etwas anderes als ganze Zahlen zu verwenden ist ausserdem zusätzlich verwirrend für erfahrene Programmierer.
Namen sollte man auch nicht durchnummerieren. Das ist fast immer ein Zeichen dafür das man keine einzelnen Namen sondern eine Datenstruktur für die Werte verwenden möchte. Meistens eine Liste. Ausser wenn man einfach nur eine 1 an Namen anhängt die für Einzelwerte stehen. Das ist einfach nur sinnfrei. Wobei `str1` in `click()` insgesamt sinnfrei ist, weil der Wert nirgends verwendet wird.
Anstatt Zeilen- und Spaltennummer umständlich zu berechnen, wäre es einfacher die Struktur schon in der Liste mit den Beschriftungen für die Schaltflächen zu kodieren, also eine Liste mit Listen für die Zeilen anzulegen. Dann muss man die entsprechenden Nummern in den Schleifen einfach nur mit `enumerate()` mitzählen lassen.
Mit dem Schlüsselwort ``lambda`` definiert man anonyme Funktionen, also Funktionen die keinen Namen haben. Diese Funktion dann gleich an einen Namen zu binden macht irgendwie keinen Sinn, denn *dafür* ist die ``def``-Anweisung ja schon gedacht.
In `click()` wird viel zu viel gemacht. Da steckt ja die Behandlung von *allen* Tasten drin. Eine Funktion oder Methode sollte aber immer nur eine Aufgabe erledigen.
Man sollte keine ”nackten” ``except``\s verwenden, sondern dort immer angeben welche Ausnahme(n) man erwartet. Insbesondere *jede* Ausnahme einfach durch einen festen Text zu ersetzen ist keine adäquate Ausnahmebehandlung und erschwert die Fehlersuche ungemein. Der Benutzer bekommt hier SYNTAX ERROR angezeigt, auch wenn es gar kein Syntaxfehler war.
Beim Speichern eines Wertes mit '->M' ist das addieren von 2 zum Index an dem das '=' gefunden wurde nicht robust, weil das an der Stelle davon ausgeht das sich nach dem '=' ein unwichtiges Zeichen befindet. Sollte man das Leerzeichen an anderer Stelle im Code mal entfernen, muss man hier daran denken, dass es noch vorhanden ist.
Der Test auf vorhandensein eines '='-Zeichens und *dann* das ermitteln der Position ist auch irgendwo redundant. Man kann auch gleich die Position ermitteln und entsprechend mit der Ausnahme umgehen. Oder man verwendet die `partition()`-Methode und entscheidet nach vorhandensein eines '=' welchen Teil man speichert.
Die Logik vom 'M->'-Zweig erschliesst sich mir nicht wirklich‽ Die Memory-Funktionalität ist auch mit keiner Taste verbunden, dafür gibt es ein 'M-' als Taste für das es keinen Code gibt.
Die feste Vorgabe der Fenstergrösse ist ein Fehler. Bei mir auf dem Laptop werden deshalb nicht alle Tasten angezeigt und nur der Anfang von der Anzeige.
Ich lande dann bei so etwas (ungetestet):
Code: Alles auswählen
try:
import Tkinter as tk
except ImportError:
import tkinter as tk
from functools import partial
class Calculator(object):
def __init__(self, parent):
self.memory = ''
button_texts = [
['7', '8', '9', '*', 'C'],
['4', '5', '6', '/', '<~'],
#
# FIXME: 'M-' is not connected to any action.
#
['1', '2', '3', '-', 'M-'],
['0', '.', '=', '+', '%'],
]
for row_number, text_row in enumerate(button_texts, 1):
for column_number, text in enumerate(text_row):
tk.Button(
parent,
text=text,
width=5,
height=2,
font='bold',
relief=tk.RIDGE,
command=partial(self.click, text)
).grid(row=row_number, column=column_number)
self.entry = tk.Entry(
parent, width=29, bg='white', font='bold', bd=10
)
self.entry.grid(row=0, column=0, columnspan=10)
def click(self, key):
#
# TODO: Break this method up into one method per calculator key (type).
#
if key == '=':
try:
result = ' = {0}'.format(eval(self.entry.get()))
except Exception as error:
result = ' [{0}]'.format(error)
self.entry.insert(tk.END, result)
elif key == 'C':
self.entry.delete(0, tk.END)
elif key == '<~':
pass
elif key == '->M':
pre, equal, post = self.entry.get().partition('=')
self.memory = (post if equal else pre).strip()
elif key == 'M->':
self.entry.insert(tk.END, self.memory)
if '=' in self.entry.get():
self.entry.delete(0, tk.END)
try:
if self.entry.get()[0] == '-':
self.entry.delete(0)
else:
self.entry.insert(0, '-')
except IndexError:
pass
else:
if '=' in self.entry.get():
self.entry.delete(0, tk.END)
self.entry.insert(tk.END, key)
def main():
root = tk.Tk()
root.title('Taschenrechner')
_calculator = Calculator(root)
root.mainloop()
if __name__ == '__main__':
main()
Wobei es unschön/unsauber ist hier die Programmlogik und die GUI in einer Klasse zu vermischen, insbesondere weil ein Teil der Datenhaltung in GUI-Widgets passiert, wo sie nicht hingehört. Und `eval()` ist eine billige aber unschöne Lösung. Tatsächlich die Programmlogik und den Zustandsautomaten für einen Taschenrechner zu implementieren ist allerdings auch gar nicht so einfach wie man auf den ersten Blick vielleicht denkt. Das zum Beispiel auf einen Rechner zu beschränken der die „Reverse Polish Notation” (RPN) implementiert, wäre deutlich einfacher.