Übungsaufgabe Datenbank

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
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

Hallo an alle,

Ich stecke gerade an einer weiteren Übungsaufgabe fest https://www.programmieraufgaben.ch/aufg ... k/uahdskr3

Hab es soweit, dass ich eine JSON Datenbank anlegen kann und diese auch lesen und beschreiben kann.
Allerdings haben meine Dictionaries die selben Keys (Name, Alter, Wohnort usw.), so das mit d.update(neuer_benutzer) die alten Daten überschrieben werden.
Könnt ihr mir bitte einen Denkanstoß geben, wie ich das Problem lösen kann?

Code: Alles auswählen

'''
Entwerfen Sie ein Programm womit Eingaben von Benutzern gespeichert werden:
Dies wären z. B. Name, Alter, Geburtsdatum, Wohnort, ...
Außerdem sollten diese jederzeit wieder abrufbar sein.
Viel Glück
PS: Strukturen würden nicht schaden
'''

import json

pfad = './' 
name = 'benutzer_db'
daten = {}

def lese_JSON_datei(pfad, name):
    try:
        with open(pfad + name + '.json') as f:
            print(json.load(f))
    except:
        print("Keine Datenbank vorhanden. Legen Sie einen neuen Benutzer an!")

def schreibe_JSON_datei(pfad, name, daten):
    with open(pfad + '/' + name + '.json', 'w') as f:
        json.dump(daten, f, indent = 4, separators = (',',':'))#, sort_keys = True)

def erstelle_benutzer():
    neuer_benutzer = {'Name': [input('Vorname?'), input('Nachname?')],
                    'Alter' : input('Alter?'),
                    'Wohnort': input('Wohnort?'),
                    'Familienstand': input('Familienstand?')}
    daten.update(neuer_benutzer)
    schreibe_JSON_datei(pfad, name, daten)

def lösche_benutzer(name):
    pass

def menü():
    läuft = True
    while läuft:
        print('\n')
        print('1. Daten lesen')
        print('2. Neuen Benutzer hinzufügen')
        print('3. Einen Benutzer löschen')
        print('4. Programm Ende')
        eingabe = input()
        if eingabe == '1':
            lese_JSON_datei(pfad, name)
        elif eingabe == '2':
            erstelle_benutzer()
        elif eingabe == '3':
            lösche_benutzer(input('Welchen Benutzer möchten Sie löschen?'))
        else:
            läuft = False
            

menü()
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Du brauchst eine passende Datenstruktur. Welche außer Wörterbuch kennst Du noch?

Pfade setzt man nicht mit + zusammen, sondern benutzt pathlib.Path. Benutze keine nakten except, sondern fange nur die Fehler ab, die Du auch sinnvoll verarbeiten kannst. `erstelle_benutzer` benutzt globale Variablen, das sollte nicht sein. läuft ist eigentlich überflüssig, weil man ‹Endlosschleifen› mit while-True schreiben kann, die man per `break` verläßt.
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

Die Datei schaut so aus:

Code: Alles auswählen

{
    "Name":[
        "Max",
        "Mustermann"
    ],
    "Alter":"34",
    "Wohnort":"M\u00fcnchen",
    "Familienstand":"ledig"
}
Meine Überlegung wäre, dass sie irgendwie nach Benutzer_1, Benutzer_2 usw. strukturiert sein sollte.
Also irgendwie so:

Code: Alles auswählen

benutzer_1 {
    "Name":[
        "Max",
        "Mustermann"
    ],
    "Alter":"34",
    "Wohnort":"M\u00fcnchen",
    "Familienstand":"ledig"
}
Bbenutzer_2 {
....
...
...
}
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

Sirius3 hat geschrieben: Samstag 28. März 2020, 16:40 Du brauchst eine passende Datenstruktur. Welche außer Wörterbuch kennst Du noch?

Pfade setzt man nicht mit + zusammen, sondern benutzt pathlib.Path. Benutze keine nakten except, sondern fange nur die Fehler ab, die Du auch sinnvoll verarbeiten kannst. `erstelle_benutzer` benutzt globale Variablen, das sollte nicht sein. läuft ist eigentlich überflüssig, weil man ‹Endlosschleifen› mit while-True schreiben kann, die man per `break` verläßt.
Ok Danke erstmal für die Tipps.
Also bisher kenne ich nur Listen und Dictionaries.
pathlib.Path sagt mir nichts, aber werde mich da reinlesen.
Ja das mit dem break wusste ich eigentlich aber hab nicht daran gedacht :?
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@shIxx: Was hast Du denn erwartet an der Stelle?

Anmerkungen zum Quelltext: Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Variablen gehören da nicht hin. Also in diesem Fall `daten`.

Konstanten werden per Konvention KOMPLETT_GROSS geschrieben.

"./" kann man sich sparen, das aktuelle Arbeitsverzeichnis wird auch so als Ausgangsverzeichnis bei relativen Pfadangaben verwendet. Zudem ist nicht garantiert das "." auf jedem System das aktuelle Arbeitsverzeichnis bezeichnet. Dafür gibt es, wenn man es denn tatsächlich mal brauchen sollte `os.curdir` beziehungsweise `pathlib.Path.cwd()`.

`name` bzw. `NAME` ist als Name für eine Konstante ein wenig nichtssagend.

Bei Textdateien sollte man immer explizit eine Kodierung angeben. Bei JSON-Datei ist das eigentlich *immer* "utf-8".

`f` ist kein guter Name. Wenn man `file` meint, sollte man auch `file` schreiben.

Die Funktion zum Lesen liest die Daten und gibt sie aus, aber sie werden nicht im Programm vermerkt. Man kann sie also beispielsweise nicht verändern. Die Funktion sollte einfach die eingelesenen Daten zurück geben. Und falls die Datei nicht geöffnet werden kann, dann eine leere Datenstruktur.

Beim Schreiben wird zwar eine Einrückung definiert, aber dann Leerzeichen bei den Trennzeichen weggelassen. Also eine Massnahme um die Lesbarkeit zu erhöhen, und eine um sie zu verschlechtern. Das ist eine komische Mischung. Warum?

Alter sollte eine Zahl sein und keine Zeichenkette. Beziehungsweise würde man hier nicht das Alter speichern, denn ohne zu wissen *wann* das eingegeben wurde und welches Datum aktuell ist, weiss man ja gar nicht ob das überhaupt noch stimmt wenn man es liest. Normalerweise speichert man deswegen das Geburtsdatum, denn daraus lässt sich das Alter berechnen. Je nach Anwendungsfall müsste man hier auch vorsehen das diese Information nicht vorhanden ist und in dem Fall den Schlüssel weg lassen oder `None` als Wert speichern.

`läuft` kann man sich sparen. Einfach eine Endlosschleife (``while True:``) die an entsprechender Stelle mit ``break`` verlassen wird.

Wenn Du ein Wörterbuch mit durchnummerierten Schlüsseln haben möchtest, warum nimmst Du dafür dann keine Liste?
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

__blackjack__ hat geschrieben: Samstag 28. März 2020, 16:56 @shIxx: Was hast Du denn erwartet an der Stelle?
Etwas weniger Kritik :lol: ne Spaß feedback ist gut!

