mitmario.dev

Klasse und Objekt

Python Sandbox 4 Min Lesezeit 4 BeispieleLektion 1 von 6

In Mias Laden hat jeder Artikel einen Namen, einen Preis und einen Bestand. Als Dictionary geht das gut, solange jedes Dictionary an genau einer Stelle entsteht. Meistens entstehen sie aber an vielen. Im Kurs stehen schon länger Daten so beisammen, die zusammengehören, etwa eine Ausgabe mit Datum, Kategorie und Betrag.

Was ein Dictionary nicht weiß

Drei Artikel als Dictionaries
def zeile(artikel):
    return f"{artikel['name']}: {artikel['preis']:.2f} Euro, {artikel['bestand']} Stück"


regal = [
    {"name": "Brot", "preis": 2.49, "bestand": 12},
    {"name": "Milch", "preis": 1.19, "bestand": 30},
    {"name": "Butter", "Preis": 2.29, "bestand": 8},
]
for artikel in regal:
    print(zeile(artikel))

Drei Artikel, und beim dritten ist Preis groß geschrieben. Python stört das nicht, ein Dictionary nimmt jeden Schlüssel, den man ihm gibt. Erst zeile() fragt nach preis und bekommt KeyError: 'preis'.

Der Traceback zeigt auf Zeile 2, in die Funktion. Der Fehler steckt aber in Zeile 8, wo die Butter angelegt wird, und die kommt im Traceback gar nicht vor. Das kennst du aus Lektion 9.1. Die Meldung kommt dort, wo der Wert gebraucht wird, und nicht dort, wo er falsch hineingekommen ist.

Es fehlt also eine Stelle, an der festgelegt ist, dass ein Artikel einen Namen, einen Preis und einen Bestand hat. Genau diese drei, und ohne sie gibt es keinen.

Ein Bauplan

Eine Klasse für Artikel
class Artikel:
    def __init__(self, name, preis, bestand):
        self.name = name
        self.preis = preis
        self.bestand = bestand


brot = Artikel("Brot", 2.49, 12)
milch = Artikel("Milch", 1.19, 30)

print(brot.name, brot.preis, brot.bestand)
print(milch.name, milch.preis, milch.bestand)

brot.bestand = brot.bestand - 3
print(brot.bestand, milch.bestand)
print(type(brot))

Diese Stelle ist eine Klasse (class). class Artikel: legt einen neuen Typ an, so wie es int und str schon gibt. Darunter steht, wie ein Artikel gebaut wird.

Die Klasse selbst ist noch kein Artikel, sie ist der Bauplan. Artikel("Brot", 2.49, 12) baut nach diesem Plan ein Objekt (object), so wie int("3") eine Zahl baut. Aus einem Bauplan können beliebig viele Häuser entstehen, alle mit denselben Räumen und jedes mit eigenen Möbeln.

Gebaut wird in __init__, mit zwei Unterstrichen auf jeder Seite, wie bei __name__. Diese Methode ruft Python für jedes neue Objekt genau einmal auf, und zwar von selbst. Die Werte in der Klammer hinter Artikel landen in den Parametern hinter self. Und self ist das Objekt, das gerade entsteht.

self.name = name hängt ihm den Namen an. Links steht das Attribut (attribute) des Objekts, rechts der Parameter. Dass beide gleich heißen, ist üblich. Wenn du ein Objekt mit Namen anlegst, also Artikel(name="Brot", preis=2.49, bestand=12), findest du dieselben Wörter danach als Attribute wieder.

Gelesen wird ein Attribut mit einem Punkt, brot.name, und geändert wird es wie eine Variable. Im Beispiel sinkt der Bestand vom Brot um drei, und die Milch bleibt bei 30. Jedes Objekt hat seine eigenen Werte.

type(brot) sagt <class '__main__.Artikel'>, also die Klasse samt der Datei, in der sie steht. __main__ ist die Datei, die gestartet wurde. Klassennamen fangen mit einem Großbuchstaben an. Python verlangt das nicht, aber fast jeder hält sich daran, und so siehst du auf einen Blick, dass Artikel der Bauplan ist und artikel ein einzelnes Stück.

Der Fehler kommt jetzt dort, wo er entsteht

Dasselbe Regal mit der Klasse
class Artikel:
    def __init__(self, name, preis, bestand):
        self.name = name
        self.preis = preis
        self.bestand = bestand


def zeile(artikel):
    return f"{artikel.name}: {artikel.preis:.2f} Euro, {artikel.bestand} Stück"


regal = [
    Artikel("Brot", 2.49, 12),
    Artikel("Milch", 1.19, 30),
    Artikel(name="Butter", Preis=2.29, bestand=8),
]
for artikel in regal:
    print(zeile(artikel))

Dasselbe Regal, diesmal aus Objekten. Die Butter hat wieder Preis mit großem P, als benanntes Argument. Jetzt meldet sich Python schon beim Anlegen, in Zeile 15, und seit Python 3.13 gibt es sogar einen Vorschlag dazu: TypeError: Artikel.__init__() got an unexpected keyword argument 'Preis'. Did you mean 'preis'?. Ausgegeben wird gar nichts, weil das Regal nie entsteht.

Bei einem vergessenen Wert ist es genauso. Artikel("Butter", 2.29) endet mit Artikel.__init__() missing 1 required positional argument: 'bestand', wieder in der Zeile, in der der Bestand fehlt. Wenn ein Wert fehlen darf, bekommt der Parameter einen Standardwert, wie bei einer Funktion.

Ein vergessenes self

Ein vergessenes self
class Artikel:
    def __init__(self, name, preis, bestand):
        self.name = name
        preis = preis
        self.bestand = bestand


brot = Artikel("Brot", 2.49, 12)
print(brot.name)
print(brot.preis)

In __init__ fehlt vor dem Preis das self.. Python sagt dazu nichts, denn preis = preis ist eine ganz gewöhnliche Zuweisung an eine Variable in der Funktion. Und die ist weg, sobald __init__ fertig ist, wie jede Variable in einer Funktion.

Das Objekt hat danach einen Namen und einen Bestand, aber keinen Preis. Auffallen tut das erst, wenn jemand nach dem Preis fragt: AttributeError: 'Artikel' object has no attribute 'preis'.

Im Reiter Debug steht ein Objekt übrigens vorerst als <__main__.Artikel object at 0x…> da, mit einer Zahl, die bei jedem Lauf eine andere ist. Was drinsteckt, siehst du dort erst, wenn in der Klasse steht, wie ein Artikel als Text aussehen soll. Das kommt zwei Lektionen später.

Was die Aufgabe will

Drei Konten stehen als Dictionaries da. Eins davon ist neu und hat noch keinen Stand. Gesucht ist eine Klasse Konto, mit der ein neues Konto bei null anfängt, und ein Programm, das mit ihr arbeitet statt mit den Dictionaries.

Zum Mitnehmen

class Artikel: legt einen Bauplan an, und Artikel("Brot", 2.49, 12) baut danach ein Objekt. __init__ läuft für jedes neue Objekt einmal und hängt ihm mit self.name = name seine Attribute an. Lesen geht mit dem Punkt, etwa brot.name, und ohne self davor bleibt ein Wert nur in __init__ und ist danach weg.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.