@ctex: Ein paar Anmerkungen zu dem Quelltext mit GUI:
`time` wird importiert, aber nicht verwendet.
`DbMd` wird definiert, aber nirgends verwendet. Das hat auch einen *sehr* schlechten Namen. Ich könnte nicht einmal sinnvoll *raten* was das wohl bedeuten mag. Abkürzungen in Namen sollte man vermeiden, solange sie nicht allgemein bekannt sind.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Du verdeckst mit dem Namen `Hauptfenster` im Hauptprogramm den Namen der Klasse `Hauptfenster`.
Das Argument `mwort` wird in `ueberpruefen()` nicht verwendet.
Die ``while``-Schleife in der Funktion macht keinen Sinn weil die wegen de, ``break`` am Ende höchstens einmal durchlaufen wird.
Die Zeichenkette 'Wandernder Unsinn' kommt mehrfach vor und sollte deshalb als Konstante definiert werden.
Explizites Vergleichen mit literalen `True`- oder `False`-Werten ist schlechter Stil weil da nur wieder ein Wahrheitswert bei heraus kommt, den man ja ohne den Vergleich schon hatte. Entweder will man den gleich verwenden, oder dessen Negation mit ``not``.
Es gibt drei ``if``\s bei denen `istAdj` Teil der Bedingung ist. Das kann man zusammenfassen.
Falls nur das letzte 'e' entfernt werden soll, ist `rstrip()` die falsche Methode. Falls alle 'e' am Ende entfernt werden sollen ist `rstrip()` richtig, aber die `if`-Abfrage vorher überflüssig.
Die Veränderung der Eingabe falls der Name `Moritz` und Schimpfwörter vorkommen ist fehlerhaft. Zum einen gibt `find()` keinen Wahrheitswert zurück der angibt ob das Wort enthalten ist oder nicht. Im Gegenteil, das gibt immer einen wahren Wert *ausser* wenn das zu suchende Wort an erster Stelle steht. Wenn man den Test mit dem ``in``-Operator ausdrückt, kommt das nächste Problem: ``and`` bindet stärker als ``or``, die Bedingung sagt also nicht das aus was Du anscheinend haben möchtest, ohne das Du Klammern setzt. Ausserdem kann man das mit `any()` und einem Generatorausdruck etwas schöner/lesbarer/kompakter schreiben.
Da mit dem Begriff 'Arschloch' dann doch nichts gemacht wird, kann man den auch weglassen.
`verarbeiten()` kann man kürzer ausdrücken.
Widgets layouten sich in ihrer `__init__()` nicht selbst. Das macht keines der vorhandenen Widgets, denn damit nimmt man dem Aufrufer die Entscheidung was genau er mit dem Widget machen will.
Bei der Frage nach der Dateiausgabe ist eine unschöne Code-Wiederholung. Da würde man eher eine ”Endlosschleife” schreiben, die durch ein ``break`` verlassen wird. Ausserdem würde ich das 'yes'/'no' zu einem Wahrheitswert machen.
`self.eingabe` wird nirgends verwendet.
Attribute sollten nach der `__init__()` alle existieren, damit man leicht sieht welche Attribute ein Objekt hat, und damit nicht die Reihenfolge der Methodenaufrufe bestimmt welche Attribute es gibt und welche nicht.
Statt die ganzen Einzelteile vom Benutzer an einzelne Attribute zu binden und dann hinterher in eine Liste zu stecken, könnte man die auch gleich in einer Liste sammeln.
Um ein Textfeld komplett zu leeren nimmt man nicht irgendeine grosse Zahl als Ende sondern das tatsächliche Ende: `tkinter.END`.
Für das was Du mit `eAlles` veranstaltest gibt es eine Funktion irgendwo unter `urllib`.
Weder `eAlles` noch `datei` gehören als Attribute an das Objekt.
Bei den Dateien würde ich eine Kodierung angeben.
Es kann passieren das weder im ``try`` noch im ``except``-Block der Name `datei` gebunden wird. Dann führt das ``datei.close()`` im ``finally``-Block zwangsläufig zu einem `NameError`.
`sys.exit()` ist eine einschneidende Funktion. Die sollte man nicht irgendwo im Programm aufrufen, das nimmt anderem Code die Möglichkeit noch irgend etwas zu tun. Es reicht hier völlig aus die GUI-Hauptschleife mit der `quit()`-Methode zu verlassen.
Ich lande dann als Zwischenergebnis ungefähr hier:
Code: Alles auswählen
# coding: utf8
import sys
import webbrowser
import tkinter
import tkinter.messagebox
import tkinter.filedialog
from urllib.parse import quote
PROGRAMM_TITEL = 'Wandernder Unsinn'
def ueberpruefen(eingabe, ist_adjektiv=False):
#
# TODO Die API ist schlecht — die Rückgabewerte sollten nur einen
# (Duck)Typ haben, und nicht mal `str` und mal `bool` sein.
# Für Fehler gibt es Ausnahmen!
#
# Ausserdem sollte die Benutzerinteraktion hier nicht drin sein.
#
# Und der Name `ueberpruefen()` passt auch nicht, die Funktion
# tut mehr als nur etwas zu überprüfen.
#
if eingabe.stript() == '':
tkinter.messagebox.showerror(PROGRAMM_TITEL, 'Bitte gebe etwas ein!')
return False
if ist_adjektiv:
if eingabe.endswith('e'):
eingabe = eingabe[:-1]
if ' ' in eingabe:
tkinter.messagebox.showerror(
PROGRAMM_TITEL, 'Du darfst nur ein Wort eingeben!'
)
return False
eingabe = eingabe.lower()
bad_words = ['dumm', 'doof']
if 'Moritz' in eingabe and any(w in eingabe for w in bad_words):
for bad_word in bad_words:
eingabe = eingabe.replace(bad_word, 'superschlau')
return eingabe.strip(' "-:').lstrip('.?!')
def verarbeiten(werte):
return (
'Der {}e {} traf die {}e {} {} und sagte: "{}" und sie antwortete:'
' "{}" Und deshalb {}.'.format(*werte)
)
class Hauptfenster(tkinter.Frame):
def __init__(self, master=None):
tkinter.Frame.__init__(self, master)
self.dateipfad = None
while True:
self.dateiausgabe = tkinter.messagebox.askquestion(
PROGRAMM_TITEL, 'Datei-Ausgabe?'
) == 'yes'
if not self.dateiausgabe:
break
self.dateipfad = tkinter.filedialog.asksaveasfilename(
initialfile='Wandernder Unsinn',
title='Datei-Pfad zur Ausgabe',
defaultextension='.txt',
filetypes={'Text-Datei {.txt}' : '.txt'},
)
if self.dateipfad != '':
break
self.anzeige_label = tkinter.Label(self)
self.anzeige_label.pack(expand=True)
self.eingabe_entry = tkinter.Entry(self)
self.eingabe_entry.pack(expand=True)
self.fertig_button = tkinter.Button(self, command=self.on_fertig)
self.fertig_button.pack(fill=tkinter.BOTH, pady=20)
self.fertig_button.focus_set()
self.schritt = None
self.restart()
def restart(self):
self.eingaben = list()
self.schritt = 'Mann_Wie'
self.anzeige_label['text'] = "Der <Adjektiv>e ..."
self.fertig_button['text'] = "Okay"
def on_fertig(self):
if self.schritt == 'Mann_Wie':
eingabe = ueberpruefen(self.eingabe_entry.get(), ist_adjektiv=True)
if eingabe:
self.eingaben.append(eingabe)
self.schritt = 'Mann_Wer'
self.anzeige_label['text'] = '... <Name von Mann> traf die ...'
self.eingabe_entry.delete(0, tkinter.END)
elif self.schritt == 'Mann_Wer':
eingabe = ueberpruefen(self.eingabe_entry.get())
if eingabe:
self.eingaben.append(eingabe)
self.schritt = 'Frau_Wie'
self.anzeige_label['text'] = '... die <Adjektiv>e ...'
self.eingabe_entry.delete(0, tkinter.END)
elif self.schritt == 'Frau_Wie':
eingabe = ueberpruefen(self.eingabe_entry.get(), ist_adjektiv=True)
if eingabe:
self.eingaben.append(eingabe)
self.schritt = 'Frau_Wer'
self.anzeige_label['text'] = '... <Name von Frau> ...'
self.eingabe_entry.delete(0, tkinter.END)
elif self.schritt == 'Frau_Wer':
eingabe = ueberpruefen(self.eingabe_entry.get())
if eingabe:
self.eingaben.append(eingabe)
self.schritt = 'Wo'
self.anzeige_label['text'] = '... <Wo> und sagte: ...'
self.eingabe_entry.delete(0, tkinter.END)
elif self.schritt == 'Wo':
eingabe = ueberpruefen(self.eingabe_entry.get())
if eingabe:
self.eingaben.append(eingabe)
self.schritt = 'Satz'
self.anzeige_label['text'] = (
'... und sagte: "<Satz>" und sie antwortete: ...'
)
self.eingabe_entry.delete(0, tkinter.END)
elif self.schritt == 'Satz':
eingabe = ueberpruefen(self.eingabe_entry.get())
if eingabe:
self.eingaben.append(eingabe)
self.schritt = 'Antwort'
self.anzeige_label['text'] = (
'... und sie antwortete: "<Antwort>" )und deshalb ...'
)
self.eingabe_entry.delete(0, tkinter.END)
elif self.schritt == 'Antwort':
eingabe = ueberpruefen(self.eingabe_entry.get())
if eingabe:
self.eingaben.append(eingabe)
self.schritt = 'Deshalb'
self.fertig_button['text'] = 'Abschließen'
self.anzeige_label['text'] = '... <Deshalb>.'
self.eingabe_entry.delete(0, tkinter.END)
elif self.schritt == 'Deshalb':
eingabe = ueberpruefen(self.eingabe_entry.get())
if eingabe:
self.eingaben.append(eingabe)
self.eingabe_entry.delete(0, tkinter.END)
ergebnis = verarbeiten(self.eingaben)
if tkinter.messagebox.askquestion(
PROGRAMM_TITEL,
'SPIEL ENDE!\n'
'{}\n\n'
'--------------------\n'
'Willst du das Ergebnis per E-Mail versenden?\n'.format(
ergebnis
)
) == 'yes':
webbrowser.open(
quote(
'mailto:?subject=Lustige Geschichte ERSTELLT MIT'
' Wandernder Unsinn - VON MORITZ RUTH'
'&body=Lustig:\x0a\x0a' + ergebnis
)
)
if self.dateiausgabe:
datei = None
try:
datei = open(self.dateipfad, 'x', encoding='utf8')
datei.write('------------ERSTE RUNDE------------\n')
datei.write(ergebnis)
except FileExistsError:
datei = open(self.dateipfad, 'a', encoding='utf8')
datei.write(
'\n\n\n------------NEUE RUNDE------------\n'
)
datei.write(ergebnis)
finally:
if datei is not None:
datei.close()
if tkinter.messagebox.askquestion(
PROGRAMM_TITEL, 'Neue Runde?'
) == 'yes':
self.restart()
else:
self.quit()
else:
assert False, 'Unbekannter Schrittwert {0!r}'.format(self.schritt)
def main():
root = tkinter.Tk()
hauptfenster = Hauptfenster(root)
hauptfenster.pack(padx=40, pady=50, expand=False)
root.mainloop()
if __name__ == '__main__':
main()
Ich habe bei `ueberpruefen()` das Gefühl, das da eigentlich zwei verschiedene Funktionen in einer stecken und das man das trennen sollte.
Der Satz mit den Platzhaltern und die einzelnen Werte sind viel zu stark über das gesamte Programm verteilt und vor allem hart als Code modelliert, statt als Datenstruktur. Wenn man da etwas ändern möchte, muss man das gesamte Programm durchgehen. Wenn man die Vorlage für eine andere ”Geschichte” verändern möchte, muss man sogar viel Code neu schreiben.
Die einzelnen Zweige in der `on_fertig()` machen aber fast alle das gleiche. Spricht wieder dafür da Daten in einer Struktur heraus zu ziehen und Code zu schreiben der das dann generisch abarbeitet.
Die `on_fertig()` ist auch viel zu lang. Die ganzen Zweige könnte man auf eigene Methoden aufteilen und dann kann man diese Methoden direkt als `schritt` verwenden und spart sich die Indirektion über diese unschönen Zeichenketten.
Das versenden einer E-Mail würde ich in eine eigene Funktion auslagern. Insgesamt wäre eine Trennung von GUI und Geschäftslogik keine schlechte Idee.
Von der Benutzung her sind diese Fragen ohne dass man das Gesamtbild sieht, IMHO auch nicht so schön. Da könnte man mit einem `Text`-Widget und Eingabefeldern *dort* drin, sicher eine bedienerfreundlichere Oberfläche für den Benutzer erstellen.