Anmerkungen zum Quelltext: Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Variablen gehören da nicht hin. Also in diesem Fall `daten`.
Ok, wieder was gelernt. Ich bin kein Informatikstudent sonder komme aus dem Maschinenbau. Ich lerne es eigentlich nur so für mich und zum Spaß (und weil ich wegen Corona genug zeit dafür habe :roll: )
Konstanten werden per Konvention KOMPLETT_GROSS geschrieben.
Ich weiß aber habe nicht daran gedacht.
"./" kann man sich sparen, das aktuelle Arbeitsverzeichnis wird auch so als Ausgangsverzeichnis bei relativen Pfadangaben verwendet. Zudem ist nicht garantiert das "." auf jedem System das aktuelle Arbeitsverzeichnis bezeichnet.
Wusste ich nicht, habe es so in einem Tutorial über JSON gesehen und dort wurde gesagt das man './' angeben soll wenn man das aktuelle Verzeichnis haben möchte.
Dafür gibt es, wenn man es denn tatsächlich mal brauchen sollte `os.curdir` beziehungsweise `pathlib.Path.cwd()`.
Ok, in dieses "pathlib.Path" werde ich mich einlesen aber das brauch ich ja in diesem Fall nicht.
`name` bzw. `NAME` ist als Name für eine Konstante ein wenig nichtssagend.
Stimmt, das war mir irgendwie gar nicht klar als ich es angelegt habe
Bei Textdateien sollte man immer explizit eine Kodierung angeben. Bei JSON-Datei ist das eigentlich *immer* "utf-8".
Habe von Datenstrukturen nicht gerade viel Ahnung. Wie geht das?
Wie gesagt ich lerne Python jetzt ein paar Monate aber die letzte Wochen erst intensiver.
Habe zuerst Sachen gelernt wie Datentypen (int, float, listen, wörterbücher). Dann Schleifen und "if". Dann Funktionen und Klassen (wobei ich das mit der Objektorientierung noch nicht ganz verstanden habe) Und jetzt mach ich so kleine Übungsaufgaben.
`f` ist kein guter Name. Wenn man `file` meint, sollte man auch `file` schreiben.
Ist vermerkt!
Die Funktion zum Lesen liest die Daten und gibt sie aus, aber sie werden nicht im Programm vermerkt. Man kann sie also beispielsweise nicht verändern. Die Funktion sollte einfach die eingelesenen Daten zurück geben. Und falls die Datei nicht geöffnet werden kann, dann eine leere Datenstruktur.
Wie meinst Du das? Soll sie einfach nur "return json.load(file)" und dann eine andere Funktion die es dann ausgibt?
Beim Schreiben wird zwar eine Einrückung definiert, aber dann Leerzeichen bei den Trennzeichen weggelassen. Also eine Massnahme um die Lesbarkeit zu erhöhen, und eine um sie zu verschlechtern. Das ist eine komische Mischung. Warum?
Ichdachte mir Keys werden mit einem ":" vom value getrennt und im Falle [Vorname, Nachnale] wird mit einem "," getrennt.
Alter sollte eine Zahl sein und keine Zeichenkette. Beziehungsweise würde man hier nicht das Alter speichern, denn ohne zu wissen *wann* das eingegeben wurde und welches Datum aktuell ist, weiss man ja gar nicht ob das überhaupt noch stimmt wenn man es liest. Normalerweise speichert man deswegen das Geburtsdatum, denn daraus lässt sich das Alter berechnen. Je nach Anwendungsfall müsste man hier auch vorsehen das diese Information nicht vorhanden ist und in dem Fall den Schlüssel weg lassen oder `None` als Wert speichern.
Ja das es besser währe das Geburtsdatum zu speichern hab ich mir auch gedacht aber dann währe es unnötig kompliziert geworden weil ich dann irgendwie die Systemuhr auslesen müsste usw.Es geht mir hier nur um die Übung, sprich Daten eingeben und sie später auslesen zu können
`läuft` kann man sich sparen. Einfach eine Endlosschleife (``while True:``) die an entsprechender Stelle mit ``break`` verlassen wird.
Ist vermerkt!
Wenn Du ein Wörterbuch mit durchnummerierten Schlüsseln haben möchtest, warum nimmst Du dafür dann keine Liste?
Meinst Du gleich alles in listen speichern?
Sprich benutzer_1 = [Name, Max, Mustermann, Alter, 36, .......]
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Falscher Ansatz. Man steckt gleichartige Objekte in eine Liste und Daten mit unterschiedlicher Bedeutung, wie Name oder Alter, in Wörterbücher.
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@shIxx: `open()` hat ein `encoding`-Argument, mit dem man die Kodierung angeben kann. Das sollte man wie gesagt sowieso immer tun, denn sonst rät Python was das System da verwendet und das kann aber von System zu System, und selbst innerhalb eines Systems unterschiedlich sein. Bei JSON-Dateien ist die Kodierung immer UTF-8. Das ist zwar nicht wirklich Teil der Spezifikation, aber so ziemlich alle JSON-Dateien die man da draussen so findet die nicht nur ASCII enthalten sind UTF-8 kodiert.

Genau, die Funktion zum Laden der Daten sollte auch nur genau das machen: die Daten laden. Anzeigen ist eine separate Tätigkeit.

Die Trennzeichen bei JSON sind in der Tat ":" und "," aber davor und dahinter dürfen Leerzeichen stehen und hinter den Zeichen steht halt normalerweise auch ein Leerzeichen, was Du explizit verhinderst. Das macht man wenn einem die Lesbarkeit egal ist und man Platz/Bytes sparen möchte. Dann setzt man aber nicht gleichzeitig einen Wert für die Einrückung denn das verballert dann ja wieder unnötig Platz. Entweder will man es lesbar mit Einrückung und Leerzeichen nach Trennzeichen, oder man will es platzsparend ohne Einrückung und ohne Leerzeichen nach Trennzeichen. Eine Mischung davon macht keinen Sinn.

Nein ich meine nicht alles in Listen zu speichern. Nur die Sachen wo Du vorher ein Wörterbuch mit nummierten Schlüsseln verwenden wolltest. Eine Liste unter einem nummerierten Schlüssel zu speichern ist auch wieder genau die unsinnigste Kombination.

Eine Person wird als Wörterbuch repräsentiert. Und wenn man dann mehrere Personen, also mehrere Wörterbücher hat, kann man die in einer Liste speichern.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

OK ich habe jetzt erstmal was zum nachdenken. Wie gesagt es fällt mir alles noch ein bisschen schwer.
Es ist auch so verwirrend wenn man listen in Wörterbücher hat und dann (wie Du meinst) Wörterbücher wiederum in Listen nummeriert. Da qualmt mir der Schädel :roll:
Sirius3 hat geschrieben: Samstag 28. März 2020, 17:45 Falscher Ansatz. Man steckt gleichartige Objekte in eine Liste und Daten mit unterschiedlicher Bedeutung, wie Name oder Alter, in Wörterbücher.
Könntest Du bitte ein Beispiel machen?
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Code: Alles auswählen

daten = [
    {
        "Name": ["Max", "Mustermann"],
        "Alter": 34,
        "Wohnort": "München",
        "Familienstand": "ledig",
    },
    {
        "Name": ["Maria", "Musterfrau"],
        "Alter": 42,
        "Wohnort": "Hamburg",
        "Familienstand": "verwitwet",
    },
]
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

OK vielen Dank.
ahh und dann kann ich einfach Wörterbücher in die Liste hinzufügen, nehm ich an.

Code: Alles auswählen

neuer_benutzer = {'Name': [input('Vorname?'), input('Nachname?')],
                    'Alter' : input('Alter?'),
                    'Wohnort': input('Wohnort?'),
                    'Familienstand': input('Familienstand?')}
daten.append(neuer_benutzer)
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

Bin gerade dabei eure Tipps umzusetzen und da ist mir eine grundlegende Frage gekommen.

Was ist "besser" bzw. "richtiger" und warum?
Bei lese_JSON_datei die Funktion daten_ausgabe aufrufen oder "return json.load(file)" und das dann in der Zeile if eingabe == 1:
daten_ausgabe(lese_JSON_datei(DATENBANK)) übergeben?

oder ist beides blöd von mir?
Bin schon ganz verunsichert :lol:

Code: Alles auswählen

def daten_ausgabe(daten):
    print(daten)

def lese_JSON_datei(dateiname):
    try:
        with open(dateiname + '.json') as file:
            daten = json.load(file)
            daten_ausgabe(daten)
    except:
        print("Keine Datenbank vorhanden. Legen Sie einen neuen Benutzer an!")
        erstelle_benutzer(dateiname)
        
def menü():
    while True:
        print('\n')
        print('1. Daten lesen')
        print('2. Neuen Benutzer hinzufügen')
        print('3. Einen Benutzer löschen')
        print('4. Programm Ende')
        eingabe = int(input())
        if eingabe == 1:
            lese_JSON_datei(DATENBANK)
        elif eingabe == 2:
            erstelle_benutzer(DATENBANK)
        elif eingabe == 3:
            lösche_benutzer(input('Welchen Benutzer möchten Sie löschen?'))
        else:
            break
            
menü()
oder:

Code: Alles auswählen

def daten_ausgabe(daten):
    print(daten)

def lese_JSON_datei(dateiname):
    try:
        with open(dateiname + '.json') as file:
            return json.load(file)
    except:
        print("Keine Datenbank vorhanden. Legen Sie einen neuen Benutzer an!")
        erstelle_benutzer(dateiname)
        
def menü():
    while True:
        print('\n')
        print('1. Daten lesen')
        print('2. Neuen Benutzer hinzufügen')
        print('3. Einen Benutzer löschen')
        print('4. Programm Ende')
        eingabe = int(input())
        if eingabe == 1:
            daten_ausgabe(lese_JSON_datei(DATENBANK))
        elif eingabe == 2:
            erstelle_benutzer(DATENBANK)
        elif eingabe == 3:
            lösche_benutzer(input('Welchen Benutzer möchten Sie löschen?'))
        else:
            break
            
menü()
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei ›Daten lesen‹ willst Du ja nicht nur die Daten ausgeben, sondern auch, mit ›Benutzer hinzufügen‹ weitere Nutzer hinzufügen oder mit ›Benutzer löschen‹ einer dieser Datnsätze löschen.
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

Klar verstehe, "return" ist hier die richtige Wahl.
Ich muss mich noch daran gewöhnen, ein Problem auf die kleinst mögliche "Sache" runter zu brechen und dieses dann jeweils mit einer Funktion zu lösen bzw. verarbeiten.
Man muss irgendwann anfangen wie ein Programmierer zu denken aber das ist Übungssache und kommt mit der Zeit (schätz ich mal ^^)
einfachTobi
User
Beiträge: 512
Registriert: Mittwoch 13. November 2019, 08:38

