Objektorientierte Programmierung OOP


Einführung

In all unseren bisherigen Programmen haben wir unser Programm um Funktionen oder Anweisungsblöcke herum entwickelt, in denen Daten manipuliert werden. Dies wird die prozedurorientierte Programmierweise genannt. Es gibt eine andere Weise, sein Programm zu organisieren, die darin besteht, Daten und Funktionalität zu kombinieren und sie in etwas zusammenzupacken, das man ein Objekt nennt. Dies wird das objektorientierte Paradigma der Programmierung genannt. Meistens kann man mit prozedurorientierter Programmierung auskommen, aber wenn man große Programme schreiben will oder eine Lösung haben möchte, die dafür besser geeignet ist, dann sollte man objektorientierte Programmiertechniken verwenden.

Klassen und Objekte sind die beiden Hauptaspekte der objektorientierten Programmierung. Eine Klasse erzeugt einen neuen Datentyp, wobei Objekte die Instanzen der Klasse sind. Man kann dies damit vergleichen, dass man Variablen vom Datentyp int haben kann, was dann mit anderen Worten bedeutet, dass Variablen, die Ganzzahlen speichern, Variablen sind, die Instanzen (Objekte) der Klasse int sind.

Anmerkung für C/C++/Java/C#-Programmierer

Beachten Sie, dass in Python sogar Ganzzahlen als Objekte (der Klasse int) behandelt werden. Dies ist anders als C++ und Java (vor Version 1.5), wo Ganzzahlen primitive eingebaute Datentypen sind. Siehe help(int) für weitere Einzelheiten zu dieser Klasse.

C#- und Java-1.5-Programmierer wird dieses Konzept bekannt vorkommen, weil es dem Boxing/Unboxing-Mechanismus ähnlich ist.

Objekte können Daten in gewöhnlichen Variablen speichern, die zu dem Objekt gehören. Variablen, die zu einem Objekt oder einer Klasse gehören, werden Felder genannt. Objekte können auch Funktionalität aufweisen, die durch Funktionen ausgelöst werden, die zu der Klasse gehören. Solche Funktionen werden Methoden der Klasse genannt. Die Sprechweise ist wichtig, weil sie uns hilft, zwischen Funktionen und Variablen zu unterscheiden, die separat für sich stehen, und solchen, die zu einer Klasse oder einem Objekt gehören. Insgesamt werden die Felder und Methoden als die Attribute der Klasse bezeichnet.

Von Feldern gibt es zwei Typen - sie können entweder zu jeder Instanz (jedem Objekt) der Klasse gehören, oder sie können zur Klasse selbst gehören. Dementsprechend werden sie im ersten Fall Instanzvariablen und im zweiten Fall Klassenvariablen genannt.

Eine Klasse wird mit dem Schlüsselwort class erzeugt. Die Felder und Methoden der Klasse werden in einem eingerückten Block aufgelistet.

Klassenmethoden haben nur eine Besonderheit gegenüber gewöhnlichen Funktionen - sie müssen einen zusätzlichen ersten Namen haben, der am Beginn der Parameterliste hinzugefügt wird, dabei übergibt man diesem Parameter jedoch nicht einen Wert, wenn man die Methode aufruft, sondern Python sorgt dafür. Diese spezielle Variable repräsentiert das Objekt selbst, und es hat sich eingebürgert, ihr den Namen self zu geben.

Man könnte diesem Parameter zwar irgendeinen Namen geben, aber es wird dennoch ausdrücklich empfohlen, den Namen self zu benutzen - jeder andere Name ist an dieser Stelle eindeutig verpönt. Es hat viele Vorzüge, einen Standardnamen zu benutzen - jeder Leser Ihres Programms wird ihn sofort erkennen, und spezielle IDEs (Integrierte Entwicklungsumgebungen) können einen unterstützen, wenn man die Bezeichnung self verwendet.

Anmerkung für C++/Java/C#-Programmierer

Das self in Python entspricht dem self-Zeiger in C++ und dem this in Java und C#.

Sie fragen sich wahrscheinlich, auf welche Weise Python den Wert für self übergibt und warum Sie dafür keinen Wert zu übergeben brauchen. Ein Beispiel wird dies klar machen. Angenommen, Sie haben eine Klasse, die MeineKlasse heißt und eine Instanz dieser Klasse, die MeinObjekt heißt. Wenn Sie eine Methode dieses Objekts als MeinObjekt.methode(param1, param2) aufrufen, dann wird dies von Python automatisch in MeineKlasse.methode(MeinObjekt, param1, param2) umgewandelt - und genau dafür ist dieses besondere self da.

Das bedeutet auch, dass man selbst bei einer Methode, die keine Parameter entgegennimmt, dennoch diese Methode mit einem self-Parameter definieren muss.

Klassen

Die einfachste mögliche Klasse zeigt das folgende Beispiel.

Erzeugen einer Klasse

Beispiel 11.1. Erzeugen einer Klasse

#!/usr/bin/python   

class Person:
    pass # Ein leerer Block

p = Person()
print(p)

So funktioniert es

Wir erzeugen eine neue Klasse, indem wir die Anweisung class benutzen, gefolgt von dem Namen der Klasse. Darauf folgt ein eingerückter Block von Anweisungen, die den Rumpf der Klasse bilden. In diesem Fall haben wir einen leeren Block, was durch die Anweisung pass angezeigt wird.

Als Nächstes erzeugen wir ein Objekt (eine Instanz) dieser Klasse, indem wir den Namen der Klasse benutzen, gefolgt von einem Klammerpaar. (Wir werden im nächsten Abschnitt mehr über diese so genannte Instanziierung lernen). Zur Sicherheit überprüfen wir den Typ der Variable, indem wir sie einfach mit print ausgeben. Dies zeigt uns, dass es sich um eine Instanz der Klasse Person im Modul __main__ handelt.

Beachten Sie, dass die Adresse, an der Ihr Objekt im Hauptspeicher Ihres Computers gespeichert ist, ebenfalls ausgegeben wird. Diese Adresse wird auf Ihrem Computer einen anderen Wert haben, weil Python die Objekte überall speichern kann, wo es Platz dafür gibt.

Objektmethoden

Wir haben bereits besprochen, dass Klassen/Objekte Methoden haben können, die bis auf den zusätzlichen Übergabeparameter self gewöhnliche Funktionen sind. Wir werden hierfür nur ein Beispiel sehen.

Benutzung von Objektmethoden

#!/usr/bin/python   
class Person:
    def say_hi(self):
        print('Hello, how are you?')

p = Person()
p.say_hi()
# Zugriff auf die Objektmethode auch direkt über die Klasse möglich:
# Person().say_hi()
print()
Person().say_hi()

So funktioniert es

Hier sehen wir, wie die Sache mit dem self funktioniert. Beachten Sie, dass die Methode sagHallo keinen Parameter entgegennimmt, aber dennoch das self in der Funktionsdefinition hat.

Die __init__-Methode

Es gibt viele Methodennamen, die in Pythonklassen eine besondere Bedeutung haben. Wir werden nun die Bedeutung der Methode __init__ sehen.

Die Methode __init__ wird aufgerufen, sobald ein Objekt einer Klasse instanziiert wird. Die Methode kann dafür benutzt werden, ihr Objekt auf irgendeine Weise zu initialisieren. Beachten Sie die doppelten Unterstriche sowohl am Anfang als auch am Ende des Namens.

Beispiel 11.3. Benutzung der __init__-Method

#!/usr/bin/python   				
class Person:
    def __init__(self, name):
        self.name = name

    def say_hi(self):
        print('Hello, my name is', self.name)

p = Person('Swaroop')
p.say_hi()
# The previous 2 lines can also be written as
# Person('Swaroop').say_hi()

So funktioniert es

Hier definieren wir die Methode __init__ so, dass sie einen Parameter name entgegennimmt (zusammen mit dem üblichen self). Wir erzeugen hier einfach ein neues Feld, das ebenfalls name heißt. Beachten Sie, dass dies zwei unterschiedliche Variablen sind, obwohl sie den gleichen Namen haben. Die Schreibweise mit dem Punkt ermöglicht es uns, zwischen den beiden zu unterscheiden.

Beachten Sie vor allem, dass wir die Methode __init__ nicht explizit aufrufen, sondern die Argumente in Klammern nach dem Klassennamen übergeben, wenn wir eine neue Instanz der Klasse erzeugen. Das ist die besondere Bedeutung dieser Methode.

Nun können wir das Feld self.name in unseren Methoden benutzen, wie es anhand der Methode sagHallo demonstriert wird.

Anmerkung für C++/Java/C#-Programmierer

Die Methode __init__ entspricht einem Konstruktor in C++, C# oder Java.

Klassen- und Objektvariablen

Wir haben bereits den Teil der Klassen und Objekte besprochen, der für ihre Funktionalität sorgt; nun werden wir den Teil betrachten, der die Daten enthält. Eigentlich handelt es sich um nichts anderes als gewöhnliche Variablen, die an die Namensräume der Klassen und Objekte gebunden sind, d.h. die Namen sind nur innerhalb des Kontextes der Klassen und Objekte gültig.

Es gibt zwei Arten von Feldern - Klassenvariablen und Objektvariablen, die danach klassifiziert werden, ob die Klasse oder das Objekt die jeweiligen Variablen besitzt.

Klassenvariablen werden gemeinsam benutzt, in dem Sinne, dass auf sie von allen Objekten (Instanzen) der Klasse zugegriffen wird. Es gibt nur eine Kopie einer Klassenvariable, und wenn irgendein Objekt eine Änderung an einer Klassenvariable vornimmt, dann spiegelt sich diese Änderung sofort auch in allen anderen Instanzen der Klasse wieder.

Objektvariablen gehören den einzelnen Objekten (Instanzen) der Klasse individuell. In diesem Fall hat jedes Objekt seine eigene Kopie des Feldes, d.h. sie werden nicht gemeinsam benutzt und sind auf keine Weise mit dem Feld des gleichen Namens in einer anderen Instanz der selben Klasse verknüpft. An einem Beispiel werden wir das leicht verstehen.

Benutzung von Klassen- und Objektvariablen

Beispiel 11.4. Benutzung von Klassen- und Objektvariablen

class Robot:
    """Represents a robot, with a name."""

    # A class variable, counting the number of robots
    population = 0

    def __init__(self, name):
        """Initializes the data."""
        self.name = name
        print("(Initializing {})".format(self.name))

        # When this person is created, the robot
        # adds to the population
        Robot.population += 1

    def die(self):
        """I am dying."""
        print("{} is being destroyed!".format(self.name))

        Robot.population -= 1

        if Robot.population == 0:
            print("{} was the last one.".format(self.name))
        else:
            print("There are still {:d} robots working.".format(
                Robot.population))

    def say_hi(self):
        """Greeting by the robot.

        Yeah, they can do that."""
        print("Greetings, my masters call me {}.".format(self.name))

    @classmethod
    def how_many(cls):
        """Prints the current population."""
        print("We have {:d} robots.".format(cls.population))


droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()

droid2 = Robot("C-3PO")
droid2.say_hi()
Robot.how_many()

print("\nRobots can do some work here.\n")

print("Robots have finished their work. So let's destroy them.")
droid1.die()
droid2.die()

Robot.how_many()

So funktioniert es

Dies ist ein langes Beispiel, aber es hilft uns, das Wesen von Klassen- und Objektvariablen zu demonstrieren. Die Variable bevoelkerung gehört hier zur Klasse Person und ist daher eine Klassenvariable. Die Variable name gehört dagegen zum jeweiligen Objekt (das durch self zugewiesen wird) und ist daher eine Objektvariable.

Wir nehmen daher auf die Klassenvariable bevoelkerung Bezug, indem wir schreiben Person.bevoelkerung und nicht self.bevoelkerung. Beachten Sie, dass eine Objektvariable mit dem gleichen Namen wie eine Klassenvariable diese Klassenvariable versteckt! Wir nehmen auf die Objektvariable name Bezug, indem wir die Schreibweise self.name in den Methoden dieses Objekts benutzen. Merken Sie sich diesen einfachen Unterschied zwischen Klassen- und Objektvariablen.

Beachten Sie, dass die __init__-Methode benutzt wird, um die Instanz von Person mit einem Namen zu initialisieren. In dieser Methode erhöhen wir den Zähler bevoelkerung um 1, weil wir eine weitere Person hinzugefügt haben. Beachten Sie auch, dass der Wert von self.name vom jeweiligen Objekt abhängt, was das Wesen von Objektvariablen ausmacht.

Denken Sie daran, dass Sie auf die Variablen und Methoden des gleichen Objekts nur mit Hilfe der Variablen self Bezug nehmen können. Dies wird als Attribut-Referenzierung bezeichnet.

Wir sehen in diesem Programm auch die Benutzung von Dokumentations-Strings sowohl für Klassen als auch für Methoden. Wir können auf den Dokumentations-String der Klasse zur Laufzeit mit Person.__doc__ zugreifen, und mit Person.sagHallo.__doc__ auf den Dokumentations-String der Methode.

Ganz ähnlich zur Methode __init__ gibt es eine andere spezielle Methode __del__, die aufgerufen wird, wenn ein Objekt aufhört zu leben, d.h. wenn es nicht mehr gebraucht wird und der von ihm belegte Speicher an das System zur Wiederverwendung zurückgegeben wird. In dieser Methode erniedrigen wir einfach den Zähler Person.bevoelkerung um 1.

Die __del__-Methode wird automatisch aufgerufen, wenn das Objekt nicht mehr gebraucht wird, und es gibt keine Garantie dafür, wann dies der Fall ist. Wenn Sie dies explizit tun wollen, müssen Sie einfach die del-Anweisung benutzen, die wir bereits aus früheren Beispielen kennen.

Anmerkung für C++/Java/C#-Programmierer

In Python sind alle Mitglieder, d.h. alle Attribute einschließlich der Felder public, und alle Methoden sind virtual.

Eine Ausnahme: Wenn Sie Felder verwenden, deren Namen mit einem doppelten Unterstrich beginnt, so wie __privatvar, dann modifiziert Python den Namen so, dass daraus praktisch eine private Variable wird.

Daraus hat sich die Konvention ergeben, dass Variablen, die nur innerhalb der Klasse oder des Objekts benutzt werden, mit einem Unterstrich beginnen sollten, während alle anderen Namen öffentlich sind und von anderen Klassen/Objekten benutzt werden können. Denken Sie daran, dass dies nur eine Konvention ist, und nicht von Python erzwungen wird (außer bei Variablen, deren Name mit einem doppelten Unterstrich beginnt).

Beachten Sie auch, dass die __del__-Methode dem Konzept eines Destruktors entspricht.

Vererbung

Eine der Hauptvorzüge objektorientierter Programmierung ist die Möglichkeit der Wiederverwendung von Programmcode, und eine Weise, mit der dies erreicht wird, ist durch den Mechanismus der Vererbung. Vererbung kann man sich am besten als eine mit Klassen realisierte Beziehung zwischen Datentypen und Unterdatentypen vorstellen.

Angenommen, Sie wollen ein Programm schreiben, das die Dozenten und Studenten an einer Hochschule verwaltet. Diese beiden Personengruppen haben einige gemeinsame Merkmale wie Name, Alter und Adresse. Sie haben auch einige spezielle Merkmale wie Gehalt, Vorlesungen und Beurlaubungen für Dozenten und Prüfungsnoten und Studiengebühren für Studenten.

Sie können zwei unabhängige Klassen für die beiden Typen und Prozesse anlegen, aber dies würde bedeuten, dass Sie, wenn Sie ein neues gemeinsames Merkmal hinzufügen wollen, es bei jeder der beiden voneinander unabhängigen Klassen tun hinzufügen müssen. Das wird sehr schnell unhandlich.

Eine bessere Lösung besteht darin, eine gemeinsame Klasse namens SchulMitglied anzulegen, und dann die Klasssen für Dozenten und Studenten von dieser Klasse erben zu lassen, d.h. sie werden zu Unterdatentypen dieses Datentyps (dieser Klasse) gemacht, und wir können diesen Unterdatentypen dann weitere besondere Merkmale hinzufügen.

Dieser Ansatz hat viele Vorteile. Wenn wir der Klasse SchulMitglied Funktionalität hinzufügen oder diese ändern, dann spiegelt sich dies automatisch in den Unterdatentypen wieder. Zum Beispiel können Sie ein neues Feld für die Nummer einer Identitätskarte sowohl für Dozenten als auch für Studenten hinzufügen, indem Sie dies einfach in der Klasse SchulMitglied ergänzen. Änderungen an den Unterdatentypen haben jedoch keine Auswirkung auf andere Unterdatentypen. Ein weiterer Vorteil besteht darin, dass man auf ein Objekt der Klasse Dozent oder Student auch als ein Objekt der Klasse SchulMitglied Bezug nehmen kann, was in einigen Situationen nützlich sein könnte, z. B. wenn man die Anzahl der Mitglieder der Hochschule zählen möchte. Diese Möglichkeit, einen Unterdatentyp in jeder Situation, wo ein Elterndatentyp erwartet wird, ersetzen zu können, d.h. ein Objekt als eine Instanz der Elternklasse behandeln zu können, wird Polymorphismus genannt.

Beachten Sie auch, dass wir den Programmcode der Elternklasse wiederverwenden, dass wir ihn also nicht in den verschiedenen Klassen wiederholen müssen, wie wir es hätten tun müssen, wenn wir unabhängige Klassen benutzt hätten.

Die Klasse SchulMitglied wird in dieser Konstellation auch als die Basisklasse oder die Superklasse bezeichnet. Die Klassen Dozent und Student werden die abgeleiteten Klassen oder Subklassen genannt.

Hier ist nun das Beispiel als Programm.

Beispiel 11.5. Verwendung von Vererbung

class SchoolMember():
    '''Represents any school member.'''
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print('(Initialized SchoolMember: {})'.format(self.name))

    def tell(self):
        '''Tell my details.'''
        print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")


class Teacher(SchoolMember):
    '''Represents a teacher.'''
    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age)
        self.salary = salary
        print('(Initialized Teacher: {})'.format(self.name))

    def tell(self):
        SchoolMember.tell(self)
        print('Salary: "{:d}"'.format(self.salary))


class Student(SchoolMember):
    '''Represents a student.'''
    def __init__(self, name, age, marks):
        SchoolMember.__init__(self, name, age)
        self.marks = marks
        print('(Initialized Student: {})'.format(self.name))

    def tell(self):
        SchoolMember.tell(self)
        print('Marks: "{:d}"'.format(self.marks))

t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)

# prints a blank line
print()

members = [t, s]
for member in members:
    # Works for both Teachers and Students
    member.tell()

So funktioniert es

Um Vererbung zu benutzen, geben wir die Namen der Basisklassen in einem Tupel an, das in der Klassendefinition dem Namen der Klasse folgt. Danach ist zu beachten, dass die __init__-Methode der Basisklasse unter Benutzung der self-Variable explizit aufgerufen wird, damit der Anteil des Objekts, der von der Basisklasse bereitgestellt wird, initialisiert werden kann. Es ist sehr wichtig, sich dies zu merken - Python ruft nicht automatisch den Konstruktor der Basisklasse auf; Sie müssen ihn selber explizit aufrufen.

Wir sehen hier auch, dass wir Methoden der Basisklasse aufrufen können, indem wir den Namen der Basisklasse mit einem Punkt voranstellen, und dann die Variable self zusammen mit den anderen Parametern der Methode übergeben.

Beachten Sie, dass wir Instanzen von Dozent und Student einfach wie Instanzen von SchulMitglied behandeln, wenn wir die auskunft-Methode der Klasse SchulMitglied benutzen.

Beachten Sie auch, dass die auskunft-Methode der Subklassen aufgerufen wird, und nicht die auskunft-Methode der Superklasse SchulMitglied. Man kann dies so verstehen, dass Python immer zuerst versucht, Methoden der jeweiligen Klasse zu finden, die in diesem Fall vorhanden sind. Wenn die Methode nicht gefunden wird, dann fängt Python an, die Methoden der zugehörigen Basisklassen eine nach der anderen durchzugehen, in der Reihenfolge, wie sie in dem Tupel in der Klassendefinition angegeben ist.

Eine Anmerkung noch zur Sprechweise - wenn mehr als eine Klasse in dem Tupel der Basisklassen angegeben ist, von denen die Klasse erbt, dann spricht man von Mehrfachvererbung.

Zusammenfassung

Wir haben nun die verschiedenen Aspekte von Klassen und Objekten erforscht und die damit verbundenen Sprechweisen kennen gelernt. Wir haben auch die Vorteile und möglichen Stolperfallen bei der objektorientierten Programmierung gesehen. Python ist hochgradig objektorientiert, und wenn wir diese Konzepte genau verstehen, wird uns dies langfristig sehr viel helfen.

Als Nächstes lernen wir, mit Ein/Ausgaben umzugehen und in Python auf Dateien zuzugreifen.

Der Selbstbezug

Klassenmethoden haben nur eine Besonderheit gegenüber gewöhnlichen Funktionen - sie müssen einen zusätzlichen ersten Namen haben, der am Beginn der Parameterliste hinzugefügt wird, dabei übergibt man diesem Parameter jedoch nicht einen Wert, wenn man die Methode aufruft, sondern Python sorgt dafür. Diese spezielle Variable repräsentiert das Objekt selbst, und es hat sich eingebürgert, ihr den Namen self zu geben.

Man könnte diesem Parameter zwar irgendeinen Namen geben, aber es wird dennoch ausdrücklich empfohlen, den Namen self zu benutzen - jeder andere Name ist an dieser Stelle eindeutig verpönt. Es hat viele Vorzüge, einen Standardnamen zu benutzen - jeder Leser Ihres Programms wird ihn sofort erkennen, und spezielle IDEs (Integrierte Entwicklungsumgebungen) können einen unterstützen, wenn man die Bezeichnung self verwendet.

Anmerkung für C++/Java/C#-Programmierer

Das self in Python entspricht dem self-Zeiger in C++ und dem this in Java und C#.

Sie fragen sich wahrscheinlich, auf welche Weise Python den Wert für self übergibt und warum Sie dafür keinen Wert zu übergeben brauchen. Ein Beispiel wird dies klar machen. Angenommen, Sie haben eine Klasse, die MeineKlasse heißt und eine Instanz dieser Klasse, die MeinObjekt heißt. Wenn Sie eine Methode dieses Objekts als MeinObjekt.methode(param1, param2) aufrufen, dann wird dies von Python automatisch in MeineKlasse.methode(MeinObjekt, param1, param2) umgewandelt - und genau dafür ist dieses besondere self da.

Das bedeutet auch, dass man selbst bei einer Methode, die keine Parameter entgegennimmt, dennoch diese Methode mit einem self-Parameter definieren muss.