Kodokon kodokon.com

OOP: Klassen, __init__, Methoden und Vererbung

Strukturiere deinen Code mit gut durchdachten Klassen: Initialisierung, Instanz- oder Klassenattribute, Vererbung und ihre Kompromisse.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Du arbeitest ständig mit Python-Objekten: Strings, Listen, Dateien. Eigene Klassen zu schreiben bedeutet, Zustand (die Attribute) und Verhalten (die Methoden) hinter einer kohärenten Schnittstelle zu bündeln. Ein oft missverstandener Vokabelpunkt: __init__ ist kein Konstruktor. Das Objekt existiert bereits (von __new__ erzeugt), wenn __init__ self erhält; seine Aufgabe ist es, die Instanz zu initialisieren. Und an self ist nichts Magisches: Es ist der erste Parameter jeder Instanzmethode, den Python beim Aufruf automatisch übergibt.

PYTHON
class Account:
    def __init__(self, owner, balance=0.0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be > 0")
        self.balance += amount
        return self.balance

    def __repr__(self):
        return f"Account({self.owner!r}, {self.balance})"

account = Account("Ada")
account.deposit(120.0)
print(account)
Eine vollständige Klasse: __init__, eine Geschäftsmethode und __repr__

Eine im Produktivbetrieb entscheidende Unterscheidung: Klassenattribute werden im Rumpf der Klasse deklariert und von jeder Instanz geteilt; Instanzattribute werden auf self erzeugt, meist in __init__. Beim Lesen schaut Python zuerst auf der Instanz nach, greift dann auf die Klasse zurück und danach auf die Elternklassen. Eine Zuweisung self.x = ... erzeugt immer ein Instanzattribut, das das gleichnamige Klassenattribut verdeckt. Die klassische Falle im echten Leben: ein veränderliches Klassenattribut.

PYTHON
class Basket:
    items = []

    def add(self, item):
        self.items.append(item)

a = Basket()
b = Basket()
a.add("apple")
print(b.items)  # ['apple']: shared list!
Die Falle des veränderlichen Klassenattributs

Vererbung drückt eine "ist ein"-Beziehung aus: ein Car ist ein Vehicle. Die Unterklasse erbt die Methoden, kann sie überschreiben, und super() erlaubt ihr, die Version der Elternklasse aufzurufen - unverzichtbar in __init__, damit du die Initialisierung des Elternteils nicht kurzschließt. Python löst Aufrufe auf, indem es der MRO (Method Resolution Order) folgt. Greife nicht systematisch zur Vererbung: Um Code ohne eine "ist ein"-Beziehung wiederzuverwenden, bevorzuge Komposition (ein Objekt besitzt ein anderes). Tiefe Hierarchien sind fragil: Jede Elternklasse wird zu einer impliziten API für all ihre Kinder.

PYTHON
class Vehicle:
    def __init__(self, brand):
        self.brand = brand

    def describe(self):
        return self.brand

class Car(Vehicle):
    def __init__(self, brand, doors):
        super().__init__(brand)
        self.doors = doors

    def describe(self):
        base = super().describe()
        return f"{base}, {self.doors} doors"

print(Car("Aster", 5).describe())
Vererbung und super(): erweitern, ohne neu zu schreiben

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Was genau ist die Rolle von __init__?
    • Das Objekt im Speicher erzeugen
    • Eine bereits erzeugte Instanz initialisieren
    • Die Klassenattribute deklarieren
    • Bei jedem Attributzugriff aufgerufen werden
  2. Eine Klasse deklariert items = [] in ihrem Rumpf. Nach a.add(1), das self.items.append(1) ausführt, was sieht eine andere Instanz b?
    • b.items ist eine leere Liste
    • b.items enthält 1: Die Liste wird geteilt
    • Python löst einen AttributeError aus
  3. Was ist in Car.__init__ der Sinn des Aufrufs super().__init__(brand)?
    • Eine zweite Vehicle-Instanz erzeugen
    • Die in der Elternklasse definierte Initialisierung ausführen
    • Die Methoden von Vehicle in Car kopieren
    • Prüfen, dass Car wirklich von Vehicle erbt