Easy Learn - Vokabeltrainer

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

1. Kannst du mir erklären was genau du mit Datenstrucktur meinst?

2. Ich würde es gerne erstmal bei einem Vokabelbearbeitungsprogramm lassen, und jetzt erstmal die GUI implementieren damit ich das ganze endlich verstehe

3. Die Exceptions sind 1. dafür da um das Abstürtzen des Programms zu vermeiden und 2. damit die Funktion die z.B. die delete_file()-Funktion aufruft eine Rückmeldung bekommt und dem Benutzer somit mitteilen kann, dass das Löschen nicht erfolgreich war

4.Ich stelle mir das ganze zur Zeit so vor:
-Ich erstelle Klassen für die Fenster (jedes eine)
-neben der __init__ kommen da nur auf die GUI bezogene Methode rein, z.B. den Inhalt der Listbox durch einen neuen ersetzen (App.replace_listbox(neuer_inhalt))
-den Logischen Teil erledigen Funktionen außerhalb der Klassen die z.B. durch die Buttons aufgerufen werden

Wäre das denn dann richtig?

Wie kommen diese Funktion jetzt an die Instanz der Klasse?

Gruß
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Scriptinggamer hat geschrieben:1. Kannst du mir erklären was genau du mit Datenstrucktur meinst?
*Wie* stellst Du in Python eine "Vokabel" dar? Und wie stellst Du viele Vokabeln dar?

Z.B.: Wenn ich in Lateinisch "gladius" kenne und weiß, dass es auf Deutsch mit "Schwert" übersetzt wird, wie bildest Du das in Python ab? Und nun kommen da eben noch beliebig andere "Wortpaare" hinzu... wie sieht es dann aus?
Scriptinggamer hat geschrieben: 3. Die Exceptions sind 1. dafür da um das Abstürtzen des Programms zu vermeiden und 2. damit die Funktion die z.B. die delete_file()-Funktion aufruft eine Rückmeldung bekommt und dem Benutzer somit mitteilen kann, dass das Löschen nicht erfolgreich war
Und wo liegt da nun der Gewinn, außer, dass Du nun *zusätzlich* eine gesonderte `if...else` Abfrage brauchst, in der Du sogar *weniger* Informationen über den Fehler hast als zuvor? :K

Beispiel

Code: Alles auswählen

# Dein "Ansatz":
def func_where_excpetion_happens():
    try:
        # do soemthing
    except SpecialException:
        return False
    else:
        return True

def caller_func():
    if func_where_exception_happens():
        # keep on working
    else:
        # tell the user something or find workaround

# besserer Ansatz:
def func_where_excpetion_happens():
    # do soemthing

def caller_func():
    try:
        func_where_excpetion_happens()
    except SpecialException:
        # tell the user *what* went wrong or find workaround

# oder auch
def func_where_excpetion_happens():
    try:
        # do soemthing
    except SpecialException:
        # find workaround

def caller_func():
    # nothing can go wrong here :-)
    func_where_excpetion_happens()
konkretes Beispiel:

Code: Alles auswählen

def load(filename):
    """Diese Funktion läd aus einer JSON-Datei ein Dictionary"""
    try:
        with open(filename) as f:
            return json.load(f)
    except IOError:
        # "Workaround": Wenn die Datei nicht geladen werden kann,
        # dann liefern wir eben ein leeres Dictionary und das Programm
        # kann weiterlaufen. (Ist natürlich nur sinnvoll, wenn ein leeres
        # Dictionary weiterverarbeitet werden kann)
        return dict()

def main():
    adressbook = load(sys.argv[1])
    # Und nun mit der Struktur weiterarbeiten:
    show_adresses(adressbook)
    # ... usw.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

zu 1. Ist es denn nicht ok das ich die Listen in der folgenden Form speichere: [[vokabelpaar1, vokabelpaar2, vokabelpaar3, ...], [sprache1, sprache2], Listenname] ?

zu 2. Ich werde also alle axceptions rausnehmen und später in der aufrufenden Funktionen Verwenden

Ich hab ja kein Problem damit ein Funktionierendes Programm zu schreiben, mir gehts nur darum das es auch Übersichtlich nicht vermischt und Sauber ist. Ist den meine 4. - Vorstellung ok? soll ich das erstmal umsetzen?