Die Funktion `daten_ausgabe` kannste dir auch schenken, da sie nur die Daten an `print` übergibt. Da kannst du auch gleich `print` aufrufen.
Dass automatisch ein Benutzer angelegt wird, wenn ich die Daten nur lesen will, würde mich irritieren. Da wäre nach der Ausgabe, dass keine Datenbank vorhanden ist, eine Abfrage sinnvoll, ob ein neuer Benutzer angelegt werden soll.
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

Habe es nun soweit geschafft das ich Personen an die Liste anhängen kann und diese auch speichern und wieder abrufen kann.
Jetzt geht es mir ums löschen. Ich möchte den Index der zur löschenden Person aus der Liste erfahren und dann den eintrag mit der pop methode entfernen:
Bsp.:
person = input('Wen möchten Sie löschen? [Nachname]')
daten_löschen(person)

Code: Alles auswählen

import json

with open('db.json', 'r') as file:
    daten = json.load(file)

# print(len(daten))

for index in daten:
    for value in index.values():
            print(value)
die Ausgabe schaut dann wie folgt aus:

Code: Alles auswählen

['Max', 'Mustermann']
45
Hamburg
ledig
['Maria', 'Musterfrau']
34
Berlin
verwitwet
Wie kann ich den Eintrag vergleichen?
Ich komme nicht dahinter :cry:

Code: Alles auswählen

# Es müsste irgendie in der Art gehen
if type(value) == list():
	for j in value:
		if j == person
			zu_löschender_index = # dann gebe mir den index zurück
daten.pop(zu_löschender_index)
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

`index` ist kein Index, sonst könntest Du gar nicht `.value` aufrufen. Beim Programmieren ist es wichtig, die Dinge richtig zu benennen; `daten` sind `personen` und `index` ist eine `person`.

Zum zweiten Codestück: wie soll der Typ einer Variable gleich der Instanz einer Liste sein?
Typprüfungen sind eigentlich so gut wie nie nötig. Was willst Du im anderen Fall den tun?
Statt etwas zu löschen, erzeugt man einfach eine neue Liste, die das gesuchte Element nicht mehr enthält. Dazu braucht man dann auch nicht mehr den Index zu kennen.

Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht mit 8 Leerzeichen oder gar Tabs.
shIxx
User
Beiträge: 22
Registriert: Sonntag 13. Oktober 2019, 16:11

Sirius3 hat geschrieben: Sonntag 29. März 2020, 21:57 `index` ist kein Index, sonst könntest Du gar nicht `.value` aufrufen. Beim Programmieren ist es wichtig, die Dinge richtig zu benennen; `daten` sind `personen` und `index` ist eine `person`.
Stimmt natürlich,.. wie gesagt ich bin etwas verwirrt.
Habe eine Datei db.json in dieser ist eine Liste und in dieser wiederum Wörterbücher wo unter Name wieder eine Liste ist und weitere Wörterbücher für Alter usw.
Zum zweiten Codestück: wie soll der Typ einer Variable gleich der Instanz einer Liste sein?
Typprüfungen sind eigentlich so gut wie nie nötig. Was willst Du im anderen Fall den tun?
Statt etwas zu löschen, erzeugt man einfach eine neue Liste, die das gesuchte Element nicht mehr enthält. Dazu braucht man dann auch nicht mehr den Index zu kennen.
Es war irgendwie mein Denkansatz, dass ich den Eintrag der Liste finden muss, indem der Name mit dem vom User eingegebenen übereinstimmt. Dann wüsste ich an welcher stelle das zu löschende Wörterbuch ist, und dann kann ich es mit .pop entfernen.
Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht mit 8 Leerzeichen oder gar Tabs.
Das war weil mir die Idee spontan beim schreiben des Postes gekommen ist und da hab ich es mit Tab eingerückt,.. sry

Naja ist schon spät. Gute Nacht euch allen, bleibt gesund und danke für die konstruktive Hilfe!
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@shIxx: Zur Einrückung: Das klingt komisch. Der Editor ist ja normalerweise so eingestellt das ein Druck auf die Tab-Taste mit vier Leerzeichen einrückt. Wie hast Du denn da ein Tab als Zeichen eingegeben? Oder rückst Du vier Leerzeichen weit ein in dem Du tatsächlich viermal die Leertaste drückst?
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten