"Notizen Programm" - Feedback

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

Hallöchen allerseits,

hab ein kleines Programm und würde gerne wissen, ob ich Bugs übersehen habe, was es stilistisch vielleicht zu beachten gäbe, und vorallem, wo ich ErrorCheck mäßig noch was verbessern kann.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Simple program to store some notes

FILENAME = "notes.txt"  # must be in working directory

# make sure file exists
try:
    f = open(FILENAME, "r")
    f.close()
except FileNotFoundError:
    f = open(FILENAME, "w")
    f.close()
        
def print_notes():
    with open(FILENAME, "r") as file:
        print("Notes so far:")
        while True:
            line = file.readline()
            if len(line) == 0:
                break
            print(line, end="")
    
def write_note(note):
    with open(FILENAME, "a") as file:
        if not note.isspace():
            file.write(note + "\n")
    
def delete_notes():
    with open(FILENAME, "w") as file:
        print("Notes successfully deleted.")

def main():
    print("=== Note Manager ===")
    print("'show': print all" \
          ", 'del': delete all, 'done': quit.\n\n")
    
    while True:
        note = input("Enter note or command: ")
        if note == "done":
            break
        elif note == "show":
            print_notes()
        elif note == "delete":
            delete_notes()
        else:
            write_note(note)
        
if __name__ == "__main__":
    main()

ich weiß, das Programm ist eher langweilig, aber würde mich sehr darüber freuen was dazu zu lernen ;)

LG
HarteWare
BlackJack

@HarteWare: Das reine importieren eines Moduls sollte keine Seiteneffekte haben. Insbesondere nicht so etwas drastisches wie das anlegen einer Datei im aktuellen Arbeitsverzeichnis.

Gibt es einen `FileNotFoundError`?

Code: Alles auswählen

In [6]: FileNotFoundError
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/home/bj/<ipython-input-6-32843a8d74dd> in <module>()
----> 1 FileNotFoundError

NameError: name 'FileNotFoundError' is not defined
Also „ErrorCheck mäßig” könntest Du Deine Fehlerbehandlung mal testen. :-)

Grundsätzlich hilft das am Anfang sowieso nicht viel, denn selbst wenn die Datei dadurch angelegt wurde kann es ja sein das sie vom Benutzer ausserhalb des Programms wieder gelöscht wird, also sollten die Funktionen die sich auf die Existenz verlassen sich eben nicht auf die Existenz verlassen sondern sauber damit umgehen können wenn die Datei nicht existiert.

Die ``while``-Schleife in `print_notes()` ist umständlich. Man kann mit ``for`` direkt über die Zeilen einer Datei iterieren. Und mit der `writelines()`-Methode auf Dateien könnte man die auch gleich die ``for``-Schleife noch einsparen und direkt in `sys.stdout` schreiben.

Code: Alles auswählen

def print_notes():
    print('Notes so far:')
    with open(FILENAME, 'r') as lines:
        sys.stdout.writelines(lines)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich würde ja eine Datenstruktur um die Notizen bauen, so dass man diese um Speicher durchsuchen, ändern und ggf. auch nur bestimmte löschen kann - typisches CRUD eben!

Und zu Deinem Hauptmenü sage ich auch nur *Datenstrukturen* ;-) Link
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

Hallo,

erstma vielen Dank für eure Antworten ! :)
Das reine importieren eines Moduls sollte keine Seiteneffekte haben. Insbesondere nicht so etwas drastisches wie das anlegen einer Datei im aktuellen Arbeitsverzeichnis.
@Blackjack, deinen ersten Satz mit dem Modul verstehe ich nicht ganz. Soll das heißen, für meine Aufgaben gibt es ein gutes Modul und ich sollte es nutzen?

Wegen dem error... also ich hab Python 3.3 x32. Öffne mal den interaktiven interpreter und gib ein

Code: Alles auswählen

f = open("qwertz.txt", "r")
dann sollte besagter error kommen ;)

Die Frage ist halt, ob ich einfach still und leise die Datei anlegen soll, falls sie gelöscht wurde, oder noch nicht existiert. Auch wenn dies wohl höchst unwahrscheinlich sein wird, ein potenzieller Nutzer meines "Programms" weiß ja vielleicht nicht, dass er extra noch ne Datei anlegen muss, geschweige denn, wie sie heißen soll. Oder meintest du nur, es gibt bessere Wege ne Datei in die "working directory" zu machen?

Ich bekomme bei der verbesserten print_notes() Funktion einen Fehler, wenn die Datei leer ist. Lässt sich das geschickt irgendwie umgehen? Wie kann ich bei einer Datei sehen, ob sie leer ist? Abgesehen davon schonmal Danke, ist in der Tat viel schöner die Lösung

Code: Alles auswählen

Traceback (most recent call last):
  File "D:\Python\Scripts\Notes\Notes.py", line 49, in <module>
    main()
  File "D:\Python\Scripts\Notes\Notes.py", line 42, in main
    print_notes()
  File "D:\Python\Scripts\Notes\Notes.py", line 21, in print_notes
    sys.stdout.writelines(file)
AttributeError: closed
@Hyperion
In der Tat, das Programm ist noch sehr erweiterbar, hatte mir überlegt da nen Kalender oder so draus zu machen mit datetime etc., aber man fängt ja klein an :p
Und danke wegen dem Menü Tipp, jetzt weiß ich, wie man das gescheit lösen kann, statt mit irgendwelchen "elif, elif, elif, ..." :D
Create Read Update Delete, sagt ein kurzer Blick in Wikipedia. Dann müsst ich halt die Notizen mit irgendner Nummer kennzeichnen oder so, oder wie?

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

HarteWare hat geschrieben: Create Read Update Delete, sagt ein kurzer Blick in Wikipedia. Dann müsst ich halt die Notizen mit irgendner Nummer kennzeichnen oder so, oder wie?
Naja, Du müsstest die vor allem in eine Datenstruktur packen - im Moment gibst Du sie ja nur aus ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

Also zum Beispiel beim Start des Programms die Datei in ne Liste Laden und beim Beenden die Liste wieder schreiben, und zwischen drin nur auf der Liste arbeiten? Dann wär auch gleich das Problem mit dem Attribute Error da in print_notes() umgangen...
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

HarteWare hat geschrieben:
BlackJack hat geschrieben: Das reine importieren eines Moduls sollte keine Seiteneffekte haben. Insbesondere nicht so etwas drastisches wie das anlegen einer Datei im aktuellen Arbeitsverzeichnis.
@Blackjack, deinen ersten Satz mit dem Modul verstehe ich nicht ganz. Soll das heißen, für meine Aufgaben gibt es ein gutes Modul und ich sollte es nutzen?
Nein, BlackJack meint damit, dass es keine Seiteneffekte geben darf, wenn du dein Modul importierst. Wenn die Datei notizen.py heißt und du in der Python-Shell "import notizen" schreibst, darf das also nicht dazu führen, dass Dateien angelegt werden o.ä.
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

Aaaaachso, alles klar, jetzt machts Sinn! Danke

LG
BlackJack

@HarteWare: Also in Python 3.2 gibt es diese Ausnahme jedenfalls noch nicht. Und das mit dem `writelines()` sollte eigentlich funktionieren. Wüsste nicht warum sie das in Python 3 kaputt gemacht haben sollten.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: Mit Python 3.3 gibt's viele neue Exceptions: PEP-3151
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

HarteWare hat geschrieben:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
Hallo,

zwei Sachen:

1. Wenn du Python 3 nimmst, lautet der Shebang #!/usr/bin/env python3. Ich gehe davon aus, dass du Windows hast und dort nur Python 3 installiert hast. Deswegen funktioniert das auch bei dir. Eigentlich ist python aber der Shebang für Python 2.
Es ist bei Windows aber möglich, Python 2 und Python 3 gleichzeitig zu installieren. Man muss das manchmal machen, weil einige Programme leider immer noch nur auf Python 2 basieren. Unter Windows gibt es dann seit Python 3.3 ein kleines Programm, py.exe, welches standardmäßig für das Öffnen von .py-Dateien zuständig ist. Das schaut dann im Shebang nach, ob dort python oder python3 eingetragen und leitet das dann an den entsprechenden Interpreter weiter.
Wie gesagt, python ist für Python 2 und python3 für Python 3. Ich persönlich schreibe meistens ein python2, auch wenn das nicht nötig ist, einfach zur Verdeutlichung, dass es Python 2 ist (Siehe PEP 397 und hier). Das kam neu mit Python 3.3 hinzu.

2. Python 3 hat standardmäßig UTF-8 als Quelltextkodierung, d.h. du musst dieses # -*- coding: utf-8 -*- gar nicht mehr schreiben. Du kannst einfach nach Lust und Laune in deinem Quelltext alle möglichen Zeichen verwenden, hauptsache du speicherst es als UTF-8.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Hellstorm hat geschrieben: 1. Wenn du Python 3 nimmst, lautet der Shebang #!/usr/bin/env python3.
Bei Arch Linux z.B. nicht... finde ich immer noch doof, aber was solls :-(
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

@Hellstorm
Achja, das sind diese Dinge die man bei anderem Code aufschnappt. Dann lass ich die Kodierung weg und pass das auf python3 an. Danke für den Hinweis ;)

@BlackJack
Weiß nicht woran es liegt, mit der Exception. Wird mich aber nicht mehr stören, da ichs etwas anders mache. Außerdem fehlen mir die Kenntisse, um da weiter nachzuforschen..

LG

P.S.: In dem Link von Hyperion, wo das Erstellen eines besseren Menüs beschrieben wird, wie mache ich das, wenn ich die Funktionen mit Argumenten aufrufen möchte? Hier der Beispielcode

Code: Alles auswählen

def add_entry():
    print("Eintrag wird hinzugefügt")

def search_entry():
    print("Eintrag wird gesucht")

def remove_entry():
    print("Eintrag wird gelöscht")

def quit():
    print("Beende das Programm")

def load():
    print("Datensatz wird geladen")

def save():
    print("Datensatz wird gespeichert")

def handle_menu(menu):
    while True:
        for index, item in enumerate(menu, 1):
            print("{}  {}".format(index, item[0]))
        choice = int(input("Ihre Wahl? ")) - 1
        if 0 <= choice < len(menu):
            menu[choice][1]()
        else:
            print("Bitte nur Zahlen im Bereich 1 - {} eingeben".format(
                                                                    len(menu)))

menu = [
    ["Eintrag hinzufügen", add_entry],
    ["Eintrag löschen", remove_entry],
    ["Eintrag suchen", search_entry],
    ["Telefonbuch laden", load],
    ["Telefonbuch speichern", save],
    ["Beenden", quit]
]

handle_menu(menu)
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

*push*
Ich habs heut nochmals versucht, bei einem "Addressbuch", und ich scheitere sobald ich nicht alles hardcoden möchte und die Funktion fürs Menü mit Argumenten aufrufen muss. Fehlen denn noch infos, um mir vielleicht weiterhelfen zu können?

LG
Zuletzt geändert von Hyperion am Sonntag 19. Januar 2014, 18:07, insgesamt 1-mal geändert.
Grund: ToFu entfernt
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@HarteWare: Funktionen mit Argumenten rufst Du auf, indem Du die Argumente in den Klammern angibst. Wo ist das Problem?
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

Sirius3 hat geschrieben:@HarteWare: Funktionen mit Argumenten rufst Du auf, indem Du die Argumente in den Klammern angibst. Wo ist das Problem?
Naja, das wird ja für alle Funktionen in einer Zeile verallgemeinert durch

Code: Alles auswählen

menu[index][1]()
oder so in der Art.

Wenn ich aber zum Beispiel für die eine Funktion Argument a und für die andere Argument b oder gar keins will, dann muss ich ja irgendwie unterscheiden. Und ich frag mich halt, obs da ne gute Art gibt, oder ob ich halt mit if Abfragen etc. das machen muss
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Woher kommen die Argumente? Woher weiß das Programm, dass es ein Argument a oder b gibt?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du meinst, falls die Funktionen *unterschiedliche* Argumente bekommen müssen? In diesem Falle gibt es verschiedene Möglichkeiten.

Wenn Du immer einen Container mit Adressedaten übergeben willst und in einigen Fällen nichts, so kannst Du einfach die Funktionen so definieren:

Code: Alles auswählen

def func_with_param(adresses):
    # do something with param

def another_func_with_same_param(adresses):
    # do something with param

def func_without(*args):
    # do something *without* using params!
    # we just ignore it!

# die Funktionen einfach *immer* mit Parameter aufrufen
menu[index](adresses)
Wenn Du unterschiedliche Parameter übergeben willst, wird es kniffliger. Dann könntest Du mittels ``functools.partial`` die Funkionen einmalig zu Beginn an die zu übergebenden Objekte binden und hättest immer noch *einen* Aufruf.

So in der Art:

Code: Alles auswählen

from functools import partial

def func_1(arg):
    print(arg)

adresses = [
    {"name": "Foo", "town": "FooTown"},
    {"name": "Bar", "town": "BarTown"}
]

func_1_call = partial(func_1, adresses)
# func_1_call trägst Du in die Menüstruktur ein!

# eigentlich durch ``menu[index]()``
func_1()_call()
> [{'town': 'FooTown', 'name': 'Foo'}, {'town': 'BarTown', 'name': 'Bar'}]
Ändert sich nun im Laufe des Programms das Adressen-Objekt, so wird das ebenfalls durch das ``partial``-Objekt weitergeleitet (logisch, da Du auf demselben Objekt arbeitest!)

Code: Alles auswählen

adresses.append({"name": "FooBar", "town": "FooBarTown"})

func_1_call()
> [{'town': 'FooTown', 'name': 'Foo'}, {'town': 'BarTown', 'name': 'Bar'}, {'town': 'FooBarTown', 'name': 'FooBar'}]
Dies wäre ein Weg, der mir spontan einfiele.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
HarteWare
User
Beiträge: 69
Registriert: Samstag 23. Februar 2013, 21:16
Wohnort: localhost

@Sirius
Ich dachte halt, ich lade in der main() alle Addressen in eine Liste und arbeite dann auf der Liste und am Ende wird die Liste wieder in die Datei geschrieben (mit pickle).
Und wenn ich jetzt z.B. eine Addresse hinzufügen will, übergeb ich dann diese Liste als Argument an eine Funktion, die die Daten eingeben lässt und dann ein entsprechendes Addresse Objekt an die Liste anhängt.
Ich habe einfach Schwierigkeiten mit diesem Menü Design, sobald die dazugehörigen Funktion mehr machen als nen einfaches print() oder so. Auf der anderen Seite sehe ich schon, dass es so viel geschickter ist. Aber wenn ich z.B. in der main() irgendwas habe, dann kennen diese anderen Funktionen das garnicht. Daher weiß ich nicht recht, wie ich zwischen den verschiedenen Teilen richtig interagieren soll. Vielleicht gehe ich das Ganze auch einfach falsch an...

@Hyperion
Hab grad vorm abschicken deinen Post gesehen. Ich werd mal schauen ob ich was umsetzten kann, danke. Ich meld mich dann nochmal

LG
Antworten