Gruß
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Scriptinggamer hat geschrieben:zu 1. Ist es denn nicht ok das ich die Listen in der folgenden Form speichere: [[vokabelpaar1, vokabelpaar2, vokabelpaar3, ...], [sprache1, sprache2], Listenname] ?
Bitte noch mal genauer! Python hat doch Literale für Listen... also schreibe doch mal explizit ein Beispiel mit zwei, drei Vokabeln, damit wir das mal endlich *sehen* ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Scriptinggamer: Ich finde die Form nicht okay denn in der äusseren Liste stecken Objekte mit ganz unterschiedlichen Bedeutungen, die anhand eines Indexes, also einer Zahl die überhaupt nichts über die Bedeutung aussagt, angesprochen werden. Das führt zu unnötig schwer verständlichem Quelltext mit „magischen Zahlen”.
deets

Du solltest wie schon wiederholt gesagt vor allem erstmal eines vergessen: GUI. Und alles, was damit zu tun hat.

Und Klassen sind nichts GUI-Spezifisches!!!

Deine Datenstruktur "liste von listen mit noch was hinten dran" ist nicht wirklich gut. BlackJack hat erlauetert warum.

Man koennte da jetzt diverse Dinge machen, aber ich hab mal OO genutzt, und das hier gebaut:

Code: Alles auswählen


import os
from json import load, dump
from random import choice

class Vocabulary(object):


    def __init__(self, learn_lang, known_lang, vocabulary=None):
        self.learn_lang, self.known_lang = learn_lang, known_lang
        # maps lear_lang vocabulary to known_lang answers
        if vocabulary is None:
            vocabulary = {}
        self.vocabulary = vocabulary
        

    def save(self, path):
        name = os.path.join(path, "%s_%s.voc" % (self.learn_lang, self.known_lang))
        with open(name, "wb") as outf:
            dump(
                {
                    "learn_lang" : self.learn_lang,
                    "known_lang" : self.known_lang,
                    "vocabulary" : self.vocabulary,
                    },
                outf
                )
        return name
    

    @classmethod
    def load(cls, filename):
        with open(filename, "rb") as inf:
            d = load(inf)
            return cls(d["learn_lang"], d["known_lang"], d["vocabulary"])
                    
        

    def __contains__(self, vocable):
        return vocable in self.vocabulary


    def add_vocable(self, vocable, answer):
        self.vocabulary[vocable] =  answer
        

    def pick(self):
        return choice(self.vocabulary.keys())


    def check_answer_correct(self, learn_word, answer):
        # hier koennte man in Zukunft vielleicht
        # sogar eine unscharfe ueberpruefung machen,
        # um tippfehler tolerant zu sein und so
        return self.vocabulary.get(learn_word, "") == answer

    

if __name__ == "__main__":
    import tempfile
    import glob
    voc = Vocabulary("Englisch", "Deutsch")
    voc.add_vocable("fun", "spass")
    voc.add_vocable("learn", "lernen")

    td = tempfile.mkdtemp()
    fname = voc.save(td)

    voc = Vocabulary.load(fname)

    assert "fun" in voc
    assert "learn" in voc
    assert voc.check_answer_correct("fun", "spass")
    assert not voc.check_answer_correct("learn", "lesen")
    assert voc.pick() in ["fun", "learn"]
Wie du siehst, habe ich den test-code gleich unten hingeschrieben. Er prueft alle Methoden ab. Und der gesamte Code kommt 100% ohne GUI oder Userinteraktion aus.
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Jetzige Form:
voc_list = [[["Hund", "dog", "Dogge"], ["Katze", "cat", "Kitekat"], ["Ratte", "rat", "ohne Te(e)"]], ["Deutsch", "Englisch"], "Haustierliste"}
Warscheinlich bessere Form:
voc_list = {"vocables" : [["Hund", "dog", "Dogge"], ["Katze", "cat", "Kitekat"], ["Ratte", "rat", "ohne Te(e)"]], "languages" : ["Deutsch", "Englisch"], "name" : "Haustierliste"}

Soll ich das GUI jetzt nach dem geschilderten Plan erstellen?

Gruß
deets

NEIN.

Sondern erstmal deine Funktionen, die auf diesen Daten arbeiten, sie laden, speichern usw. Und zwar *OHNE* globale Variablen zu benutzen!

Die Struktur halte ich immer noch nicht fuer ideal, weil sie immer noch ueber *Indizes* arbeiten. Besser waeren listen von dicts zB.
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

So,
Aber wie soll ich das jetzt mit FILES_PATH machen?

Könntest du mir ein beispiel für deine Listen-Idee geben?
deets

Ich habe dir ein *komplettes* Beispiel fuer laden, speichern, verwalten und abfragen von Vokabeln gegeben. Hast du das nicht gesehen?!?
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Oh, hab ich übersehen, danke :D
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

So, Jetzt ist das Ganze in einer Klasse und in der main() findet ein Testlauf mit abfrage (ein bisschen schlingeliges UI hat sich reingeschmuggelt) statt
Gruß
deets

warum hat sich UI eingeschmuggelt? Tests sind *TESTS*, die sollen so laufen. Ohne Interaktion. Das ist wesentlich wichtig, als da irgendwelchen User-Input einzubauen. Tests schreiben zu lernen ist deutlich wichtiger als das Beherrschen von raw_input & Co.

Und deine Datenstruktur ist immer noch murks, denn dieser rumgeindize ist unverstaendlich. Wenn du Sprachen tauschen willst, dann benutz die Sprach-Namen als Keys fuer ein Dict in den Vokablen. Dann kannst du darauf entsprechend zugreifen.

Warum hast du pickle statt json verwendet? Ein klar lesbares und sogar von Menschen schreibbares Format vs. einem opaken binaeren Blob? Gibt's dafuer einen wirklichen *Grund*?

Last but not least: FILE_PATH nicht gross, und *warum* uebrehaupt? Warum kannst du das nicht als Parameter reinreichen? UNd warum sind deine ganzen Parameter auf sinnlose default-werte ("") gestellt?
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

1. UI weil ich keinen Nutzer simulieren möchte der richtige und Falsche eingaben macht, da ist input(vokabelname) schneller geschrieben
2. Soll ich also in die Vokabelliste lauter Dictionarys mit folgendem aufbau stecken?: {"vocable1" : "Hund", "vocable2" : "dog", "crib" : "Dogge", "number" : 2}
3. Ich kenne json garnicht, in den Tutorials die ich mir angeguckt hab gabs immer nur pickle, aber wenn du sagts das es besser ist, dann ersetze ich pickle eben durch json
4. Irgendwer meinte, ich solle das in ne Variable packen damit ich es schnell ändern kann, und weil sie kontstant ist ,dachte ich mir, schreib ich sie groß...
5. Wenn ich die Parameter auf NONE stellen würde könnte es zu Fehlern kommen, so bekommt man wenn man drauf zugreift einen leeren String, und das die überhaupt auf default werte haben hat folgenden Grund: Wenn ich eine Neue Vokabelliste machen will, kann ich Name, Sprache1 und Sprache2 mitgeben. Wenn ich aber eine Vorhandene Liste öffnen will, brauche ich nur eine Instanz zu erstellen und die load Funktion ausführen.

Code: Alles auswählen

#Neue Liste:
vocabulary = Vocabulary("Englisch - Lektion 1", "Deutsch", "Englisch")
#Vorhandene Liste:
vocabulary = Vocabulary()
vocabulary.load("Haustierliste.el")
Gruß
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@1.) Nur weil Du etwas nicht willst, ist es noch lange nicht sinnvoll ;-)

@2.) Du kannst machen, wozu immer Du auch Lust hast. Aber Du solltest darüber nachdenken, ob und ggf. *wieso* eine Datenstruktur für Dich geeignet ist. Und das erkennt man eben schnell, wenn man die grundlegenden Funktionen für den Zugriff baut, denn dann merkst Du ja selber, wie einfach oder kompliziert oder gar anfällig Deine API ist.

Ich würde einzelne Vokabeln als Dictionary mit dem gegebenen Wort als Schlüssel und einer Liste von Übersetzungen als Value abbilden:

Code: Alles auswählen

vocabularies = {
    "eat": ["essen", "fressen"],
    "run": ["rennen", "laufen"],
    ...
}
Der umgekehrte Weg bedarf letztlich eines eigenen Dictionaries - Sprachen sind ja per se nicht symmetrisch bezüglich der Abbildung der Wörter ;-)

Vielleicht gibt es aber auch noch eine bessere Darstellung der Wortbeziehungen allgemein, die man ggf. für das Lernen dann in meine vorgeschlagene Form bringen kann? Auch so etwas kann man immer überlegen! Für meinen "Wer wird Millionär"-Clone habe ich die richtige Antwort auf eine Frage per Konvention und implizit in meiner persistenten Datenstruktur und weise diese erst zur Laufzeit explizit zu. Auch solche Sachen kann und sollte man sich fürhzeitig überlegen.

@3.) JSON ist nicht per se besser, sondern für viele Zwecke eben sinnvoller. Und das Eingeben / Korrigieren von Vokablen per Texteditor ist ja nun nicht so unwahrscheinlich ;-) Da greifst Du eben besser zu einem Format, welches im Klartext vorliegt.

@4.) "Irgend wer" hatte grundsätzlich natürlich recht. Sogenannte "Magic Numbers" sollte man tunlichst vermeiden - dafür kann man gerne auch mal eine globale "Konstante" einführen. In Deinem Falle bleibt die Frage, *wieso* das eine Konstante sein soll und ob es nicht *sinnvoller* ist, den Pfad als normalen Parameter zu übergeben! Zumal Du das ganze als Exemplarvariable definierst und nicht einmal als Klassenvariable. Dazu aber noch mehr beim nächsten Punkt!

@5.) Ich frage mich, ob es überhaupt so eine gute Idee ist, die Persistenz wirklich in die Vokabelklasse zu packen. Wieso kann es da nicht eine externe Funktion geben, die einem ein fertiges Exemplar der Klasse zurück liefert? Und leere Strings als default-Werte sind da einfach Unsinn. Zumal Du mit der externen Lösung so eine `init`-Funktion nicht mehr brauchst.

Ganz generell noch eine Anmerkung: Du kannst und darfst *alles* umsetzen, wie Du willst! Du musst uns nicht um Erlaubnis fragen oder gar um "Segen" bitten ;-) Wir geben einfach nur unsere Ansicht wieder - je nach User steckt dahinter aber eben eine Menge Erfahrung und Kompetenz und alleine deswegen solltest Du solche Hinweise befolgen oder wenigstens intensiv darüber nachdenken. Aber wenn Du eben gerne sofort mit GUI loslegen willst, dann darfst Du das natürlich machen :-) Du musst nur damit rechnen, dass wir Dir dann wieder vieles "um die Ohren" hauen oder schließlich keine ausführlichen Reviews mehr schreiben, wenn man keinen Lerneffekt erkennt.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Scriptinggamer: Ad 1.: Tests sind dazu da dass man sie ohne Benutzerinteraktion automatisiert ausführen kann. Die sollte man einfach mal schnell ausführen können ohne dass man sich damit weiter auseinander setzen muss — wenn sie fehlerfrei durchgelaufen sind. Die kann man zum Beispiel immer aufrufen bevor man ein commit macht, um zu sehen ob die letzten Änderungen etwas am bisherigen Code kaputt gemacht haben. Wenn man dann jedes mal von Hand irgend welche zusätzlichen Eingaben machen muss, dann lässt man das testen sehr schnell bleiben weil es zu viele Umstände macht.

Ad 5.: `Vocabulary.load()` sollte dann ganz einfach keine Methode sein die man auf einem Exemplar aufruft, sondern eine `classmethod()` die man auf der Klasse aufruft und die ein `Vocabulary`-Exemplar als Rückgabewert hat.

Abfragen in beide Richtungen funktioniert IMHO nicht wirklich gut weil die beiden Seiten nicht eindeutig sein müssen. Nehmen wir mal die Liste [('Ordnung', 'order'), ('Bestellung', 'order')] und fragen nach der zweiten Sprache — was ist jetzt die korrekte Antwort auf 'order'?
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

1. Leuchtet ein, alles klar
2. Ich habe es jetzt nochmal verändert, ich finde dann ist die Datenstrucktur inordnung (So habe ich ein Objekt (dict) für jede Vokabel, mit vokabel1, 2 und Eselsbrücke) auf die ich zugreifen kann
3. Ist ja cool, so hatte ich mir das eigentlich vorgestellt mit dem Speichern von Daten...
4. Da ich es sowieso nur zwei mal nutze benutze ich halt keine Variable mehr für den Pfad
5. Wie genau sollte das dann aussehen mit dem speichern und laden?

Mir ist natürlich klar das ich machen kann was ich möchte, aber die meisten hier, vorallem die Leute die antworten, haben einfach schon viel mehr erfahrung als ich, deshalb ist es sinnvoll viel zu fragen...

Jetziges Script

Gruß
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Hi,
Was man hier nicht alles lernt, ich kannte classmethod vorher garnicht, nur von datetime.datetime.now(), aber da hab ich nicht weiter drüber nachgedacht...
Ist das denn mit der classmethod SO richtig? Gibts noch weitere Verbesserungsvorschläge?
BlackJack

@Scriptinggamer: Ja so kann man das mit einer `classmethod()` machen.

Das Abschneiden der Dateinamensendung mit ``[:-3]`` ist nicht besonders robust, weil der übergebene Dateiname auch gar keine oder eine beliebig lange Endung haben könnte. Ich würde da `os.path.splitext()` empfehlen.

Defaultwerte werden nur *einmal* ausgewertet wenn das ``def`` ausgeführt wird. Die Liste für `vocables` bei der `__init__()` gilt also für *alle* Exemplare von `Vocabulary` — was sicher nicht das Verhalten ist was gewünscht wird. Bei veränderbaren Defaultwerten sollte man also besser `None` angeben und das in der Methode dann auswerten und gegebenenfalls dort für jeden Aufruf eine neue leere Liste erstellen.

Der Name `dump_list` in `save()` wird an ein Wörterbuch gebunden. Das ist irreführend. Und ein nettes Beispiel warum man höchstens „duck types” und keine konkreten Typen als Prä- oder Suffix für Namen verwenden sollte.

Bei `add_vocable()` hätte ich erwartet das man da auch tatsächlich eine Vokabel mit konkreten Werten hinzufügen kann und nicht nur eine Platzhalterstruktur angelegt wird, die man dann extra noch füllen muss.

Man könnte auch überlegen ein paar sinnvolle „magische” Methoden zu implementieren wie `__len__()` und `__iter__()`.

`compare()` ist semantisch keine Methode. Da sollte man entweder eine Funktion draus machen, oder mit `staticmethod()` kennzeichnen, dass es keine Methode ist.

Die verschiedenen Rückgabewerte sind unschön. Und einen Teil der möglichen Ergebnisse über den Rückgabewert und einen anderen über Ausnahmen zu lösen, wäre IMHO auch unschön. Warum dort die leere Antwort als etwas anderes als einfach Falsch gewertet wird, erschliesst sich mir nicht so ganz‽ Und bei nicht ganz korrekten Antworten würde ich eher statt `True` und `False` eine Gleitkommazahl zwischen 0 und 1 als Ergebnis liefern, die irgendwie abbildet wie stark das Ergebnis abweicht. Zum Beispiel die Levenshtein-Distanz ins Verhältnis zur Länge der Eingabe(n) setzen oder so.

Warum gibt es `get_ask_list()` und `update_ask_list()`? Die machen doch sehr ähnliche Sachen. Beide haben das Problem dass der Datentyp `list` im Namen steht. Und `update_ask_list()` aktualisiert nichts — der Name ist also irreführend.

Das ``del vocabulary`` in `test_script()` ist überflüssig.
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Neue Version hier.

1. Alles was ich nicht erwähne habe ich bis auf weiteres bedingungslos akzeptiert ;)
2. Kannst du mir nochmal genau erklären warum None statt []?
3. Habe alle Typen aus den Bezeichnern genommen
4. Da ich das ganze mit GUI plane (hier die vermurkste alte GUI-Version) und man da erstmal auf einen "Neue Vokabel"-Button klickt, die leeren Felder der Listbox erscheinen, man in Entrys die Daten einträgt und auf "Eintragen" klickt, ist es nicht nötig eine neue Vokabel dierekt hinzuzufügen.
5. compare() heißt jetzt check_answer() und ist, wie auch replace_ask_vocables() (ehemaliges update_ask_list()), eine unabhängige Funktion

Vielen Dank
Gruß
Antworten