Iiih `dataclasses`. Damit fängt man sich Typannotationen ein. Und die dann nur so halbherzig (Callable) machen ist zusätzlich unschön. Mit `attrs` kann man die Typannotationen weglassen.
Der angesprochene Test ist ja eigentlich nur nötig weil eine eigentlich recht generische *Funktion* in eine Klasse gesteckt wurde und zusätzlich etwas über Menüpunkte wissen muss.
Das `berechne()` aus `zeige_ergebnis()` aufgerufen wird ist IMHO falsch. Damit macht `zeige_ergebnis()` mehr als der Name vermuten lässt, und im Hauptprogramm steht dann nur noch die Aufrufabfolge `erfrage_werte()` und `zeige_ergebnis()`, wo man ja eigentlich dazwischen erwarten würde das eine Berechnung stattfinden würde. Letztlich würde ich die `zeige_ergebnis()` „inlinen“, weil man sonst die Trennung zwischen Programmlogik und Benutzerinteraktion komplizierter machen müsste. Was ja wahrscheinlich der Grund ist warum Du die Aufrufe so unerwartet verschachtelt hast.
`text_als_zahl()` und `zahl_als_text()` sind keine Namen die Tätigkeiten beschreiben.
Bei `erfrage_ende()` ist das schräg das der Prompt nicht übergeben wird (womit die Funktion allgemeiner wird) aber die Werte für `ja` und `nein`. Das macht ja am meisten Sinn wenn man mal die Sprache der Kommunikation mit dem Benutzer ändern möchte, dann muss man aber in dieser Funktion den Prompt übersetzen, und an ganz anderer Stelle im Programm die Werte für Ja und Nein. Diese Werte sind ansonsten ja auch nicht wirklich variabel. Ich würde hier entweder die Frage(-Vorlage) auch als Argument nehmen, oder die Antwortmöglichkeiten in der Funktion festklopfen.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
from decimal import Decimal, DivisionByZero, InvalidOperation
from operator import add, mul, sub, truediv
from attrs import frozen, field
EINGABEFEHLER = "FEHLER: Ungültige Eingabe"
def erzeuge_ueberschrift(text):
if "\n" in text:
raise ValueError(f"line ending within {text!r}")
return f"\n{text.upper()}\n{'=' * len(text)}"
@frozen
class Menuepunkt:
text = field()
operation = field()
def als_ueberschrift(self):
return erzeuge_ueberschrift(self.text)
@frozen
class Menue:
menuepunkte = field()
def __str__(self):
return "\n".join(
[
erzeuge_ueberschrift("Hauptmenü"),
*(
f"({nummer}) {menuepunkt.text}"
for nummer, menuepunkt in enumerate(self.menuepunkte, 1)
),
]
)
def erfrage_auswahl(self):
while True:
index = erfrage_wert("Ihre Auswahl: ", int) - 1
if 0 <= index < len(self.menuepunkte):
return self.menuepunkte[index]
print("FEHLER: Menüpunkt nicht vorhanden")
@classmethod
def aus_modell(cls, modell):
return cls([Menuepunkt(text, operation) for text, operation in modell])
def parse_zahl(text):
try:
return Decimal(text.replace(",", "."))
except InvalidOperation:
raise ValueError(text) from None
def formatiere_zahl(zahl):
return str(zahl).replace(".", ",")
def erfrage_wert(abfrage, konverter=str):
while True:
try:
return konverter(input(abfrage))
except ValueError:
print(EINGABEFEHLER)
def erfrage_zahlen():
return [
erfrage_wert(
f"Geben Sie die {ordnungszahlwort} Zahl ein: ", parse_zahl
)
for ordnungszahlwort in ["erste", "zweite"]
]
def berechne(erster_operand, zweiter_operand, operation):
ergebnis = operation(erster_operand, zweiter_operand)
ganzzahl = int(ergebnis)
if ergebnis == ganzzahl:
return ganzzahl
return ergebnis
def erfrage_ende(fragevorlage, ja, nein):
frage = fragevorlage.format(ja, nein)
antworten = {ja.casefold(), nein.casefold()}
while True:
antwort = erfrage_wert(frage).casefold()
if antwort in antworten:
return antwort == ja.casefold()
print(EINGABEFEHLER)
def main():
menue = Menue.aus_modell(
[
("Addition", add),
("Subtraktion", sub),
("Multiplikation", mul),
("Division", truediv),
]
)
while True:
print(menue)
menuepunkt = menue.erfrage_auswahl()
print(menuepunkt.als_ueberschrift())
erster_operand, zweiter_operand = erfrage_zahlen()
try:
ergebnis = berechne(
erster_operand, zweiter_operand, menuepunkt.operation
)
except (DivisionByZero, ZeroDivisionError):
print("FEHLER: Division durch Null")
else:
print("Das Ergebnis ist:", formatiere_zahl(ergebnis))
if erfrage_ende("Möchten Sie das Programm beenden ({}/{})?", "j", "n"):
break
if __name__ == "__main__":
try:
main()
except (KeyboardInterrupt, EOFError):
pass