Hallo, ich bin's mal wieder.
Momentan lerne ich mit dem Buch "Objektorientierte Programmierung mit Python 3" von Michael Weigend (wie schneidet das bei euch eigentlich so ab - besser als die Bücher vom Galileo-Verlag?) und nach meinem "Taschenrechner-Projekt" habe ich mich mal an etwas "größeres" gewagt: eine Art Telefonbuch.
Ich hab mich dabei strikt an die Aufgabenstellung aus dem Buch gehalten und deshalb gäbe es wohl noch mehr mögliche Funktionen (die Möglichkeit, Personen aus dem Telefonbuch zu streichen etc.), aber ich wollte erst mal fragen, ob das noch pythonischer geht.
http://paste.pocoo.org/show/547465/
PS: Darf ich den Lösungscode aus dem Buch eigentlich hier hochladen? Ich will das nur wissen, weil der Autor eine globale Variable benutzt hat (die hier ja so verteufelt wird) und ich ohne ausgekommen bin.
Telefonbuch
Wenn man es nicht anders lösen könnte, dürften wir die globalen Variablen nicht verteufeln. Warum nutzt du die Objektorientierung nicht aus?Mugen hat geschrieben:Na ja, ich meinte jetzt ein global-Statement in einer Funktion.
Ließe sich das denn noch anders lösen?
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Ja klar, indem Du das Dictionary-Objekt einfach an die Funktionen übergibstMugen hat geschrieben: Ließe sich das denn noch anders lösen?

Code: Alles auswählen
def save_new_number(phone_book):
# usw.
Einige Anmerkungen zum Code:
- Es ist imho ungewöhnlich, dass eine ``main``-Funktion ganz oben in der datei steht. IdR. erwarte ich die unten direkt über dem ``if __name__``-Hook.
- Wieso merkst Du Dir die Menüeinträge nicht auch in Deinem Dispatcher-Dict? z.B. so:
Code: Alles auswählen
choices = {
'S': (search_number, (S)uche nach Telefonnummer),
'N': (save_new_number, "(N)eue Nummer eintragen"),
'A': (print_numbers, "(A)lle Nummern ausgeben"),
'E': (sys.exit, "(E)nde")
}

- Beim Dispatching kannst Du Dir das Überschreiben von ``choice`` sparen:
Code: Alles auswählen
# Deine Lösung
try:
choice = choices[choice]
choice()
except KeyError:
print("Auswahl nicht erkannt")
print()
continue
# imho hübscher:
try:
choices[choice]()
except KeyError:
print("Auswahl nicht erkannt", end="\n\n")
- ``search_number`` kann so doch nicht klappen.
Code: Alles auswählen
print(list(telefonbuch.values())[0])

Genau an dieser Stelle kann man sich auch streiten, ob man mit einem ``if something in dict``-Konstrukt sicher stellt, dass ein Name im Telefonbuch enthalten ist, oder es mittels ``try... except KeyError`` löst, wie Du das beim Dispatchen oben tust.
- Zeile 54 kann man kürzen:
Code: Alles auswählen
# bei dir
for i in tel.keys():
# kompakter
for name in tel:
- Wieso schreibst Du kein leeres Dict in die Datei bei der Fehlerbehandlung in ``print_numbers``? Taucht denn da gar kein Fehler beim Laden auf in Zeile 51:
Code: Alles auswählen
with open("tel.txt", "rb") as tel:
tel = pickle.load(tel)
Generell sieht das alles gar nicht so verkehrt aus. Aber: Bist Du Dir darüber im klaren, dass Du immer auf Dateiebene arbeitest? Bei jedem Eintrag in Dein Telefonbuch speicherst Du das Telefonbuch-Dict und bei jeder Anzeige lädst Du das Objekt wieder in den Speicher. Ist das sinnvoll? Beim Neuanlegen ist das vielleicht sogar eine gute Idee, damit man Änderungen nicht immer separat speichern muss (per zudätzlichem Menüpunkt). Aber beim Anzeigen der Datensätze, musst Du das doch nicht neu laden?! Du hast die Daten ja im Speicher

Zudem hast Du den Dateinamen ``tel.txt`` somit als "magic number" fest in Deinem Code in jeder Funktion. Imho ist es da sinnvoller, die Dateioperationen von den eigentlichen Datenoperationen zu separieren und zudem den Dateinamen als Parameter an diese IO-Funktionen zu übergeben. Wenn Du ihn nicht per Kommandozeilenparameter übergeben magst, kannst Du den tatsächlich auf Modulebene festlegen:
Code: Alles auswählen
PHONE_BOOK_FILENAME = "tel.txt"
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
So, erst mal Entschuldigung, dass ich so spät antworte.
Ich habe soweit die Vorschläge von Hyperion umgesetzt, aber ganz zufrieden bin ich (und ihr wahrscheinlich auch) damit nicht.
http://paste.pocoo.org/show/548457/
Fix: http://paste.pocoo.org/show/548471/ (aktuell)
@if-Kaskaden von Zeile 52-57: Dort wusste ich nicht, wie man den Funktionen im Dictionary beim Aufruf Argumente übergibt. Ist die Lösung in Ordnung, oder wird dadurch das dict obsolet?
Im Moment krieg ich das Anfügen von neuen Daten auch nicht hin. Ich hab schon versucht als Modus ``"ab+"`` zu wählen, aber da wird bei mir gar nichts angefügt.
Edit: Die "unterdrückten" Exceptions (Zeile 21-22, Zeile 30-31) habe ich nur eingefügt, damit es bei einer leeren ``tel.txt`` keinen Programmabbruch gibt. Sicherlich auch nicht optimal?
Ich habe soweit die Vorschläge von Hyperion umgesetzt, aber ganz zufrieden bin ich (und ihr wahrscheinlich auch) damit nicht.
http://paste.pocoo.org/show/548457/
Fix: http://paste.pocoo.org/show/548471/ (aktuell)
@if-Kaskaden von Zeile 52-57: Dort wusste ich nicht, wie man den Funktionen im Dictionary beim Aufruf Argumente übergibt. Ist die Lösung in Ordnung, oder wird dadurch das dict obsolet?
Im Moment krieg ich das Anfügen von neuen Daten auch nicht hin. Ich hab schon versucht als Modus ``"ab+"`` zu wählen, aber da wird bei mir gar nichts angefügt.
Edit: Die "unterdrückten" Exceptions (Zeile 21-22, Zeile 30-31) habe ich nur eingefügt, damit es bei einer leeren ``tel.txt`` keinen Programmabbruch gibt. Sicherlich auch nicht optimal?
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Oh weia... das sieht eher verschlimmbessert aus, sorry!Mugen hat geschrieben: Ich habe soweit die Vorschläge von Hyperion umgesetzt, aber ganz zufrieden bin ich (und ihr wahrscheinlich auch) damit nicht.
Die sind überflüssig und ja, die machen Dir ja den vorteil des Dicts kaputt! Dein Problem ist, dass Du verschiedene Objekte an die Funktionen weiterreichst. Dabei willst Du doch nur ein und dasselbe an alle weiterreichen, nämlich Dein Telefonbuch-Dictionary! Wieso gibt es dafür zweimal den Namen ``tel`` und einmal ``telbook``? Zumal Du da immer noch Bockmist baust: Wieso legst Du eine leere Datei an? Wieso legst Du keine Datei mit einem leeren Dictionary an? Das sind doch zwei Paar Schuh! Und der ``EOFError`` kommt doch bestimmt dann, wenn man aus einer leeren Datei etwas "pickeln" will, oder?Mugen hat geschrieben: @if-Kaskaden von Zeile 52-57: Dort wusste ich nicht, wie man den Funktionen im Dictionary beim Aufruf Argumente übergibt. Ist die Lösung in Ordnung, oder wird dadurch das dict obsolet?
Im ``else``-teil steht doch der Aufruf schon - Du musst ja nur noch den Parameter einsetzen.
Ok, ``sys.exit`` nimmt ja kein Dictionary entgegen... da könnte man mittels ``lambda`` einen Workaround basteln:
Code: Alles auswählen
'E': (lambda _: sys.exit(), "(E)nde")}
Ja, die waren mir eh unklar, aber daran sieht man ja, dass Du einen fetten Fehler bezüglich Deiner Datenstruktur drin hastEdit: Die "unterdrückten" Exceptions (Zeile 21-22, Zeile 30-31) habe ich nur eingefügt, damit es bei einer leeren ``tel.txt`` keinen Programmabbruch gibt. Sicherlich auch nicht optimal?

Der Name ``save_new_number`` gefällt mir so auch nicht; denn darin werden ja zwei Dinge erledigt: 1.) Ein neuer Datensatz in das Dict eingefügt und 2.) das Dict gespeichert. Wieso nicht zwei Funktionen daraus bauen?
Und wieso heißt der Parameter bei zwei Funktionen ``tel`` und einmal ``telbook``, obwohl immer das gleiche Objekt übergeben werden soll?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
http://paste.pocoo.org/show/548548/
So besser?
Ansonsten hätte ich noch eine Frage: Wie kann ich prüfen, ob die Datei ein Dictionary enthält, welches bereits mit Daten gefüllt ist? Dann sollte im append-binary-Modus gespeichert werden, weil ansonsten ja die anderen Daten futsch wären.
Edit: Das return-Statement in Zeile 22 war übrigens nur zum Testen.
So besser?
Ansonsten hätte ich noch eine Frage: Wie kann ich prüfen, ob die Datei ein Dictionary enthält, welches bereits mit Daten gefüllt ist? Dann sollte im append-binary-Modus gespeichert werden, weil ansonsten ja die anderen Daten futsch wären.
Edit: Das return-Statement in Zeile 22 war übrigens nur zum Testen.
Code: Alles auswählen
In [1]: bool({})
Out[1]: False
In [2]: bool({1:23})
Out[2]: True

- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Imho ja.Mugen hat geschrieben: So besser?
Aber:
- In Zeile 10 willst Du einen ``IOError`` abfangen - oder nicht? Niemals einfach so ein except benutzen. Damit fängst Du alle Exceptions ab - und das willst Du nicht.
- Der einzelne Aufruf in Zeile 51 ist unnötig.
- Ich würde die Funktion nicht ``make_dict`` nennen - es geht Dir ja um das Telfeonbuch. Und das generierst Du ja auch nicht (immer), sondern lädst es aus einer Datei. Wieso also nicht ``get_telbook``? Ggf sogar ``load_telbook``.
- Es fehlen noch ein Shebang und ein Encoding-Cookie

- Es fehlt die komplette Doku

Ich lasse immer zwei Zeilen zwischen Funktionen frei; das erhöht imho die Leserlichkeit. Speziell, weil man ja ab und an einzelne Zeilen auch innerhalb von Funktionen leer lässt.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert