Klassenmerkmale in Python verstehen

PythonBeginner
Jetzt üben

Einführung

In diesem Lab erwerben Sie ein praktisches Verständnis der wichtigsten objektorientierten Programmierkonzepte (OOP) in Python. Wir beginnen mit der Kapselung (Encapsulation), lernen, wie Daten und Methoden innerhalb einer Klasse gebündelt und der Zugriff auf Daten mithilfe privater Attribute gesteuert werden.

Anschließend implementieren Sie die Vererbung (Inheritance), um Beziehungen zwischen Klassen aufzubauen, was die Wiederverwendung von Code fördert. Wir werden auch die Polymorphie (Polymorphism) untersuchen, die es ermöglicht, Objekte verschiedener Klassen einheitlich zu behandeln. Schließlich verwenden Sie die super()-Methode, um Methoden aus einer Elternklasse effektiv aufzurufen, und üben die Mehrfachvererbung (Multiple Inheritance), um zu sehen, wie eine Klasse von mehreren Elternklassen erben kann.

Dies ist ein geführter Laborkurs (Guided Lab), der Schritt-für-Schritt-Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Laborkurs für **Anfänger** (beginner) mit einer Abschlussquote von **100%** ist. Er hat eine **100%** positive Bewertungsrate von Lernenden erhalten.

Kapselung mit Basisklassen erkunden

In diesem Schritt untersuchen wir die Kapselung (Encapsulation), ein zentrales OOP-Prinzip. Kapselung beinhaltet das Bündeln von Daten (Attributen) und den Methoden, die auf diesen Daten operieren, in einer einzigen Einheit, der Klasse. Sie beschränkt außerdem den direkten Zugriff auf den internen Zustand eines Objekts, was hilft, versehentliche Datenänderungen zu verhindern.

In Python verwenden wir eine Namenskonvention, um anzuzeigen, dass ein Attribut „privat“ ist. Das Voranstellen eines einzelnen Unterstrichs (z. B. _name) signalisiert, dass das Attribut für den internen Gebrauch bestimmt ist. Obwohl dies nicht strikt erzwungen wird, ist es eine starke Konvention, die Entwickler respektieren.

Wir beginnen damit, zwei separate Klassen, Dog und Cat, zu erstellen, um zu sehen, wie sie strukturiert werden können.

Suchen Sie zuerst im Dateiexplorer auf der linken Seite der WebIDE nach der Datei animal_classes.py. Öffnen Sie diese und fügen Sie den folgenden Python-Code hinzu. Dieser Code definiert eine Dog-Klasse und eine Cat-Klasse, die jeweils ein privates _name-Attribut und Methoden zur Interaktion damit besitzen.

## File: animal_classes.py

class Dog:
    def __init__(self, name):
        ## Ein einzelner Unterstrich als Präfix kennzeichnet ein "privates" Attribut.
        self._name = name

    ## Öffentliche Methode, um den Wert des privaten Attributs abzurufen.
    def get_name(self):
        return self._name

    ## Öffentliche Methode, um den Wert des privaten Attributs festzulegen.
    def set_name(self, value):
        self._name = value

    def say(self):
        print(f"{self._name} says: Woof!")

class Cat:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

    def say(self):
        print(f"{self._name} says: Meow!")

## Dieser Block wird nur ausgeführt, wenn das Skript direkt gestartet wird.
if __name__ == "__main__":
    ## Erstellen einer Instanz der Dog-Klasse
    my_dog = Dog("Buddy")
    print(f"Initial dog name: {my_dog.get_name()}")

    ## Ändern des Hundenamens mithilfe der Setter-Methode
    my_dog.set_name("Rocky")
    print(f"New dog name: {my_dog.get_name()}")
    my_dog.say()

    print("-" * 20)

    ## Erstellen einer Instanz der Cat-Klasse
    my_cat = Cat("Whiskers")
    print(f"Cat name: {my_cat.get_name()}")
    my_cat.say()

Nachdem Sie den Code hinzugefügt haben, speichern Sie die Datei.

Führen wir nun das Skript aus, um die Kapselung in Aktion zu sehen. Öffnen Sie das Terminal in der WebIDE und führen Sie den folgenden Befehl aus:

python animal_classes.py

Sie sehen die folgende Ausgabe, die zeigt, dass wir über unsere öffentlichen Methoden get_name und set_name mit dem privaten Attribut _name interagieren.

Initial dog name: Buddy
New dog name: Rocky
Rocky says: Woof!
--------------------
Cat name: Whiskers
Whiskers says: Meow!

Vererbung und Polymorphismus implementieren

Im vorherigen Schritt ist Ihnen möglicherweise aufgefallen, dass die Klassen Dog und Cat viel identischen Code aufweisen (__init__, get_name, set_name). Dies ist eine perfekte Gelegenheit, die Vererbung (Inheritance) zu nutzen. Vererbung ermöglicht es einer neuen Klasse (dem Kind oder der Unterklasse), Attribute und Methoden von einer bestehenden Klasse (dem Elternteil oder der Superklasse) zu erben, was die Wiederverwendung von Code fördert.

Wir führen auch die Polymorphie (Polymorphism) ein, was „viele Formen“ bedeutet. In OOP bezieht es sich auf die Fähigkeit verschiedener Klassen, auf denselben Methodenaufruf auf ihre eigene, einzigartige Weise zu reagieren.

Lassen Sie uns unseren Code refaktorieren. Wir erstellen eine Elternklasse Animal, um den gemeinsamen Code aufzunehmen, und lassen Dog und Cat von ihr erben. Die Methode say, die für jede Klasse unterschiedlich ist, wird die Polymorphie demonstrieren.

Öffnen Sie die Datei animal_classes.py und ersetzen Sie deren gesamten Inhalt durch den folgenden Code:

## File: animal_classes.py

class Animal:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

    def say(self):
        print(f"{self._name} makes a generic animal sound.")

## Dog erbt von Animal
class Dog(Animal):
    ## Dies überschreibt die say()-Methode aus der Animal-Klasse
    def say(self):
        print(f"{self._name} says: Woof!")

## Cat erbt von Animal
class Cat(Animal):
    ## Dies überschreibt ebenfalls die say()-Methode
    def say(self):
        print(f"{self._name} says: Meow!")

def make_animal_speak(animal_instance):
    animal_instance.say()

if __name__ == "__main__":
    generic_animal = Animal("Creature")
    my_dog = Dog("Buddy")
    my_cat = Cat("Whiskers")

    print("--- Calling say() on each object ---")
    generic_animal.say()
    my_dog.say()
    my_cat.say()

    print("\n--- Demonstrating Polymorphism ---")
    make_animal_speak(generic_animal)
    make_animal_speak(my_dog)
    make_animal_speak(my_cat)

Speichern Sie die Datei. Beachten Sie, wie die Klassen Dog und Cat nun viel einfacher sind. Sie erben die Methoden __init__, get_name und set_name von Animal. Jede Klasse stellt ihre eigene Version der Methode say bereit, was ein Beispiel für das Methodenüberschreiben (Method Overriding) ist.

Führen Sie nun das aktualisierte Skript im Terminal aus:

python animal_classes.py

Die Ausgabe wird sein:

--- Calling say() on each object ---
Creature makes a generic animal sound.
Buddy says: Woof!
Whiskers says: Meow!

--- Demonstrating Polymorphism ---
Creature makes a generic animal sound.
Buddy says: Woof!
Whiskers says: Meow!

Die Funktion make_animal_speak akzeptiert jedes Objekt, das eine say-Methode besitzt. Obwohl wir ihr verschiedene Objekttypen (Animal, Dog, Cat) übergeben, funktioniert sie korrekt, da jedes Objekt weiß, wie es die say-Aktion auf seine eigene Weise ausführen muss. Das ist die Stärke der Polymorphie.

Die super()-Methode zur Erweiterung der Funktionalität nutzen

Wenn eine Kindklasse eine Methode ihrer Elternklasse überschreibt, muss sie diese Methode manchmal erweitern und nicht nur ersetzen. Die super()-Funktion bietet eine Möglichkeit, die Methode der Elternklasse innerhalb der Kindklasse aufzurufen.

Dies ist bei der __init__-Methode sehr üblich. Eine Kindklasse muss oft ihre eigenen Initialisierungsschritte zusätzlich zu der vom Elternteil durchgeführten Initialisierung ausführen.

Fügen wir unseren Klassen Dog und Cat eindeutige Attribute hinzu. Der Dog erhält ein age (Alter) und die Cat erhält eine color (Farbe). Wir verwenden super(), um sicherzustellen, dass die __init__-Methode der Elternklasse Animal weiterhin aufgerufen wird, um den _name festzulegen.

Ändern Sie die Datei animal_classes.py, indem Sie deren Inhalt durch den folgenden Code ersetzen:

## File: animal_classes.py

class Animal:
    def __init__(self, name):
        print(f"Animal __init__ called for {name}")
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

    def say(self):
        print(f"{self._name} makes a generic animal sound.")

class Dog(Animal):
    def __init__(self, name, age):
        ## Ruft die __init__-Methode der Elternklasse auf, um das 'name'-Attribut zu verarbeiten
        super().__init__(name)
        print("Dog __init__ called")
        self.age = age

    def say(self):
        ## Wir können super() auch verwenden, um die say()-Methode der Elternklasse aufzurufen
        ## super().say()
        print(f"{self._name} says: Woof! I am {self.age} years old.")

class Cat(Animal):
    def __init__(self, name, color):
        ## Ruft die __init__-Methode der Elternklasse auf
        super().__init__(name)
        print("Cat __init__ called")
        self.color = color

    def say(self):
        print(f"{self._name} says: Meow! I have {self.color} fur.")

if __name__ == "__main__":
    my_dog = Dog("Buddy", 5)
    my_dog.say()

    print("-" * 20)

    my_cat = Cat("Whiskers", "black")
    my_cat.say()

Speichern Sie die Datei. In dieser Version rufen Dog.__init__ und Cat.__init__ zuerst super().__init__(name) auf. Dadurch wird der Code in Animal.__init__ ausgeführt, der das Attribut _name setzt. Danach fahren sie mit ihren eigenen spezifischen Initialisierungen fort (self.age = age und self.color = color).

Führen Sie das Skript im Terminal aus:

python animal_classes.py

Die Ausgabe demonstriert die Kette der __init__-Aufrufe und die erweiterten say-Methoden:

Animal __init__ called for Buddy
Dog __init__ called
Buddy says: Woof! I am 5 years old.
--------------------
Animal __init__ called for Whiskers
Cat __init__ called
Whiskers says: Meow! I have black fur.

Mehrfachvererbung üben

Python erlaubt es einer Klasse, von mehr als einer Elternklasse zu erben. Dies wird als Mehrfachvererbung (Multiple Inheritance) bezeichnet. Sie kann ein mächtiges Werkzeug sein, um Funktionalitäten aus verschiedenen Quellen zu vermischen, führt aber auch zu Komplexität, insbesondere bei der Entscheidung Pythons, welche Methode der Elternklassen verwendet werden soll, wenn diese denselben Namen haben.

Diese Suchreihenfolge wird als Method Resolution Order (MRO) bezeichnet. Python verwendet einen Algorithmus namens C3-Linearisierung, um eine konsistente und vorhersagbare MRO zu bestimmen.

Lassen Sie uns dies anhand eines neuen Beispiels untersuchen. Öffnen Sie die Datei multiple_inheritance.py im Datei-Explorer und fügen Sie den folgenden Code hinzu:

## File: multiple_inheritance.py

class ParentA:
    def speak(self):
        print("Speaking from ParentA")

    def common_method(self):
        print("ParentA's common method")

class ParentB:
    def speak(self):
        print("Speaking from ParentB")

    def common_method(self):
        print("ParentB's common method")

## Child erbt von A, dann B
class Child_AB(ParentA, ParentB):
    pass

## Child erbt von B, dann A
class Child_BA(ParentB, ParentA):
    def common_method(self):
        print("Child_BA's own common method")

if __name__ == "__main__":
    child1 = Child_AB()
    child2 = Child_BA()

    print("--- Investigating Child_AB (ParentA, ParentB) ---")
    child1.speak()
    child1.common_method()
    ## Die .mro()-Methode zeigt die Method Resolution Order an
    print("MRO for Child_AB:", [c.__name__ for c in Child_AB.mro()])

    print("\n--- Investigating Child_BA (ParentB, ParentA) ---")
    child2.speak()
    child2.common_method()
    print("MRO for Child_BA:", [c.__name__ for c in Child_BA.mro()])

Speichern Sie die Datei. Hier erbt Child_AB von ParentA und dann von ParentB. Child_BA erbt in umgekehrter Reihenfolge. Wenn eine Methode aufgerufen wird, sucht Python in der Reihenfolge, die durch die MRO festgelegt ist, danach.

Führen Sie das Skript im Terminal aus:

python multiple_inheritance.py

Sie sehen die folgende Ausgabe:

--- Investigating Child_AB (ParentA, ParentB) ---
Speaking from ParentA
ParentA's common method
MRO for Child_AB: ['Child_AB', 'ParentA', 'ParentB', 'object']

--- Investigating Child_BA (ParentB, ParentA) ---
Speaking from ParentB
Child_BA's own common method
MRO for Child_BA: ['Child_BA', 'ParentB', 'ParentA', 'object']

Aus der Ausgabe können Sie beobachten:

  • child1.speak() ruft die Methode von ParentA auf, weil ParentA in der MRO von Child_AB zuerst kommt.
  • child2.speak() ruft die Methode von ParentB auf, weil ParentB in der MRO von Child_BA zuerst kommt.
  • child2.common_method() ruft die Version auf, die direkt in Child_BA definiert ist, da Python diese dort zuerst findet, bevor es die Elternklassen überprüft.

Das Verständnis der MRO ist entscheidend, um das Verhalten in Szenarien mit Mehrfachvererbung vorherzusagen.

Zusammenfassung

In diesem Lab haben Sie praktische Erfahrungen mit vier grundlegenden Konzepten der objektorientierten Programmierung (OOP) in Python gesammelt.

Sie begannen mit der Kapselung (Encapsulation) und lernten, Klassendaten konventionsgemäß durch private Attribute zu schützen und öffentliche Methoden für den Zugriff bereitzustellen. Anschließend haben Sie Ihren Code umstrukturiert, um Vererbung (Inheritance) zu nutzen, indem Sie eine Elternklasse Animal erstellten, um die Code-Duplizierung in den Unterklassen Dog und Cat zu reduzieren.

Bei der Implementierung der Vererbung haben Sie Polymorphismus (Polymorphism) in Aktion gesehen, da Dog- und Cat-Objekte unterschiedlich auf denselben Aufruf der say()-Methode reagierten. Sie lernten, die super()-Methode zu verwenden, um Funktionalität von einer Elternklasse aufzurufen und zu erweitern, insbesondere innerhalb der __init__-Methode. Schließlich haben Sie die Mehrfachvererbung (Multiple Inheritance) und die Bedeutung der Method Resolution Order (MRO) für die Bestimmung aufgerufen wird, welche Elternmethode aufgerufen wird, untersucht.