Anfänger sucht Hilfe

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
Badabumm89
User
Beiträge: 3
Registriert: Dienstag 21. September 2021, 12:34

Guten Tag Leute...

Ich habe mir das Buch "Python3 Programmieren für Einsteiger" von dem Autor Michael Bonacina gekauft.

Vielleicht kennt das einer von euch, oder hat es gar selbst?

Naja auf jeden Fall komme ich da nicht weiter.
Weil selbst die Musterlösung aus dem Buch bei mir nicht funktioniert.

Am Ende jedes Kapitels kommen 2 Übungsaufgaben.

Die erste lautet:
Schreibe ein Programm für eine Gästeliste. Diese soll eine Schleife erhalten, in der man beliebig viele Namen eintragen kann.
Wenn schon eine Gästeliste bestehen sollte, dann sollen die neuen Namen an die Liste angehängt werden.

Das wurde so gelöst:
f = open('dateien\gaeste.txt','a')

while True:
eingabe = input("Fügen Sie einen neuen Namen hinzu" +
"(Zum Abbrechen 'quit' eingeben): ")

if eingabe == "quit":
break

f.write(eingabe+"\n")

f.close() # <- selbst hinzugefügt weil es sonst nicht funktioniert hätte

Die zweite Aufgabe lautet:
Man soll ein Programm schreiben, was einen Gast von der Liste löscht.
Da keine Möglichkeit bekannt ist, einen Teilbereich der Datei zu entfernen, soll das Programm zunächst die komplette Liste einlesen und in einer Liste speichern.

Überprüfen Sie anschließend ob der Name enthalten ist.
Ist dies der Fall müssen Sie den Eintrag aus der Liste entfernen und die Datei erneut öffnen-dieses mal mit dem Attribut 'w', um den bisherigen Inhalt zu überschreiben.

Geben Sie eine Meldung aus, ob der Eintrag gelöscht wurde.
Oder ob er nicht in der Liste enthalten ist.

Das Musterbeispiel aus dem Buch:
f = open('dateien\gaeste.txt','r')
liste = f.readlines()
f.close()


geloescht = False

eingabe = input("Welchen Gast möchten Sie löschen? ")


for i in range(len(liste)):
if liste == eingabe + "\n":
f = open('dateien\gaeste.txt','w')
liste = liste[:i] + liste[i+1:]

for linie in liste:
f.write(linie)
print (eingabe, "wurde erfolgreich gelöscht.")
geloescht = True
break

if not geloescht:
print (eingabe, "ist nicht in der Liste enthalten.")

f.close()

Doch das funktioniert nicht.
Ich habe es schon mit .remove(eingabe + "\n") versucht, aber da war die Datei leer.
Und sonst habe ich im Internet auch nichts weiter dazu gefunden.

Meine Lösung war dann:
f = open('dateien\gaeste.txt', 'r')
liste = f.readlines()
f.close()

f = open('dateien\gaeste.txt', 'w')

eingabe = input("Welchen Gast möchten Sie löschen?")

for i in range(len(liste)):

if liste == eingabe+"\n":

liste = liste[:i] + liste[i+1:]

for linie in liste:
f.write(linie)
break

f.close()

Da konnte ich den Namen und nur den Namen löschen.
Habe aber dafür keine Ausgabe ob das nur Funktioniert hat, oder der Gast überhaupt auf der Gästeliste stand.


Meine Frage an euch a) Was ist an der Musterlösung aus dem Buch falsch? Veraltet vielleicht?
Und b) Wie kann ich meinen Code erweitern, damit ich die zweite Aufgabe lösen kann?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Badabumm89: Wenn das Schliessen der Datei in der Musterlösung fehlt, dann ist das ein Fehler der Musterlösung. Damit einem das nicht passiert, dass eine Datei nicht geschlossen wird, zum Beispiel auch wenn der Programmablauf an einem vorhandenen `close()` nicht vorbei kommt, beispielsweise weil vorher eine Ausnahme ausgelöst wurde, verwendet man Dateien wo es möglich ist mit der ``with``-Anweisung:

Code: Alles auswählen

with open(R"dateien\gaeste.txt", "a", encoding="urf-8") as file:
    while True:
        eingabe = input(
            "Fügen Sie einen neuen Namen hinzu"
            " (Zum Abbrechen 'quit' eingeben): "
        )
        if eingabe == "quit":
            break

        file.write(eingabe + "\n")
Habe da gleich noch ein paar Sachen geändert: Namen sollten in der Regel nicht nur aus einem Buchstaben (oder kryptischen Abkürzungen bestehen), wenn man `file` meint, sollte man nicht nur `f` schreiben.

In dem Dateinamen/Pfad kommt ein \ vor, da sollte man ”rohe” Zeichenkettenliterale verwenden um Überraschungen zu vermeiden falls das Zeichen danach mit dem \ zusammen eine besondere Bedeutung hat.

Textdateien sollte man immer explizit mit einer Kodieurngsangabe öffnen, damit es nicht von den Systemeinstellungen abhängt welche Kodierung verwendet wird. Falls man die Wahl hat, bietet sich UTF-8 an, weil damit alle Zeichen kodiert werden können, die man auch bei `input()` eingeben kann.

Die zweite Musterlösung ist ein bisschen schlimmer fehlerhaft. Bei der ersten hat man eigentlich nur Probleme wenn man das in einer Umgebung ausführt bei der das offene Datei-Objekt am Ende bestehen bleibt. Zum Beispiel in IDE wie IDLE, wenn man das mit einem Debugger ausführt, oder in interaktiven Umgebungen wie JupyterLab/-Notebooks. Wenn man das normal als Programm startet und das dann Endet, schliesst das Betriebssystem beim Abräumen des Prozesses dessen noch offene Dateien. Ist aber unsauber sich darauf zu verlassen.

Beim Einlesen der Zeilen gilt das gleiche wie schon für das erste Programm gesagte: ``with``, ”rohes” Zeichenkettenliteral, Kodierungsangabe beim öffnen der Datei, und ein besserer Name als `f` für das Datei-Objekt.

`liste` ist kein guter Name weil der nicht verrät was in der Liste enthalten ist. `zeilen` wäre sinnvoller.

In der Schleife zum Löschen wird es dann wirr. Die Einrückung ist falsch, aber auch Inhaltlich macht das nicht wirklich Sinn bei jedem Fund die Zeilen neu zu schreiben. Man würde erst die Vorkommen entfernen, und danach die Zeilen neu schreiben. Und auch hier wieder mit ``with`` arbeiten. Dazu ist das letzte `f.close()` auch an der völlig falschen Stelle. Sollte die Eingabe gar nicht gefunden werden, dann hat man einfach nur Glück, dass es `f` bereits vom Einlesen gibt, und dass man `close()` auch mehrfach hintereinander, also auch auf einer bereits geschlossenen Datei aufrufen kann, ohne dass dabei etwas passiert, beispielsweise eine Ausnahme weil das ja eigentlich nicht viel Sinn macht.

Falls man nur das erste Vorkommen löschen will, dann kann man `remove()` verwenden. Falls man alle Vorkommen löschen will, würde man einfach eine neue Liste ohne den Eintrag aufbauen.

In beiden Fällen braucht man keine Schleife über einen Laufindex. Das ist in Python ein „code smell“ wenn man etwas schreibt wie ``for i in range(len(sequence)):``. Das ist ein „anti pattern“ weil man in Python direkt über die Elemente iterieren kann, ohne den Umweg über einen Index. Falls man *zusätzlich* zu den Elementen eine laufende Zahl benötigt, gibt es die `enumerate()`-Funktion

`linie` ist als Name falsch. Das ist keine Linie, sondern eine Zeile.

Daten sollte man (genau wie Code) nich wiederholen. Der Dateiname wird mehr als einmal verwendet, der sollte aber nur einmal im Quelltext stehen. Als Konstante zum Beispiel.

Ungetestet:

Code: Alles auswählen

DATEINAME = R"dateien\gaeste.txt"

with open(DATEINAME, "r", encoding="utf-8") as file:
    zeilen = list(file)

eingabe = input("Welchen Gast möchten Sie löschen? ")
alte_laenge = len(zeilen)

#
# Erstes Vorkommen entfernen:
#
zeilen.remove(eingabe + "\n")
#
# oder alle Vorkommen entfernen:
#
zeilen = [zeile for zeile in zeilen if zeile != eingabe + "\n"]

if len(zeilen) != alte_laenge:
    print(f"{eingabe} wurde entfernt.")
    with open(DATEINAME, "w", encoding="utf-8") as file:
        file.writelines(zeilen)
else:
    print(f"{eingabe} wurde nicht gefunden.")
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei der "Musterlösung" ist die Einrückung nicht korrekt. Weil unabhängig vom `if` beim ersten Durchgang die Liste wieder in die Datei geschrieben wird.

Wenn man in Python einen Index benutzt, dann macht man meist etwas falsch.
Dateien öffnet man immer innerhalb eines with-Statements, dann wird sie auch automatisch wieder geschlossen.
Bei Text-Dateien sollte man immer explizit das Encoding angeben:

Code: Alles auswählen

GAESTELISTE = 'dateien\gaeste.txt'
with open(GAESTELISTE , encoding="utf8") as file:
    gaeste = [line.strip() for line in file]

eingabe = input("Welchen Gast möchten Sie löschen? ")
try:
    gaeste.remove(eingabe)
except ValueError:
    print("Gast nicht gefunden")
else:
    with open(GAESTELISTE, encoding="utf8") as file:
        file.writelines(gast + "\n" for gast in gaeste)
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Upsi, bei meinem Beispiel das mit dem `remove()` bitte ignorieren, weil ich die Ausnahme gar nicht auf dem Schirm hatte. 😳
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Badabumm89
User
Beiträge: 3
Registriert: Dienstag 21. September 2021, 12:34

Vielen Dank für die schnellen Antworten.
Aber das Programm möchte einfach nicht laufen, (der zweite Teil mit dem löschen vom Gast).

Bei Sirius3 - Lösung kommt folgende Fehlermeldung:

Welchen Gast möchten Sie löschen? Jan
Traceback (most recent call last):
File "E:\Desktop\Programmfehler\test.py", line 12, in <module>
file.writelines(gast + "\n" for gast in gaeste)
io.UnsupportedOperation: not writable

Bei der Lösung von _blackjack_ kommt nur ein Fehler, wenn der Name nicht in der Liste enthalten ist.
Ansonsten funktioniert es, also es wird der Name ausgegeben, welchen man gelöscht hat.

Hier die Fehlermeldung, wenn man einen Namen eingibt, welcher nicht auf der Liste steht:

Welchen Gast möchten Sie löschen? Jan
Traceback (most recent call last):
File "E:\Desktop\Programmfehler\test_2.py", line 12, in <module>
zeilen.remove(eingabe + "\n")
ValueError: list.remove(x): x not in list


Desweiteren möchte ich mich für die Tipps bedanken.
Jetzt weiß ich das ich für's Dateien lesen etc. den with nutzen muss und das man immer eine Angabe zur Codierung machen sollte.

Ich würde auch gerne wissen was das R zu sagen hat in:
with open(R"dateien\gaeste.txt", "a", encoding="urf-8") as file:

Ich habe es spaßeshalber mal ohne R gemacht und da hat es den ersten Eintrag in der Liste immer nochmal dazu geschrieben.
Bsp.: Jan, Thomas, Gerd, Holger standen in der Liste. Dann habe ich die erste Datei nochmal ausgeführt und weitere Namen der Liste hinzugefügt.
Als ich die Datei gaeste.txt danach geöffnet hatte stand dort am Anfang zweimal Jan -> Jan, Jan, Thomas, Gerd, Holger, Maria, Bärbel, Dana

Kann mir das einer erklären^^?


Ich hoffe das ich meine Fragen nicht zu umständlich formuliert habe.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Bei mir fehlt der Schreib-Modus:

Code: Alles auswählen

    with open(GAESTELISTE, mode="w", encoding="utf8") as file:
        file.writelines(gast + "\n" for gast in gaeste)
Bei __blackjack__ fehlt das Exception-Handling, wie er ja schon angemerkt hatte.
Das r vor literalen Strings dient dazu, dass \ nicht als Escape-Zeichen interpretiert wird. Besser wäre als Verzeichnistrenner gleich / zu benutzen:

Code: Alles auswählen

GAESTELISTE = 'dateien/gaeste.txt'
Das hat aber nichts mit den doppelten Einträgen zu tun, die kommen daher, dass Du die Datei im Modus "a" für anhängen geöffnet hast.
Badabumm89
User
Beiträge: 3
Registriert: Dienstag 21. September 2021, 12:34

Danke habe es hinbekommen.
Antworten