Frage zu append von Variablen

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
swissbird
User
Beiträge: 5
Registriert: Freitag 23. Juli 2021, 17:54

Hallo Forum

Zuerst soll gesagt sein, dass ich blutigster Programmier-Anfänger bin, befinde mich gegen Ende eines ersten Python-Grundkurses, der meinen ersten ernsthaften Kontakt mit diesem Fachgebiet darstellt. Leider ist der auswählte, kostenpflichtige Kurs ein ziemlicher Reinfall, er diente mir vor allem dazu, über die aufgetauchten Fragen eine grosse Sammlung von hilfreichen Seiten anzulegen, über die ich mir weiterhelfe. Der Kurs selber, oder besser dessen Videos, ist von offensichtlichen Fehlern und Irrtümmern durchzogen, die ich sogar als Anfänger erkenne. Trotzdem will ich im Sinne meiner Entwicklung jede aufgetauchte Frage und Unklarheit für mich klären, und nun gibt es eine, auf die ich nirgendwo im Netz eine Antwort gefunden habe. So entsteht nun meine erste Frage hier im Forum.

Thema: Die Aufgabe ist das Erstellen eines Telefonbuches, in dem mittels Input der User einen Namen und eine Nummer eingeben kann, welche dann in einer Liste gespeichert werden. Das ganze soll aus zwei Dateien bestehen, die eine mit der Klasse und den Funktionen, die andere mit der Liste und dem Code für die Eingabemaske.

So weit, so klar.

In den angehängten Screenshots des Tutorials dazu sieht man nun die finale Situation, jedoch ist auch in beiden Screenshots ein markierter Fehler sichtbar. Darauf wird im Tutorial nicht eingegangen, es endet so wie es zu sehen ist. Baut man dies nach, funktioniert es selbstverständlich dann auch nicht.

Die Frage, welche ich mir nun irgendwo selbst beantworten konnte, betrifft nun den Fehler in der letzten Zeilen des zweiten Screenshots. Was ist dort an der Zuordnung zur definierten Variable falsch, warum wird diese Code-Zeiler nicht akzeptiert?

Die Fehlermeldung lautet "invalid syntax".

Der Fehler in der untersten Zeile des oberen Screenshots konnte ich nicht reproduziert werden, das passt.

Bild


Bild


Vielen Dank für Eure Hilfe! Ich versichere übrigens, dass ich hier nie mit Fragen kommen werden, für die ich nur zu faul war um zu googlen. Ist nicht mein Stil...ich versuche mir immer zuerst selber zu helfen. :D
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ach herrje, das sieht aus wie jemand der von Java oder so kommt und das in Python genau so machen will, wie er es von dort kennt.

Man schreibt nicht ein Modul pro Klasse, denn Module sind dazu da um mehrere Funktionen und Klassen zusammenzufassen. Wenn jedes Modul nur eine einzige Klasse enthält, dann verschenkt man das Organisationsmittel „Modul“ unnötigerweise.

Die `object.__str__()`-Methode greift auf `__repr__()` zurück. Es macht also keinen Sinn `__repr__()` und `__str__()` zu implementieren und da genau den gleichen Code rein zu schreiben, denn das genau gleiche Ergebnis erhält man, wenn man nur `__repr__()` implementiert.

Die doppelten führenden Unterstriche Bei Namen und Nummer bedeuten nicht das was der Autor denkt. Das sollte nur ein führender Unterstrich sein. Da es dann aber triviale Getter- und Setter-Methoden gibt, sollte es aber gar keinen Unterstrich geben, denn in dem Fall greift man einfach direkt auf die Attribute zu. Triviale Getter/Setter macht man in Python nicht. Das macht man in Programmiersprachen die keine berechneten Attribute a.k.a. „Properties“ kennen. Python hat so etwas aber.

Damit schrumpft die Klasse dann hieraus zusammen:

Code: Alles auswählen

class TelefonbuchEintrag:
    def __init__(self, name, nummer):
        self.name = name
        self.nummer = nummer
    
    def __repr__(self):
        return self.name
Was hier nicht gut ist, ist `__repr__()` weil das die Methode ist, die eine Zeichenkettenrepräsentation erstellen soll, die dem Programmierer bei der Fehlersuche helfen soll. Die `object`-Implementierung hat darum den Datentyp und eine eindeutige Hexadezimalzahl in der Darstellung. So weiss man *was* das ist, und kann alle Objekte von diesem Typ in der `repr()`-Darstellung voneinander unterscheiden. Wenn man das überschreibt, sollte man das so machen, dass ein Programmierer etwas damit anfangen kann. Zum Beispiel in dem man mehr Informationen zu dem Typ und der ID hinzufügt. Wenn der Rückgabewert nicht in <…> einfasst ist, dann ist er in der Regel in einer Form, die man wieder in Python-Quelltext einfügen kann. Das könnte dann so aussehen:

Code: Alles auswählen

class TelefonbuchEintrag:
    def __init__(self, name, nummer):
        self.name = name
        self.nummer = nummer
    
    def __repr__(self):
        return f"{self.__class__.__name__}({self.name!r}, {self.nummer!r})"
Das sieht dann so aus:

Code: Alles auswählen

In [2]: eintrag = TelefonbuchEintrag("Peter Rabbit", "555-1234567")             

In [3]: eintrag                                                                 
Out[3]: TelefonbuchEintrag('Peter Rabbit', '555-1234567')
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). `Telefonbuch` sollte also `telefonbuch` heissen.

``while`` ist keine Funktion, das sollte man nicht so schreiben als wäre es eine. Da gehört ein Leerzeichen hinter das Schlüsselwort. Und die Klammern um das `True` sind überflüssig.

Man sollte keine kryptischen Abkürzungen als Namen verwenden. `inp` sollte wohl `input` heissen, so heisst aber schon die Funktion. `eingabe` wäre ein sinnvoller Name wenn da schon Deutsch für die Bezeichner verwendet wird. Im Englischen böte sich `answer` an.

Das die Vergleiche der Antwort in der Reihenfolge c, a, b erfolgen, ist ein bisschen verwirrend IMHO.

`exit()` gibt es so eigentlich gar nicht. Das würde man aus `sys` importieren müssen. Aber `sys.exit()` sollte man nur verwenden, wenn man zumindest potentiell einen anderen Rückgabecode als 0 an den Aufrufer des Programms zurückgeben will. Sonst ist das eher der Versuch sich darum zu drücken den Programmablauf sauber zu gestalten. Man könnte hier beispielsweise einfach mit ``break`` die Schleife verlassen.

Da `inp` nur einen der getesteten Werte haben kann, würde man hier für zwei der Tests auf diese Bedingung(en) ``elif`` statt ``if`` nehmen. Dann wird nicht unnötig getestet, und man kann auch einen ``else``-Zweig ans Ende setzen, mit einer Fehlermeldung.

Code: Alles auswählen

#!/usr/bin/env python3


class TelefonbuchEintrag:
    def __init__(self, name, nummer):
        self.name = name
        self.nummer = nummer

    def __repr__(self):
        return f"{self.__class__.__name__}({self.name!r}, {self.nummer!r})"


def main():
    telefonbuch = []
    while True:
        eingabe = input(
            "Was möchtest Du tun?\n"
            " a) Telefonbuch ausgeben?\n"
            " b) Eintrag hinzufügen\n"
            " c) Programm verlassen\n"
        )
        if eingabe == "a":
            print(telefonbuch)
        elif eingabe == "b":
            name = input("Gib den Namen ein: ")
            nummer = input("Gib die Nummer ein: ")
            telefonbuch.append(TelefonbuchEintrag(name, nummer))
        elif eingabe == "c":
            break
        else:
            print(f"Fehler! Ungültige Eingabe {eingabe!r}.")


if __name__ == "__main__":
    main()
Beide Fehler in den Bildern kann ich so nicht erklären. Der Editor/die IDE die Du da verwendest wird aber irgendwo noch erklären warum das unterkringelt ist. Beides sind aber wohl Warnungen und keine Fehler, denn die IDEs die ich kenne unterkringeln Fehler rot. Ansonsten müsstest Du realen Code als Quelltext zeigen und nicht als Bild(er).
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@swissbird,

noch ein kleiner Nachtrag zu der sehr ausführlichen Antwort von __blackjack__.
Du scheinst als IDE PyCharm zu verwenden. In den strengsten Einstellungen werden da auch Rechtschreibfehler unterschlängelt.
Das ist leider völlig unsinnig, da dann zum Beispiel "TelefonBuchEintrag" unterschlängelt wird.
Das würde ich in den Einstellungen ausschalten, damit man die unsinnigen Fehlermeldungen von den hilfreichen unterscheiden kann.

Edit: Wenn dieser Code so wirklich im kostenpflichtigen Kurs gezeigt wurde, ist das schon eine Frechheit. Es gibt so viele kostenfreie Kurse die oft eine bessere Qualität haben als dies hier.
paddie
User
Beiträge: 101
Registriert: Donnerstag 11. Oktober 2018, 18:09

Die grün unterkringelten Stellen sind wohl wirklich (von PyCharm angenommene) Rechtschreibfehler. Da er aber wohl auf Englisch eingestellt ist, wird er dir mindestens mal alles deutsche als falsch unterkringeln ;-)

Zu den unteren Zeilen. PyCharm testet auch auf PEP8-Konformität. Wenn du mit der Maus mal darüberfährst sollte er dir anzeigen "PEP 8: W 292: no newline at end of file". Dieser markierte "Fehler" sollte also nichts mit dem eigentlichen Programmablauf zu tun haben.
swissbird
User
Beiträge: 5
Registriert: Freitag 23. Juli 2021, 17:54

Hallo zusammen

WOW, das ist mal eine Hilfe! Vielen herzlichen Dank (und jetzt habe ich auch noch die Email-Benachrichtigung bei Antworten hier aktiviert, so dass ich das nächste Mal schneller wieder zurück bin).

@__blackjack__ In Deiner Antwort ist sehr viel zum Lernen drin, ich werde mich morgen ausführlich damit auseinandersetzen. Jedenfalls bin ich schon mal etwas beruhigt, dass einiges, was mich störte, nun als falsch bestätigt wird.

@rogerb Die Screenshots sind nicht von meiner IDE, sondern vom Video innerhalb der Lektion. Der Kurs hat kein Vermögen gekostet, konkret € 59.-, aber ich habe recht schnell begriffen, dass er mir vor allem durch die Suche nach Antworten zu einer langen Liste an Lesezeichen verhilft, unter denen ich wirklich was lerne. Ich kann auch gerne erwähnen, dass es der Kurs von edley.de ist. Aber trotzdem, ja, ich trainiere derzeit mit PyCharm, auf Empfehlung von dort hin natürlich. Was wäre denn von Euch empfohlen? VS Code?

Danke auch für den Hinweis der "eben nicht" Fehler-Markierungen. Beim zweiten Versuch des Nachbauens in der Zwischenzeit hat mein Code dann auch teilweise funktioniert (bis auf dass bei der Abfrage nur der Name, aber keine Nummer zurückgegeben wurde), dies lag daher vermutlich an einem Fehler meinerseits.

Wie gesagt, ich befasse mich morgen damit und melde mich dann wieder, und nicht mehr mit Screenshots.

Danke nochmals, coole Ersterfahrung hier im Forum!!
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@swissbird,
Was wäre denn von Euch empfohlen? VS Code?
Ich persönlich mag VS Code, aber PyCharm ist auch gut. Diese komische Rechtschreibprüfung habe ich nie verstanden. Ansonsten kann man gut damit arbeiten.
swissbird
User
Beiträge: 5
Registriert: Freitag 23. Juli 2021, 17:54

So, bin ein Stückchen weiter. Erstens habe ich mit VS Code fortgesetzt (installiert hatte ich es bereits). Einige Themen verstand ich vorher noch nicht, oder erst halb. z.B. der Sinn von

Code: Alles auswählen

if __name == "__main__":
ist mir jetzt klar. Getter/Setter in der Theorie auch. __str__() vs __repr__() muss ich noch vertiefen, die Erklärung habe ich verstanden, aber noch nicht, wann ich in Python __str__() überhaupt benötige. Aber das werde ich finden.

Zum Telefonbuch:
Ich habe das Programm exakt nach viertem Code nachgebaut (selbst, nicht kopiert) und einiges versucht. b und c funktionieren, bei a hingegen kommt folgende Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "/Users/xxx/VSCode/telb1.py", line 30, in <module>
    main()
  File "/Users/xxx/VSCode/telb1.py", line 19, in main
    print(telefonbuch)
  File "/Users/xxx/VSCode/telb1.py", line 7, in __repr__
    return f"{self.__class__.__name__}({self.name.r}, {self.nummer.r})"
AttributeError: 'str' object has no attribute 'r'
Fehlt etwas? Der dritte Code in der Antwort muss nicht rein, korrekt?
Meine Recherche nach dem Hintergrund von ".r" sowie dem Sinn von "f" vor dem return/print Argument war bisher erfolglos. Lesen kann ich selbst, aber falls mir jemand den Namen dieser Argumente oder einen Link zu einer schlauen Beschreibung nennen könnte, wäre ich froh, da sich nach etwas mit einem Buchstaben bei Google schlecht suchen lässt (python ".r" oder python f behind-print brachten leider keine Lösung). :wink:

Vielen Dank!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist kein .r sondern ein !r und bedeutet, dass statt __str__ __repr__ für die Darstellung aufgerufen wird. Das f sind Formatstrings.
swissbird
User
Beiträge: 5
Registriert: Freitag 23. Juli 2021, 17:54

Vielen Dank! Und damit ist auch die Fehlerquelle gefunden. Habe auch .r statt !r falsch gecodet, spannenderweise wurde kein Fehler markiert.
swissbird
User
Beiträge: 5
Registriert: Freitag 23. Juli 2021, 17:54

Nochmals eine Frage zu !r

Habe nun mit dem Programm etwas herumgespielt, und wenn ich an allen drei Orten das !r weglasse, ist der einzige Unterschied, dass die Ausgabewerte nicht in 'single quote escape' eingefasst sind. Was hat es mit dem auf sich?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@swissbird: Was meinst Du mit „allen drei Orten”? Das sind nur zwei "!r" in dem Quelltext.

Das "!r" entspricht einem `repr()`-Aufruf und ermittelt die Zeichenkettenrepräsentation des Objekts, also fragt die vom Objekt selbst ab. Und diese Repräsentation ist per Konvention entweder etwas was man als Quelltext verwenden könnte um ein gleichwertiges Objekt zu erzeugen, oder etwas in „spitzen Klammern“ ("<...>") was bei der Fehlersuche nützlich sein kann. Die Defaultimplementierung von `object` in CPython ist der Klassenname + die `id()` in Hexadezimaldarstellung. Was in CPython die Speicheradresse ist, an der sich das Objekt befindet. Das ist allerdings ein Implementierungsdetail, das kann in anderen Python-Implementierungen anders aussehen! Das heisst wenn man `__repr__()` nicht selbst implementiert, bekommt man zumindest eine Zeichenkette in der man den Datentyp ablesen kann, und eine Zahl die es erlaubt verschiedene Objekte vom gleichen Typ zu unterscheiden (die aber durchaus den gleichen Wert haben können).

Code: Alles auswählen

In [210]: print(repr(object()))                                                 
<object object at 0x7fee65701260>

In [211]: class Foo: pass                                                       

In [212]: print(repr(Foo()))                                                    
<__main__.Foo object at 0x7fee65666780>
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten