Strukturiere deinen Code mit gut durchdachten Klassen: Initialisierung, Instanz- oder Klassenattribute, Vererbung und ihre Kompromisse.
Diese Lektion in Kodokon öffnenDu 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.
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 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.
class Basket:
items = []
def add(self, item):
self.items.append(item)
a = Basket()
b = Basket()
a.add("apple")
print(b.items) # ['apple']: shared list!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.
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())