Python & AI Tutorials Logo
Python Programmierung

30. Klassen und Objekte einführen

30.1) Die Idee der objektorientierten Programmierung (Eigene Typen erstellen)

In diesem Buch haben Sie durchgehend mit Pythons eingebauten Typen gearbeitet: Ganzzahlen, Strings, Listen, Dictionaries und mehr. Jeder Typ bündelt Daten (wie die Zeichen in einem String) mit Operationen, die Sie auf diesen Daten ausführen können (wie .upper() oder .split()). Diese Kombination aus Daten und Verhalten ist leistungsfähig – sie ermöglicht es Ihnen, Strings als vollständige Einheiten mit eigenen Fähigkeiten zu betrachten, nicht nur als rohe Zeichenfolgen.

Objektorientierte Programmierung (object-oriented programming, OOP) erweitert diese Idee: Sie erlaubt Ihnen, Ihre eigenen benutzerdefinierten Typen zu erstellen, genannt Klassen (classes), die Daten und Verhalten bündeln, die spezifisch für Ihren Problembereich sind. So wie Python einen str-Typ für die Arbeit mit Text und einen list-Typ für die Arbeit mit Sequenzen bereitstellt, können Sie einen BankAccount-Typ zur Verwaltung finanzieller Transaktionen, einen Student-Typ zum Verfolgen akademischer Daten oder einen Product-Typ für ein Inventarsystem erstellen.

Warum eigene Typen erstellen?

Stellen Sie sich vor, Sie verwalten Informationen über Schüler/Studierende in einem Schulsystem. Ohne Klassen könnten Sie separate Variablen oder Dictionaries verwenden:

python
# Verwendung separater Variablen – wird schnell unübersichtlich
student1_name = "Alice Johnson"
student1_id = "S12345"
student1_gpa = 3.8
 
student2_name = "Bob Smith"
student2_id = "S12346"
student2_gpa = 3.5
 
# Oder Verwendung von Dictionaries – besser, aber immer noch eingeschränkt
student1 = {"name": "Alice Johnson", "id": "S12345", "gpa": 3.8}
student2 = {"name": "Bob Smith", "id": "S12346", "gpa": 3.5}

Dieser Ansatz funktioniert für einfache Fälle, aber er hat Einschränkungen:

  1. Keine Validierung: Nichts hindert Sie daran, gpa auf einen ungültigen Wert wie -5.0 oder "excellent" zu setzen
  2. Kein zusammengehöriges Verhalten: Operationen wie das Berechnen eines Ehrenstatus (Honors-Status) oder das Formatieren von Studierendeninformationen sind separate Funktionen, die überall in Ihrem Code verstreut sind
  3. Keine Typprüfung: Ein Dictionary, das einen Studierenden repräsentiert, sieht identisch aus wie jedes andere Dictionary – Python kann Ihnen nicht helfen, Fehler zu erkennen, bei denen Sie versehentlich ein Produkt-Dictionary verwenden, wo ein Studierenden-Dictionary erwartet wurde

Klassen lösen diese Probleme, indem sie Ihnen erlauben, einen neuen Typ zu definieren, der genau repräsentiert, was ein Student ist und welche Operationen für Studierende sinnvoll sind:

python
# Darauf arbeiten wir hin – eine Student-Klasse, die Daten und Verhalten bündelt
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def is_honors(self):
        return self.gpa >= 3.5
    
    def display_info(self):
        status = "Honors" if self.is_honors() else "Regular"
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa} [{status}]"
 
# Jetzt können wir Student-Objekte erstellen
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346", 3.5)
 
print(alice.display_info())  # Output: Alice Johnson (S12345) - GPA: 3.8 [Honors]
print(bob.is_honors())       # Output: True

Dieses Kapitel wird Ihnen beibringen, wie Sie solche Klassen von Grund auf aufbauen. Wir beginnen mit den einfachstmöglichen Klassen und fügen schrittweise Funktionen hinzu, bis Sie reichhaltige, nützliche benutzerdefinierte Typen erstellen können.

Klassen vs. Instanzen: Die Blaupausen-Analogie

Das Verständnis des Unterschieds zwischen einer Klasse (class) und einer Instanz (instance) ist grundlegend für die objektorientierte Programmierung:

  • Eine Klasse (class) ist wie eine Blaupause oder Vorlage. Sie definiert, welche Daten ein Objekttyp enthalten wird und welche Operationen er ausführen kann. Die Klasse selbst ist kein konkreter Studierender – sie ist die Definition dessen, was es bedeutet, ein Studierender zu sein.

  • Eine Instanz (instance) (auch Objekt (object) genannt) ist ein konkretes Beispiel, das aus dieser Blaupause erstellt wurde. Wenn Sie alice = Student("Alice Johnson", "S12345", 3.8) erstellen, erzeugen Sie eine konkrete Studierendeninstanz mit Alices spezifischen Daten.

Student-Klasse
Blaupause

alice-Instanz
Name: Alice Johnson
ID: S12345
GPA: 3.8

bob-Instanz
Name: Bob Smith
ID: S12346
GPA: 3.5

carol-Instanz
Name: Carol Davis
ID: S12347
GPA: 3.9

Sie können so viele Instanzen erstellen, wie Sie von einer einzigen Klasse benötigen – genauso wie ein Architekt eine Blaupause verwenden kann, um viele Häuser zu bauen. Jede Instanz hat ihre eigenen Daten (Alices GPA ist anders als Bobs), aber sie alle teilen sich dieselbe Struktur und dieselben Fähigkeiten, die durch die Klasse definiert sind.

Was Sie in diesem Kapitel lernen werden

Dieses Kapitel führt die Kernkonzepte der objektorientierten Programmierung in Python ein:

  1. Klassen definieren mit dem Schlüsselwort class
  2. Instanzen erstellen und auf ihre Attribute zugreifen
  3. Methoden (methods) hinzufügen, die auf Instanzdaten arbeiten
  4. self verstehen und wie Methoden auf Instanzdaten zugreifen
  5. Instanzen initialisieren mit der Methode __init__
  6. String-Repräsentationen (string representations) steuern mit __str__ und __repr__
  7. Mehrere unabhängige Instanzen erstellen aus derselben Klasse

Am Ende dieses Kapitels werden Sie in der Lage sein, Ihre eigenen benutzerdefinierten Typen zu entwerfen und zu implementieren, die Ihre Programme besser organisiert, wartbarer und ausdrucksstärker machen. Wir bauen in Kapitel 31 mit fortgeschritteneren Eigenschaften und Funktionalitäten von Klassen auf diesen Grundlagen auf und in Kapitel 32 mit Vererbung und Polymorphie.

30.2) Einfache Klassen mit class definieren

Beginnen wir damit, die einfachstmögliche Klasse zu erstellen – eine, die nur einen neuen Typ definiert, ohne zunächst Daten oder Verhalten.

Das Schlüsselwort class

Sie definieren eine Klasse mit dem Schlüsselwort class, gefolgt vom Klassennamen und einem Doppelpunkt:

python
class Student:
    pass  # Leere Klasse fürs Erste
 
# Eine Instanz erstellen
alice = Student()
print(alice)  # Output: <__main__.Student object at 0x...>
print(type(alice))  # Output: <class '__main__.Student'>

Selbst diese minimale Klasse ist nützlich – sie erstellt einen neuen Typ namens Student. Wenn Sie eine Instanz mit alice = Student() erstellen, erzeugt Python ein neues Objekt vom Typ Student. Die Ausgabe zeigt, dass alice tatsächlich ein Student-Objekt ist, auch wenn es noch nichts Interessantes tut.

Namenskonventionen für Klassen

Python-Klassennamen folgen einer spezifischen Konvention namens CapWords oder PascalCase: Jedes Wort beginnt mit einem Großbuchstaben, ohne Unterstriche zwischen den Wörtern:

python
class BankAccount:      # Gut: CapWords
    pass
 
class ProductInventory:  # Gut: CapWords
    pass
 
class HTTPRequest:      # Gut: Akronyme sind in Großbuchstaben
    pass
 
# Vermeiden Sie diese Stile für Klassen:
# class bank_account:   # Wrong: snake_case is for functions/variables
# class bankaccount:    # Wrong: hard to read
# class BANKACCOUNT:    # Wrong: ALL_CAPS is for constants

Diese Konvention hilft dabei, Klassen von Funktionen und Variablen zu unterscheiden (die snake_case verwenden), wenn man Code liest.

Instanzen erstellen

Eine Instanz aus einer Klasse zu erstellen sieht aus wie ein Funktionsaufruf – Sie verwenden den Klassennamen, gefolgt von Klammern:

python
class Product:
    pass
 
# Drei verschiedene Produktinstanzen erstellen
item1 = Product()
item2 = Product()
item3 = Product()
 
# Jede Instanz ist ein separates Objekt
print(item1)  # Output: <__main__.Product object at 0x...>
print(item2)  # Output: <__main__.Product object at 0x...>
print(item3)  # Output: <__main__.Product object at 0x...>
 
# Es sind unterschiedliche Objekte, obwohl sie denselben Typ haben
print(item1 is item2)  # Output: False
print(type(item1) is type(item2))  # Output: True

Jeder Aufruf von Product() erstellt eine neue, unabhängige Instanz. Die Speicheradressen (der 0x...-Teil) sind verschieden und bestätigen, dass es sich um separate Objekte im Speicher handelt.

Warum mit leeren Klassen anfangen?

Sie fragen sich vielleicht, warum wir mit Klassen beginnen, die nichts tun. Dafür gibt es zwei Gründe:

  1. Konzeptionelle Klarheit: Zu verstehen, dass eine Klasse einfach nur ein neuer Typ ist – getrennt von ihren Daten und ihrem Verhalten – hilft Ihnen, das grundlegende Konzept zu erfassen, bevor Komplexität hinzukommt.

  2. Praktischer Nutzen: Selbst leere Klassen können als Marker oder Platzhalter nützlich sein. Zum Beispiel könnten Sie eigene Exception-Typen definieren:

python
class InvalidGradeError:
    pass
 
class StudentNotFoundError:
    pass
 
# Diese leeren Klassen dienen als unterschiedliche Fehlertypen

Allerdings sind leere Klassen in echtem Code selten. Fügen wir einige Daten hinzu, um unsere Klassen nützlich zu machen.

30.3) Instanzen erstellen und auf Attribute zugreifen

Klassen werden nützlich, wenn sie Daten enthalten. In Python können Sie Attribute (attributes) (Daten, die an eine Instanz gebunden sind) jederzeit hinzufügen, indem Sie ihnen einfach Werte zuweisen.

Attribute zu Instanzen hinzufügen

Sie können Attribute zu einer Instanz mit Punktnotation hinzufügen:

python
class Student:
    pass
 
# Eine Instanz erstellen
alice = Student()
 
# Attribute hinzufügen
alice.name = "Alice Johnson"
alice.student_id = "S12345"
alice.gpa = 3.8
 
# Auf Attribute zugreifen
print(alice.name)        # Output: Alice Johnson
print(alice.student_id)  # Output: S12345
print(alice.gpa)         # Output: 3.8

Der Punkt-Operator (.) greift auf Attribute zu: alice.name bedeutet „hole das Attribut name des Objekts alice“. Das ist dieselbe Syntax, die Sie bereits bei Strings (wie text.upper()) und Listen (wie numbers.append(5)) verwendet haben – dort greifen Sie auf Methoden und Attribute dieser Objekte zu.

Jede Instanz hat ihre eigenen Attribute

Verschiedene Instanzen derselben Klasse haben unabhängige Attribute:

python
class Student:
    pass
 
# Zwei Studierende erstellen
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.5
 
# Jede Instanz hat ihre eigenen Daten
print(alice.name)  # Output: Alice Johnson
print(bob.name)    # Output: Bob Smith
 
# Eine Änderung wirkt sich nicht auf die andere aus
alice.gpa = 3.9
print(alice.gpa)  # Output: 3.9
print(bob.gpa)    # Output: 3.5 (unchanged)

Diese Unabhängigkeit ist entscheidend: alice und bob sind separate Objekte mit separaten Daten. Wenn Sie alice.gpa ändern, beeinflusst das nicht bob.gpa.

Attribute können jeden Typ haben

Attribute sind nicht auf einfache Typen beschränkt – sie können jeden Python-Wert enthalten:

python
class Student:
    pass
 
student = Student()
student.name = "Carol Davis"
student.grades = [95, 88, 92, 90]  # Listenattribut
student.contact = {                 # Dictionary-Attribut
    "email": "carol@example.com",
    "phone": "555-0123"
}
student.is_active = True            # Boolean-Attribut
 
# Auf verschachtelte Daten zugreifen
print(student.grades[0])           # Output: 95
print(student.contact["email"])    # Output: carol@example.com

Diese Flexibilität ermöglicht es Ihnen, komplexe reale Entitäten mit reichhaltigen Datenstrukturen zu modellieren.

Zugriff auf nicht vorhandene Attribute

Wenn Sie versuchen, auf ein Attribut zuzugreifen, das nicht existiert, wird ein AttributeError ausgelöst:

python
class Student:
    pass
 
student = Student()
student.name = "David Lee"
 
print(student.name)  # Output: David Lee
# print(student.age)  # AttributeError: 'Student' object has no attribute 'age'

Dieser Fehler ist hilfreich – er fängt Tippfehler und Logikfehler ab, bei denen Sie erwarten, dass ein Attribut existiert, es aber nicht tut.

Das Problem mit manueller Attributzuweisung

Obwohl Sie Attribute nach dem Erstellen einer Instanz manuell hinzufügen können, hat dieser Ansatz ernste Nachteile:

python
class Student:
    pass
 
# Man vergisst leicht Attribute oder vertippt sie
alice = Student()
alice.name = "Alice Johnson"
alice.student_id = "S12345"
# Forgot to set gpa!
 
bob = Student()
bob.name = "Bob Smith"
bob.stuent_id = "S12346"  # Typo: stuent instead of student
bob.gpa = 3.5
 
# Jetzt fehlt alice gpa, und bob hat einen Tippfehler
# print(alice.gpa)  # AttributeError
# print(bob.student_id)  # AttributeError

Das ist fehleranfällig und mühsam. Sie brauchen eine Möglichkeit sicherzustellen, dass jede Instanz mit den richtigen Attributen startet. Genau hier kommt die Methode __init__ ins Spiel, die wir in Abschnitt 30.5 behandeln. Aber zuerst lernen wir etwas über Methoden – Funktionen, die zu einer Klasse gehören.

30.4) Instanzmethoden hinzufügen: self verstehen

Methoden sind Funktionen (functions), die innerhalb einer Klasse definiert sind und auf Instanzdaten arbeiten. Sie geben Ihren Klassen Verhalten, nicht nur Daten.

Eine einfache Methode definieren

Fügen wir unserer Student-Klasse eine Methode hinzu:

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
# Eine Instanz erstellen und Attribute hinzufügen
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
# Die Methode aufrufen
alice.display_info()  # Output: Alice Johnson - GPA: 3.8

Die Methode display_info wird innerhalb der Klasse mit def definiert, genau wie normale Funktionen. Der entscheidende Unterschied ist der erste Parameter: self.

self verstehen

Der Parameter self ist die Art, wie eine Methode auf die konkrete Instanz zugreift, auf der sie arbeitet. Wenn Sie alice.display_info() aufrufen, übergibt Python automatisch alice als erstes Argument an die Methode. Innerhalb der Methode bezieht sich self auf alice, sodass self.name auf alice.name zugreift und self.gpa auf alice.gpa.

Hier sehen Sie, was im Hintergrund passiert:

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
# Diese beiden Aufrufe sind gleichwertig:
alice.display_info()           # Normale Art
Student.display_info(alice)    # Was Python tatsächlich macht
 
# Beide geben aus: Alice Johnson - GPA: 3.8

Wenn Sie alice.display_info() schreiben, übersetzt Python das zu Student.display_info(alice). Die Instanz (alice) wird innerhalb der Methode zum Parameter self.

Warum „self“?

Der Name self ist eine Konvention, kein Schlüsselwort. Technisch könnten Sie jeden Namen verwenden:

python
class Student:
    def display_info(this):  # Funktioniert, aber machen Sie das nicht
        print(f"{this.name} - GPA: {this.gpa}")

Verwenden Sie jedoch immer self. Es ist eine universelle Python-Konvention, die Ihren Code für andere Python-Programmierer lesbar macht. Einen anderen Namen zu verwenden, verwirrt Leser und verstößt gegen Community-Standards.

Methoden mit mehreren Instanzen

Die Stärke von self wird deutlich, wenn Sie mehrere Instanzen haben:

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
# Zwei Studierende erstellen
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.5
 
# Dieselbe Methode, unterschiedliche Daten
alice.display_info()  # Output: Alice Johnson - GPA: 3.8
bob.display_info()    # Output: Bob Smith - GPA: 3.5

Wenn Sie alice.display_info() aufrufen, ist self gleich alice. Wenn Sie bob.display_info() aufrufen, ist self gleich bob. Derselbe Methodencode funktioniert für jede Instanz, weil self sich an die Instanz anpasst, die die Methode aufgerufen hat.

alice.display_info

self = alice

bob.display_info

self = bob

Zugriff auf alice.name
alice.gpa

Zugriff auf bob.name
bob.gpa

Methoden können zusätzliche Parameter annehmen

Methoden können neben self weitere Parameter akzeptieren:

python
class Student:
    def update_gpa(self, new_gpa):
        self.gpa = new_gpa
        print(f"Updated {self.name}'s GPA to {self.gpa}")
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
alice.update_gpa(3.9)  # Output: Updated Alice Johnson's GPA to 3.9
print(alice.gpa)       # Output: 3.9

Wenn Sie alice.update_gpa(3.9) aufrufen, übergibt Python alice als self und 3.9 als new_gpa. Die Methodensignatur ist def update_gpa(self, new_gpa), aber beim Aufruf übergeben Sie nur ein Argument – Python übernimmt self automatisch.

Methoden können Werte zurückgeben

Methoden können Werte zurückgeben, genau wie normale Funktionen:

python
class Student:
    def is_honors(self):
        return self.gpa >= 3.5
    
    def get_status(self):
        if self.is_honors():
            return "Honors Student"
        else:
            return "Regular Student"
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.2
 
print(alice.get_status())  # Output: Honors Student
print(bob.get_status())    # Output: Regular Student

Beachten Sie, wie get_status eine andere Methode (is_honors) mit self.is_honors() aufruft. Methoden können andere Methoden auf derselben Instanz aufrufen.

Methoden vs. Funktionen: Wann man welches verwendet

Sie fragen sich vielleicht, wann Sie eine Methode gegenüber einer eigenständigen Funktion verwenden sollten. Hier ist die Richtlinie:

Verwenden Sie eine Methode, wenn die Operation:

  • Zugriff auf Instanzdaten benötigt (self.name, self.gpa usw.)
  • logisch zum Typ gehört (es ist etwas, das ein Student tut oder ist)
  • den Zustand der Instanz verändert

Verwenden Sie eine eigenständige Funktion, wenn die Operation:

  • keine Instanzdaten benötigt
  • mit mehreren Typen arbeitet
  • ein allgemeines Hilfswerkzeug ist
python
class Student:
    # Methode: braucht Instanzdaten
    def is_honors(self):
        return self.gpa >= 3.5
 
# Funktion: allgemeines Hilfswerkzeug, funktioniert mit jedem GPA-Wert
def calculate_letter_grade(gpa):
    if gpa >= 3.7:
        return "A"
    elif gpa >= 3.0:
        return "B"
    elif gpa >= 2.0:
        return "C"
    else:
        return "D"
 
alice = Student()
alice.gpa = 3.8
 
# Verwenden Sie die Methode für instanzspezifische Prüfungen
print(alice.is_honors())  # Output: True
 
# Verwenden Sie die Funktion für allgemeine Berechnungen
print(calculate_letter_grade(alice.gpa))  # Output: A
print(calculate_letter_grade(2.5))        # Output: C

Häufige Methodenmuster

Hier sind einige häufige Muster, die Sie oft verwenden werden:

Getter-Methoden (abgeleitete Informationen abrufen):

python
class Student:
    def get_full_info(self):
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa}"

Setter-Methoden (Attribute mit Validierung ändern):

python
class Student:
    def set_gpa(self, new_gpa):
        if 0.0 <= new_gpa <= 4.0:
            self.gpa = new_gpa
        else:
            print("Invalid GPA: must be between 0.0 and 4.0")

Query-Methoden (Ja/Nein-Fragen beantworten):

python
class Student:
    def is_honors(self):
        return self.gpa >= 3.5
    
    def is_failing(self):
        return self.gpa < 2.0

Action-Methoden (Operationen ausführen):

python
class Student:
    def add_grade(self, grade):
        self.grades.append(grade)
        # GPA basierend auf allen grades neu berechnen
        self.gpa = sum(self.grades) / len(self.grades)

30.5) Instanzen mit __init__ initialisieren

Attribute nach dem Erstellen einer Instanz manuell zu setzen ist mühsam und fehleranfällig. Die Methode __init__ löst dieses Problem, indem sie Ihnen erlaubt, Instanzen beim Erstellen mit Daten zu initialisieren.

Die Methode __init__

Die Methode __init__ (ausgesprochen „dunder init“ oder „init“) ist eine spezielle Methode, die Python automatisch aufruft, wenn Sie eine neue Instanz erstellen:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# Instanzen mit Anfangsdaten erstellen
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346", 3.5)
 
print(alice.name)  # Output: Alice Johnson
print(bob.gpa)     # Output: 3.5

Wenn Sie Student("Alice Johnson", "S12345", 3.8) schreiben, macht Python:

  1. Es erstellt eine neue leere Student-Instanz
  2. Es ruft __init__ mit dieser Instanz als self und Ihren Argumenten auf
  3. Es gibt die initialisierte Instanz zurück

Die Methode __init__ gibt keinen Wert explizit zurück – sie verändert die Instanz „in place“, indem sie ihre Attribute setzt. Wenn Sie versuchen, einen Wert aus __init__ zurückzugeben, löst Python einen TypeError aus.

python
class Student:
    def __init__(self, name):
        self.name = name
        # Geben Sie aus __init__ nichts zurück
        # return self  # Wrong! TypeError: __init__() should return None, not 'Student'

Wie __init__ funktioniert

Zerlegen wir Schritt für Schritt, was passiert:

python
class Student:
    def __init__(self, name, student_id, gpa):
        print(f"Initializing student: {name}")
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
        print(f"Initialization complete")
 
alice = Student("Alice Johnson", "S12345", 3.8)
# Output:
# Initializing student: Alice Johnson
# Initialization complete
 
print(alice.name)  # Output: Alice Johnson

Die Parameter nach self (name, student_id, gpa) werden zu erforderlichen Argumenten beim Erstellen einer Instanz. Wenn Sie sie nicht angeben, löst Python einen TypeError aus:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# student = Student()  # TypeError: __init__() missing 3 required positional arguments
# student = Student("Alice")  # TypeError: __init__() missing 2 required positional arguments
student = Student("Alice Johnson", "S12345", 3.8)  # Correct

Das ist viel besser als manuelle Attributzuweisung – Python erzwingt, dass jede Instanz mit den erforderlichen Daten startet.

Standardparameterwerte in __init__

Sie können Standardparameterwerte in __init__ verwenden, genau wie bei normalen Funktionen:

python
class Student:
    def __init__(self, name, student_id, gpa=0.0):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# GPA ist optional und hat den Standardwert 0.0
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346")  # Verwendet default gpa=0.0
 
print(alice.gpa)  # Output: 3.8
print(bob.gpa)    # Output: 0.0

Das ist nützlich für Attribute, die sinnvolle Standardwerte haben, aber bei Bedarf angepasst werden können.

Validierung in __init__

Sie können Eingaben in __init__ validieren, um sicherzustellen, dass Instanzen in einem gültigen Zustand starten:

python
class Student:
    def __init__(self, name, student_id, gpa):
        if not name:
            print("Error: Name cannot be empty")
            self.name = "Unknown"
        else:
            self.name = name
        
        self.student_id = student_id
        
        if 0.0 <= gpa <= 4.0:
            self.gpa = gpa
        else:
            print(f"Warning: Invalid GPA {gpa}, setting to 0.0")
            self.gpa = 0.0
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice.gpa)  # Output: 3.8
 
bob = Student("", "S12346", 5.0)
# Output:
# Error: Name cannot be empty
# Warning: Invalid GPA 5.0, setting to 0.0
print(bob.name)  # Output: Unknown
print(bob.gpa)   # Output: 0.0

Das stellt sicher, dass selbst dann, wenn jemand ungültige Daten übergibt, die Instanz in einem vernünftigen Zustand landet.

30.6) String-Repräsentationen mit __str__ und __repr__

Wenn Sie eine Instanz mit print() ausgeben oder sie in der interaktiven Shell ansehen, muss Python sie in einen String umwandeln. Standardmäßig erhalten Sie etwas wenig Hilfreiches:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice)  # Output: <__main__.Student object at 0x...>

Die Standardausgabe zeigt den Klassennamen und die Speicheradresse, aber nichts über Alices tatsächliche Daten. Sie können das mit den speziellen Methoden __str__ und __repr__ anpassen.

Die Methode __str__

Die Methode __str__ definiert, wie Ihre Instanzen von print() und str() in Strings umgewandelt werden:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __str__(self):
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa}"
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice)  # Output: Alice Johnson (S12345) - GPA: 3.8
print(str(alice))  # Output: Alice Johnson (S12345) - GPA: 3.8

Die Methode __str__ sollte einen String zurückgeben, der für Endnutzer lesbar und informativ ist. Betrachten Sie sie als die „freundliche“ Darstellung.

Die Methode __repr__

Die Methode __repr__ definiert die „offizielle“ String-Darstellung Ihrer Instanzen, die vom REPL und repr() verwendet wird:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __repr__(self):
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(repr(alice))  # Output: Student('Alice Johnson', 'S12345', 3.8)

Im REPL:

python
>>> alice = Student("Alice Johnson", "S12345", 3.8)
>>> alice
Student('Alice Johnson', 'S12345', 3.8)

Die Methode __repr__ sollte einen String zurückgeben, der wie gültiger Python-Code aussieht, um das Objekt neu zu erstellen. Betrachten Sie sie als die „Entwickler“-Darstellung – sie sollte eindeutig sein und beim Debugging nützlich.

Sowohl __str__ als auch __repr__ verwenden

Sie können beide Methoden für unterschiedliche Zwecke definieren:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __str__(self):
        # Freundliches, gut lesbares Format
        return f"{self.name} - GPA: {self.gpa}"
    
    def __repr__(self):
        # Eindeutiges, code-ähnliches Format
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
alice = Student("Alice Johnson", "S12345", 3.8)
 
print(alice)        # Verwendet __str__
# Output: Alice Johnson - GPA: 3.8
 
print(repr(alice))  # Verwendet __repr__
# Output: Student('Alice Johnson', 'S12345', 3.8)

Im REPL:

python
>>> alice = Student("Alice Johnson", "S12345", 3.8)
>>> alice  # Uses __repr__
Student('Alice Johnson', 'S12345', 3.8)
>>> print(alice)  # Uses __str__
Alice Johnson - GPA: 3.8

Wann man welche Methode definieren sollte

Hier ist die Richtlinie:

  • Definieren Sie immer __repr__: Es wird vom REPL und von Debugging-Tools verwendet. Wenn Sie nur eine definieren, dann diese.
  • Definieren Sie __str__, wenn Sie ein benutzerfreundliches Format benötigen: Wenn Ihre Klasse für Endnutzer ausgegeben werden soll, stellen Sie ein lesbares __str__ bereit.
  • Wenn Sie nur __repr__ definieren: Python verwendet es für repr(), und str() fällt ebenfalls auf __repr__ zurück (also verwendet auch print() es).
  • Wenn Sie nur __str__ definieren: print() verwendet __str__, aber repr() und das REPL verwenden das Standard-__repr__ (zeigt die Speicheradresse). Deshalb ist das Definieren von __repr__ in der Regel wichtiger.
python
# Nur __repr__ definiert
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price
    
    def __repr__(self):
        return f"Product('{self.name}', {self.price})"
 
item = Product("Laptop", 999.99)
print(item)        # Verwendet __repr__ als Fallback
# Output: Product('Laptop', 999.99)
print(repr(item))  # Verwendet __repr__
# Output: Product('Laptop', 999.99)

String-Repräsentation in Collections

Wenn Instanzen in Collections (Listen, Dicts usw.) enthalten sind, verwendet Python zur Darstellung __repr__, nicht __str__:

python
class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
    
    def __str__(self):
        return f"{self.name}: {self.gpa}"
    
    def __repr__(self):
        return f"Student('{self.name}', {self.gpa})"
 
students = [
    Student("Alice", 3.8),
    Student("Bob", 3.5),
    Student("Carol", 3.9)
]
 
# Das Ausgeben der Liste verwendet __repr__ für jeden student
print(students)
# Output: [Student('Alice', 3.8), Student('Bob', 3.5), Student('Carol', 3.9)]
 
# Das Ausgeben einzelner students verwendet __str__
for student in students:
    print(student)
# Output:
# Alice: 3.8
# Bob: 3.5
# Carol: 3.9

Deshalb sollte __repr__ eindeutig sein – es hilft Ihnen zu verstehen, was in Ihren Datenstrukturen steckt, während Sie debuggen. Wenn Sie eine Liste ausgeben, ruft Python im Wesentlichen repr() für jedes Element auf, um die Struktur klar darzustellen.

30.7) Mehrere unabhängige Instanzen erstellen

Einer der mächtigsten Aspekte von Klassen ist, dass Sie viele unabhängige Instanzen erstellen können, jede mit ihren eigenen Daten. Sehen wir uns das im Detail an.

Jede Instanz hat ihre eigenen Daten

Wenn Sie mehrere Instanzen aus derselben Klasse erstellen, verwaltet jede ihre eigenen separaten Attribute:

python
class BankAccount:
    def __init__(self, account_number, holder_name, balance=0.0):
        self.account_number = account_number
        self.holder_name = holder_name
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
        print(f"Deposited ${amount:.2f}. New balance: ${self.balance:.2f}")
    
    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            print(f"Withdrew ${amount:.2f}. New balance: ${self.balance:.2f}")
            return True
        else:
            print(f"Insufficient funds. Balance: ${self.balance:.2f}")
            return False
    
    def __str__(self):
        return f"{self.holder_name}'s account ({self.account_number}): ${self.balance:.2f}"
 
# Drei unabhängige Konten erstellen
alice_account = BankAccount("ACC-001", "Alice Johnson", 1000.0)
bob_account = BankAccount("ACC-002", "Bob Smith", 500.0)
carol_account = BankAccount("ACC-003", "Carol Davis", 2000.0)
 
# Operationen an einem Konto beeinflussen andere nicht
alice_account.deposit(500)
# Output: Deposited $500.00. New balance: $1500.00
 
bob_account.withdraw(200)
# Output: Withdrew $200.00. New balance: $300.00
 
# Jedes Konto behält seinen eigenen Kontostand
print(alice_account)  # Output: Alice Johnson's account (ACC-001): $1500.00
print(bob_account)    # Output: Bob Smith's account (ACC-002): $300.00
print(carol_account)  # Output: Carol Davis's account (ACC-003): $2000.00

Diese Unabhängigkeit ist grundlegend für objektorientierte Programmierung. Jede Instanz ist eine separate Einheit mit ihrem eigenen Zustand.

Instanzen in Collections

Sie können Instanzen in Listen, Dictionaries oder jeder anderen Collection speichern:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def is_honors(self):
        return self.gpa >= 3.5
    
    def __repr__(self):
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
# Eine Liste von Studierenden erstellen
students = [
    Student("Alice Johnson", "S12345", 3.8),
    Student("Bob Smith", "S12346", 3.2),
    Student("Carol Davis", "S12347", 3.9),
    Student("David Lee", "S12348", 3.4)
]
 
# Alle Studierenden mit Honors-Status finden
honors_students = []
for student in students:
    if student.is_honors():
        honors_students.append(student)
 
print("Honors students:")
for student in honors_students:
    print(f"  {student.name}: {student.gpa}")
# Output:
# Honors students:
#   Alice Johnson: 3.8
#   Carol Davis: 3.9
 
# Durchschnittlichen GPA berechnen
total_gpa = sum(student.gpa for student in students)
average_gpa = total_gpa / len(students)
print(f"Average GPA: {average_gpa:.2f}")  # Output: Average GPA: 3.58

Das ist ein häufiges Muster: mehrere Instanzen erstellen, sie in einer Collection speichern und sie dann mit Schleifen und Comprehensions verarbeiten.

Instanzen können auf andere Instanzen verweisen

Instanzen können Attribute haben, die auf andere Instanzen verweisen, wodurch Beziehungen zwischen Objekten entstehen:

python
class Course:
    def __init__(self, course_code, course_name):
        self.course_code = course_code
        self.course_name = course_name
    
    def __str__(self):
        return f"{self.course_code}: {self.course_name}"
 
class Student:
    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.courses = []  # Liste von Course-Instanzen
    
    def enroll(self, course):
        self.courses.append(course)
        print(f"{self.name} enrolled in {course.course_name}")
    
    def list_courses(self):
        print(f"{self.name}'s courses:")
        for course in self.courses:
            print(f"  {course}")
 
# Kurse erstellen
python_course = Course("CS101", "Introduction to Python")
data_course = Course("CS102", "Data Structures")
web_course = Course("CS103", "Web Development")
 
# Studierende erstellen und sie in Kurse einschreiben
alice = Student("Alice Johnson", "S12345")
alice.enroll(python_course)
alice.enroll(data_course)
# Output:
# Alice Johnson enrolled in Introduction to Python
# Alice Johnson enrolled in Data Structures
 
bob = Student("Bob Smith", "S12346")
bob.enroll(python_course)
bob.enroll(web_course)
# Output:
# Bob Smith enrolled in Introduction to Python
# Bob Smith enrolled in Web Development
 
# Kurse jedes Studierenden auflisten
alice.list_courses()
# Output:
# Alice Johnson's courses:
#   CS101: Introduction to Python
#   CS102: Data Structures
 
bob.list_courses()
# Output:
# Bob Smith's courses:
#   CS101: Introduction to Python
#   CS103: Web Development

Beachten Sie, dass sowohl Alice als auch Bob in python_course eingeschrieben sind – sie verweisen auf dieselbe Course-Instanz. Das modelliert die Beziehung aus der realen Welt, in der mehrere Studierende denselben Kurs belegen können.

Instanzidentität und Gleichheit

Jede Instanz ist ein eindeutiges Objekt, auch wenn sie dieselben Daten wie eine andere Instanz hat:

python
class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
 
alice1 = Student("Alice", 3.8)
alice2 = Student("Alice", 3.8)
 
# Unterschiedliche Objekte, selbst mit identischen Daten
print(alice1 is alice2)  # Output: False
print(id(alice1) == id(alice2))  # Output: False

Standardmäßig verhält sich == ebenfalls so, als würde es die Identität prüfen (also ob es dasselbe Objekt ist), nicht ob die Objekte dieselben Daten haben. In Kapitel 31 lernen wir, wie man den Gleichheitsvergleich mit der speziellen Methode __eq__ anpasst.


Dieses Kapitel hat Sie in die Grundlagen der objektorientierten Programmierung in Python eingeführt. Sie haben gelernt, wie man Klassen definiert, Instanzen erstellt, Methoden hinzufügt, Instanzen mit __init__ initialisiert, String-Repräsentationen steuert und mit mehreren unabhängigen Instanzen arbeitet. Diese Konzepte bilden die Grundlage für fortgeschrittenere OOP-Features, die wir in den Kapiteln 31 und 32 erkunden werden.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai