Adressbuch

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.
Xfd7887a
User
Beiträge: 135
Registriert: Montag 23. Juni 2014, 17:11

Habe folgenden Code bearbeitet:

Code: Alles auswählen

def suchen(adressen, gesucht):
    """Einzelne Adresse suchen"""
    suche = []
    for adresse in adressen:
        if gesucht.lower() in [x.lower() for x in adresse.values()]:
            suche.extend(adresse)
    return suche
Leider kommt folgende Fehlermeldung:

Code: Alles auswählen

/usr/local/bin/python3.3m /Users/martin/Documents/Projekte/_stuff/Adressbuch_Python/Adressbuch.py
Traceback (most recent call last):
  File "/Users/martin/Documents/Projekte/_stuff/Adressbuch_Python/Adressbuch.py", line 104, in <module>
    main()
  File "/Users/martin/Documents/Projekte/_stuff/Adressbuch_Python/Adressbuch.py", line 97, in main
    adresse_anzeigen(adressen, suchen(adressen, "Kins"))
  File "/Users/martin/Documents/Projekte/_stuff/Adressbuch_Python/Adressbuch.py", line 70, in adresse_anzeigen
    adresse = suchen(adressen, name)
  File "/Users/martin/Documents/Projekte/_stuff/Adressbuch_Python/Adressbuch.py", line 56, in suchen
    if gesucht.lower() in [x.lower() for x in adresse.values()]:
AttributeError: 'list' object has no attribute 'lower'
BlackJack

@Xfd7887a: Nun ganz offensichtlich ist entweder `gesucht` oder mindestens ein Wert für `x` eine Liste. Finde heraus welches und warum.
Xfd7887a
User
Beiträge: 135
Registriert: Montag 23. Juni 2014, 17:11

Ok, danke. Mein bisheriger Stand: https://github.com/toxinman/stuff/blob/ ... essbuch.py Morgen mache ich weiter.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Wieso wird denn beim Anzeigen immer noch die ``suchen``-Funktion aufgerufen? Was soll die da bewirken? Ich schrieb das schon einmal: Eine Ausgabe setzt doch implizit voraus, dass man etwas hat, was man ausgeben will. Wieso muss ich das dann erst suchen :?: Das solltest Du dringend ändern!

(Abgesehen davon, dass das Ausgeben von einer Adresse ohne Not aktuell eine Zeitkomplexität von O(n) aufweist und das Ausgeben von allen Addressen damit sogar O(n^2)!)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

Wieso wird denn beim Anzeigen immer noch die ``suchen``-Funktion aufgerufen? Was soll die da bewirken? Ich schrieb das schon einmal: Eine Ausgabe setzt doch implizit voraus, dass man etwas hat, was man ausgeben will. Wieso muss ich das dann erst suchen :?: Das solltest Du dringend ändern!
Ich weiß leider nicht, wie ich das anders machen könnte. Wenn ich die Suche entferne, also schon in der main-Funktion suchen lasse, passt das nicht mehr mit der "adressen_ausgeben"-Funktion.
BlackJack

@_nohtyp_: Du musst die `adresse_ausgen()` so ändern das man der *eine* Adresse übergibt, die dann ausgegeben wird. Also das naheliegendste und einfachste. Und beim Aufruf übergibt man der Funktion dann *eine* Adresse.
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

Ok, ich habe es geändert
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

_nohtyp_ hat geschrieben: Ich weiß leider nicht, wie ich das anders machen könnte. Wenn ich die Suche entferne, also schon in der main-Funktion suchen lasse, passt das nicht mehr mit der "adressen_ausgeben"-Funktion.
Na übergibst eben *ein* fertiges Adressen-Objekt :K Wo ist da denn das Problem? Imho geht das doch kaum einfacher...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

_nohtyp_ hat geschrieben:Ok, ich habe es geändert
OMG... doch nicht so:

Code: Alles auswählen


def adressen_anzeigen(adressen):
    """Alle Adressen werden formatiert ausgegeben"""
    for adresse in adressen:
        adresse_anzeigen(adressen, suchen(adressen, adresse["Name"]))
        print()


def adresse_anzeigen(adressen, adresse):
    """Eine Adresse wird formatiert ausgegeben"""
    for key in adresse:
        print("{}: {}".format(key, adresse[key]))
:shock:

Schau Dir doch mal an, *was* Du in der Funktion ``adresse_anzeigen`` wirklich von den übergebenen Parametern nutzt! Und dann überlege, inwiefern der Aufruf in der ``adressen_anzeigen``-Funktion sinnvoll ist :mrgreen:

Du machst es Dir irgend wie schrecklich kompliziert...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

Sorry, aber ich blicke nicht durch, was ich ändern muss. Das bei "adresse_anzeigen" "adressen" eigentlich nicht gebraucht wird, sehe ich. Aber wenn ich "adressen_anzeigen" aufrufe, brauche ich es.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

_nohtyp_ hat geschrieben:Sorry, aber ich blicke nicht durch, was ich ändern muss. Das bei "adresse_anzeigen" "adressen" eigentlich nicht gebraucht wird, sehe ich. Aber wenn ich "adressen_anzeigen" aufrufe, brauche ich es.
Das eine hat doch mit dem anderen nichts zu tun :!:

Code: Alles auswählen

def adressen_anzeigen(adressen):
    """Alle Adressen werden formatiert ausgegeben"""
    for adresse in adressen:
        adresse_anzeigen(adresse)
        print()
 
 
def adresse_anzeigen(adresse):
    """Eine Adresse wird formatiert ausgegeben"""
    for key, value in adresse.items():
        print("{}: {}".format(key, value))
Einfach, oder? 8)

Das ist beim Programmieren übrigens ein durchaus häufiges Schema: Man erweitert Funktionalität, indem man auf *bestehendes* aufbaut. Du hattest zuerst ja eine Funktion, die *eine* Adresse angezeigt hat. Die *logische* (und von mir damals vorgeschlagene) Erweiterung dient dann dazu, eine *Sammlung* von Adressen anzuzeigen. Dazu muss man dann ja nur durch die Sammlung iterieren und für *jedes einzelne* Element die bestehende Funktion aufrufen.

Also nach diesem Schema:

Code: Alles auswählen

def tue_etwas_fuer_ein_objekt(ein_objekt):
    pass

def tue_etwas_fuer_viele_objekte(objekte):
    for ein_objekt in objekte:
        tue_etwas_fuer_ein_objekt(ein_objekt)
Du hast stattdessen die Schleife *in* die bestehende Funktion eingebaut und damit nahm das "Übel" seinen Lauf ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

Genial, danke.
Einfach, oder?
Ja, eigentlich schon. Bin aber nicht drauf gekommen, ist manchmal halt so. :D
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Versuche Dir, das "Schema" zu merken und Dich bei Zeiten daran zu erinnern :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

Werde ich machen. Zu den Indexen: Sollte ich den Index gleich mit in das Dictionary einer Adresse setzen, oder einzeln. Also entweder:

Code: Alles auswählen

{"Name": ...., "ID": ...}
oder

Code: Alles auswählen

{{"Name": ...}: "ID}
?
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

_nohtyp_ hat geschrieben:

Code: Alles auswählen

{{"Name": ...}: "ID}
Ähhh?

Code: Alles auswählen

>>> {{"Name": "Python"}: "ID"}
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: unhashable type: 'dict'
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

Ok, also nehme ich die erste Variante. Danke :)
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

Habe ein wenig weitergearbeitet: https://github.com/toxinman/stuff/blob/ ... essbuch.py Nicht hauen, vieles ist noch Mist :D
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo,

ein paar Dinge kannst du da noch verbessern, ich fange einfach mal oben an:

Zum Erzeugen der Dateinamen könntest du dir eine kleine Funktion schreiben. Sowohl beim Laden als auch beim Speichern hängst du am Ende ein ".json" an den Namen dran. Hast du das an einer Stelle, dann bist du etwas flexibler.

String solltest du nicht mittels + zusammensetzen. Das ist nicht nur langsam, was in deinem Fall keine Rolle spielt, es sieht auch noch unübersichtlich aus. Benutze einfach String Formatting, dann sieht das Ende von Zeile 27 auch sauber aus.

Eine Zeile sollte nicht länger als 80 Zeichen sein. Das fällt besonders in Zeile 27 auf. Schon wegen der Übersichtlichkeit würde ich jedem Eintag eine eigene Zeile spendieren, dann liest es sich leichter.

Der enumerate-Funktion in Zeile 38 kannst du einen Startindex mitgeben, dann musst du in der Folgezeile nicht bei jedem Durchlauf eins drauf addieren.

In deinem try-Block sollte nur das nötigste stehen. Auf keinen Fall mehr. Zeilen 42 und 43 sind dafür ein Beispiel, du hast aber noch mehr davon. Der try-Block enthält die Eingabe, die Umwandlung in einen Integer und die Abfrage. Du prüfst aber nur die Fehler bei der Umwandlung in einen Integer. Ziehe also die Eingabe davor und packe lediglich den int-Aufruf (inklusive einer Zuweisung) in den try-Block. Den Zugriff auf "suche" kannst du in einem else-Block machen, welches du einem try/except-Konstrukt hinzufügen kannst. Dann ist auch sichergestellt, dass du nicht aus Versehen Ausnahmen behandelst, welche du nicht behandeln willst/kannst. Zum Beispiel in anderen Funktionen.

Für Benutzereingaben solltest du dir eine Funktion schreiben, dann musst du dich nicht ständig wiederholen.

Zeile 45 ist keine gute Idee, rekursive Aufrufe solltest du in Python vermeiden. Zumindest solche, welche beliebig tief werden können. Das könnte irgendwann zu einem Problem werden. Verwende besser eine while-Schleife. Üblicherweise verwendet man eine ``while True:``-Schleife und verlässt diese im Erfolgsfall mittels break. Das selbe Konstrukt hast du an einer anderen Stelle noch einmal.

Du solltest für die Schlüssel in deinem Dictionary Konstanten verwenden. Sonst fliegen überall in deinem Programm magische Strings durch die Gegend, deren Bedeutung unklar ist. Ganz problematisch wird es, wenn du einen solchen String mal ändern willst/musst. Dann musst du nämlich alle Vorkommen im Programm finden. Das funktioniert aber meistens nicht auf Anhieb. Hast du hingegen Konstanten, musst du das nur an genau einer Stelle erledigen.

Telefonnummern sind keine Integer.
Das Leben ist wie ein Tennisball.
_nohtyp_
User
Beiträge: 127
Registriert: Mittwoch 8. Januar 2014, 15:38

@EyDu Erstmal Danke für deine Antwort :) Ich habe ein paar Fragen dazu:
Zum Erzeugen der Dateinamen könntest du dir eine kleine Funktion schreiben. Sowohl beim Laden als auch beim Speichern hängst du am Ende ein ".json" an den Namen dran. Hast du das an einer Stelle, dann bist du etwas flexibler.
Meinst du damit, dass ich die Dateiendung als Konstante definieren soll?
Für Benutzereingaben solltest du dir eine Funktion schreiben, dann musst du dich nicht ständig wiederholen.
Wie meinst du das? Unterscheiden sich die Benutzereingabe dafür nicht zu sehr?
Üblicherweise verwendet man eine ``while True:``-Schleife und verlässt diese im Erfolgsfall mittels break.
In dieser Form:

Code: Alles auswählen

while True:
    auswahl = input("Welche Adresse haben Sie gesucht? >> ")
    try:
        auswahl = int(auswahl)
    except (ValueError, IndexError) as e:
        print(e)
    else:
        break
Oder geht das auch kürzer?
Du solltest für die Schlüssel in deinem Dictionary Konstanten verwenden.
Und dann den entsprechenden Schlüssel durch z.B.

Code: Alles auswählen

KEYS[1]
ersetzen?

Das war erstmal alles :D
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

_nohtyp_ hat geschrieben:
Du solltest für die Schlüssel in deinem Dictionary Konstanten verwenden.
Und dann den entsprechenden Schlüssel durch z.B.

Code: Alles auswählen

KEYS[1]
ersetzen?
Nee, um Gottes Willen nein. Sondern z.B. so:

Code: Alles auswählen

NAME = "name"
VORNAME = "vorname"
...
# und zur Verwendung
adresse[NAME]
Der Vorteil davon, den EyDu meinte, kannst Du schnell begreifen, wenn Du aus ``name`` nun lieber ``nachname`` machen willst. Jetzt müsstest Du nur die Definition Deiner Konstanten ändern, der Rest vom Code bliebe gleich.

Ich selber wäre mir aber nicht sicher, ob ich das wirklich machen wollen würde. Ist hier vielleicht ein wenig Geschmackssache. Imho ist der Name eines Schlüssels in dieser Problemdomäne vergleichbar mit einem Attributsnamen einer Klasse. Wenn ich den ändern will, muss ich normaler Weise gute Gründe haben und eh refactoring betreiben ;-)

Ich finde aber andere Dinge eher verbesserungsbedürftig. Du verfällst schon wieder in das Muster, *zu viel* und vor allem *unterschiedliche* Dinge in *eine* Funktion zu packen!

Nehmen wir ``suchen`` mal als Beispiel, ich habe meine Kritik als Kommentare hinein geschrieben:

Code: Alles auswählen

def suchen(adressen, gesucht):
    """Adresse wird gesucht. Nutzer wählt aus Ergebnissen Suche Eintrag."""
    # ich würde ``suche`` eher ``such_ergbenisse`` oder nur ``ergebnisse`` oder
    # ``gefunden`` nennen.
    suche = []
    for adresse in adressen:
        if gesucht.lower() in [x.lower() for x in adresse.values()]:
            suche.append(adresse)
    # hier müsste eigentlich Schluss sein! Gib das Ergebnis zurück und gut ist!

    # wieso nutzt Du nicht die Funktion für die Ausgabe von *mehreren* Adressen?
    # nichts anderes ist doch in ``suche`` enthalten.
    # eine wichtige Fähigkeit beim Programmieren ist es, wiederkehrende Aufgaben
    # zu kapseln und darauf zurückzugreifen.
    for idx, ergebnis in enumerate(suche, 1):
        print("{}: {}".format(idx, ergebnis))

    # Benutzereingaben / Entscheidungen gehören nicht in eine Funktion,
    # die schlicht "suchen" soll. Du kannst hier gar nicht wissen, ob
    # evtl. auch etwas mit *allen* Resultaten gemacht werden soll!
    auswahl = input("Welche Adresse haben Sie gesucht? >> ")
    try:
        auswahl = int(auswahl)
    except (ValueError, IndexError) as e:
        print(e)
        suchen(adressen, gesucht)
    else:
        return suche[auswahl - 1]
Ähnliches gilt für das Löschen oder auch das Ändern. Schreibe erst einmal nur Funktionen, die wirklich diese Dinge *tun* - ohne Nachfrage und ohne weitere Eingaben. So etwas regelst Du außerhalb und vor dem Aufruf einer Funktion.

Denk daran, dass Du ursprünglich eine GUI für dein Adressbuch haben wolltest. "Dank" der Mischung von Eingaben und dem Erledigen von Aufgaben in *einer* Funktion, kannst Du diese für eine GUI nicht mehr verwenden und müsstest das komplett neu schreiben. Den Prompt eines ``input`` siehst Du in einer GUI ja gar nicht! Du musst also schon bei der Benutzung über eine Textshell darauf achten, dass alle Eingaben und Ausgaben getrennt von der Kernfunktionalität stattfinden! Denn letztlich ist das auch schon eine Form von GUI - nur ohne das "G" ;-)

Vielleicht noch als Hinweis für die Gestaltung von solchen Textmenüs: Tutorial
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten