Python Tutorial – Klassen und Vererbung

Der Abschnitt Klassen und Vererbung dieses kompakten Python-Tutorials gibt eine Übersicht über die objektorientierte Programmierung mit Python, insbesondere, wie man größere Python-Projekte mit Klassen und Klassenhierarchien strukturiert.

  Motivation

Warum Klassen verwenden?

Die Strukturierung von Python-Projekten mittels Klassen ist nicht zwingend erforderlich und bei kleineren Programmen auch nicht notwendig. Bei der Entwicklung größerer Software-Komponenten, die ggf. auch graphische Benutzeroberflächen enthalten, kommt man auch in Python an Klassen und Vererbung nicht vorbei.

Die objektorientierte Programmierung in Python hat mehrere Vorteile:

  •  Modularität: Zusammengehörende Variablen und Funktionen können in Klassen gruppiert werden.

  •  Wiederverwendbarkeit: Eine einmal geschriebene Klasse kapselt Funktionalität, die an anderen Stellen wiederverwendet werden kann.

  •  Wartbarkeit: Durch die Entwicklung einer übersichtlichen Klassenhierarchie kann auch eine große Codebasis besser gewartet, überarbeitet und erweitert werden.


1 Objektorientierte Programmierung

Objektorientierte Programmierung (OOP) ist ein Entwurfskonzept höherer Programmiersprachen, das ein Programm mittels Objekten strukturiert, die Attribute und Methoden bzw. Aktionen besitzen.

OOP-Konzepte:
 Eine Klasse ist ein Objekt-Konstruktor zum Erstellen von neuen Objekten einer Klasse, diese nennt man auch die Instanzen der Klasse.
 Attribute einer Klasse sind Variablen,die innerhalb einer Klasse definiert werden und Eigenschaften der Klassen-Objekte beschreiben.
 Methoden einer Klasse sind Funktionen, die innerhalb einer Klasse definiert werden und Aktionen beschreiben, die auf den Daten der Klasse durchgeführt werden.

OOP-Vorteile:
 OOP ermöglicht eine höhere Abstraktion bei der Programmierung,
 OOP ermöglicht die Kapselung zusammengehörender Attribute und Aktionen,
 OOP ermöglicht die Wiederverwendbarkeit von Code. Durch Vererbung kann eine Subklasse die Attribute und Methoden einer Basisklasse erben.


1-1 Python als objektorientierte Programmiersprache

Python ist eine objektorientierte Programmiersprache, deren Funktionalität weitgehend über Klassen abgebildet ist. Das ist für viele Python-Einsteiger zunächst nicht bemerkbar, Tatsache ist jedoch: selbst wer in Python selber keine Klassen schreibt, nutzt implizit die Klassen der Python-Entwickler. Wer z.B. die Python-Bibliothek Scikit-Learn verwendet, nutzt Klassen wie DecisionTreeClassifier oder RandomForestClassifier, die die Attribute und Methoden der abgebildeten Machine Learning-Verfahren bündeln.


1-2 Klasse eines Python-Objektes herausfinden

Die Klasse eines Python-Objektes kann mit Hilfe der type-Anweisung herausgefunden werden. Wenn man mit type() den Typ einer Zahl, einer Zeichenkette, oder einer Liste ausgibt, wird die passende Klasse ausgegeben, mit deren Hilfe das entsprechende Objekt erzeugt wurde.

zahl = 1.23
text = 'Hallo'
mylist = [1, 4, 9, 16, 25]
print("Python-Objekte und ihre Klassen: ")
print(type(zahl)) # Ausgabe: <class 'float'>
print(type(text)) # Ausgabe:  <class 'str'>
print(type(mylist)) # Ausgabe:  <class 'list'>

2 Klassen in Python

Eine Klasse gruppiert zusammengehörende Variablen, diese nennt man Attribute der Klasse, und Funktionen, diese nennt man Methoden der Klasse, und sie besitzt stets genau eine Funktion namens __init __ (), mit deren Hilfe man neue Instanzen der Klasse initialisiert.

2-1 Klasse definieren und verwenden

In Python benutzt man für die Definition einer neuen Klasse das Schlüsselwort class, danach wird der Name der Klasse angegeben, gefolgt von Doppelpunkt, und danach eingerückt die Klassenattribute (falls vorhanden) und die Methoden der Klasse, als Erstes die "__init __ ()"-Methode zum Initialisieren.

Python-Klassen haben eine __init __ ()-Methode, die implizit beim Erstellen eines neuen Klassenobjektes ausgeführt wird. Die "__init __ ()"-Methode wird wie ein Klassenkonstruktor verwendet, um Objekteigenschaften zu setzen oder andere Vorgänge, die beim Erstellen des Objekts erforderlich sind, durchzuführen.

Die Attribute einer Klasse können entweder Klassen-Attribute oder Instanz-Attribute sein.
 Klassen-Attribute sind Eigenschaften, die allen Instanzen der Klasse gehören, sie werden noch vor der "__init __ ()"-Methode angegeben.
 Instanz-Attribute sind Eigenschaften, die für einzelne Instanzen gesetzt werden, sie werden erst innerhalb der "__init __ ()"-Methode definiert.

# 1. Klasse MyClass definieren
class MyClass():
    attr1 = "wert0"  
    def __init__(self, attr2): # Konstruktor, Initialisierung
        self.attr2 = attr2 # Instanz-Attribut
    def myprint(self): # Methode bzw. Funktion
       print("Instanz-Attribut:", self.attr2)
		
# 2. Klasse verwenden
i1 = MyClass(attr2 = "wert1")
i1.myprint() # Ausgabe: Instanz-Attribut: wert1
i2 = MyClass(attr2 = "wert2")
i2.myprint() # Ausgabe: Instanz-Attribut: wert2
print("Klassen-Attribut:", MyClass.attr1)

2-2 Methoden definieren und verwenden

Eine Methode einer Klasse ist eine Funktion, die innerhalb dieser Klasse definiert wird und als ersten Parameter einen Verweis namens self auf die Instanz enthält, von der sie aufgerufen wurde. Man unterscheidet zwischen Instanzmethoden, die zu einer Instanz der Klasse gehören (also zu einem Objekt der Klasse) und Klassenmethoden, die der Klasse im allgemeinen gehören und nur sparsam eingesetzt werden sollten.

Eine Besonderheit bei der Verwendung von Membervariablen und Instanzmethoden einer Klasse in Python ist, dass bei interner Verwendung Schlüsselwort self stets vorangestellt werden muss.

2-3 Beispiel: Klasse definieren und verwenden

Beispiel: Wir deklarieren eine Klasse Auto, mit der wir neue Auto-Objekte erzeugen wollen. Ein Auto hat die Eigenschaften Hersteller und Modell und eine Methode "ausgeben()", die die Eigenschaften des Autos zu einer Zeichenkette zusammenbaut und ausgibt.

# auto.py 
class Auto:
    def __init__(self, hersteller, modell):
       self.hersteller = hersteller
       self.modell = modell       
    def ausgeben(self):
        print("Hersteller: " + self.hersteller + ", Modell: " + self.modell)
   
 # Erstelle neue Klassenobjekte, gebe sie aus, und lösche sie wieder
my_auto1 = Auto("Audi", "A3") 
my_auto2 = Auto("BMW", "i3")
my_auto1.ausgeben() # Ausgabe: Hersteller: Audi, Modell: A3
my_auto2.ausgeben() # Ausgabe: Hersteller: BMW, Modell: i3
del my_auto1
del my_auto2  

3 Vererbung in Python

Vererbung ermöglicht es, Klassen zu definieren, die alle Methoden und Eigenschaften einer anderen Klasse erben. Die übergeordnete Klasse, von der geerbt wird, nennt man Basisklasse. Die untergeordnete Klasse, die von der Basisklasse Eigenschaften und Methoden erbt, nennt man Subklasse oder Kindklasse.

3-1 Subklassen definieren

In Python wird eine Subklasse definiert, indem man nach dem Klassennamen den Namen der Basisklasse in runde Klammern setzt, und in der __init__-Methode der Subklasse mit super().__init__ den Konstruktor der Basisklasse aufruft.

Basisklassen mit super() aufrufen
Um in einer Subklasse eine Methode der Basisklasse zu verwenden, wird ihr der Präfix super() vorangestellt.


Basisklasse und Subklasse definieren

# 1. Klassen definieren
class MyBaseClass():
	def __init__(self, attr1):
		self.attr1 = attr1
	def myprint(self):
		print("Attribut1:", self.attr1)
		
class MySubClass(MyBaseClass):
	def __init__(self, attr1, attr2):
		super().__init__(attr1)
		self.attr2 = attr2
	def myprint(self):
		super().myprint()
		print("Attribut2:", self.attr2)

3-2 Subklassen verwenden

Basisklasse und Subklasse verwenden
Basis- und Subklasse werden verwendet, indem man mit Hilfe der jeweiligen Konstruktoren neue Instanzen der Klassen erstellt, und mit Hilfe der Punkt-Notation Methoden der Klassen aufruft. Z.B. wird mit b1 = MyBaseClass(attr1 = "wert1") die Instanz b1 der Basisklasse MyBaseClass erstellt, und mit b1.myprint() die myprint-Methode der Basisklasse ausgeführt.

# 2. Klassen verwenden
b1 = MyBaseClass(attr1 = "wert1")
print("Ausgabe der Basisklasse:")
b1.myprint()
s1 = MySubClass(attr1 = "wert1", attr2="wert2")
print("Ausgabe der Subklasse:")
s1.myprint()

Ausgabe: Subklassen verwenden

Ausgabe der Basisklasse:
Attribut1: wert1
Ausgabe der Subklasse
Attribut1: wert1
Attribut2: wert2

3-3 Beispiel: Klassenhierarchien definieren und verwenden

Eine Klassenhierarchie besteht zumindest aus einer Basisklasse und zwei oder mehr Subklassen, die einige Attribute und Methoden von der Basisklasse erben, und darüber hinaus eigene Attribute und Methoden haben, in denen sie sich unterscheiden.
Eine Basisklasse "Fahrzeug" wird z.B. mehrere Subklassen wie "Auto" und "LKW" haben. Die Attribute hersteller und modell sind allen Klassen gemeinsam und gehören daher in die Basisklasse. Ein Auto hat darüber hinaus das Attribut "farbe" das hier nur für Autos und nicht für LKWs relevant ist. Ein LKW hat das Attribut "tonnage", das nur für LKWs und nicht für Autos relevant ist.

Beispiel: Basisklasse Fahrzeug, Subklasse Auto
Wir definieren zunächst die Basisklasse "Fahrzeug" mit der Subklasse "Auto". Die Subklasse Auto erbt die Eigenschaften und Funktionen von "Fahrzeug" und hat eine weitere Eigenschaft "farbe" sowie eine zusätzliche Methode "set_farbe()", mit der die Farbe geändert werden kann.

Klassen-Hierarchien

Die Klassenhierarchie zusammengehöriger Python-Klassen, d.h. die Beziehungen zwischen den Klassen, können noch vor der Programmierung durch ein UML-Klassendiagramm visualisiert werden.

In einem UML-Klassendiagramm wird jede Klasse in ein Rechteck mit drei getrennten Bereichen gesetzt: Namen der Klasse, Klassen-Attribute und Klassen-Methoden. Weiterhin werden die Beziehungen zwischen den Klassen durch Pfeilverbindungen eingezeichnet.

UML-Klassendiagramm

UML-Klassendiagramm mit Basisklasse und Subklassen

Python-Klassen auf Module verteilen
Klassen und Subklassen können in demselben Python-Modul untergebracht werden, z.B. werden in dem folgenden Beispiel die Klassen Fahrzeug, Auto und LKW nacheinander in das Modul fahrzeug.py plaziert. Für größere Klassen hat es sich bewährt, ein eigenes gleichnamiges Modul zu verwenden, dann würde man die Klasse fahrzeug in das Modul fahrzeug.py platzieren, die Klasse auto in das Modul auto.py etc.

fahrzeug.py: Klassendefinitionen

#Modul fahrzeug.py
class Fahrzeug: 
    """Basisklasse für Fahrzeuge"""
    def __init__(self, hersteller, modell):
        self.hersteller = hersteller
        self.modell = modell
    def ausgeben(self):
        print("Hersteller:", self.hersteller, ", Modell:", self.modell, 
              end =", ")

class Auto(Fahrzeug): 
    """ Subklasse für Autos """
    def __init__(self, hersteller, modell, farbe="silber"):
        super().__init__(hersteller, modell)
        self.farbe = farbe
    def ausgeben(self):
        print("[Auto]", end =" ")
        super().ausgeben()
        print("Farbe: ", self.farbe)
    def set_farbe(self, farbe):
        self.farbe = farbe

fahrzeug.py: Erläuterung des Codes
Das Modul fahrzeug.py enthält lediglich die Klassendefinitionen, hier werden noch keine neue Instanzen der Klassen angelegt und das Modul fahrzeug.py kann nicht ausgeführt werden.

main.py: Verwendung der Klassen aus fahrzeug.py
Nachdem Basisklasse und Subklasse in fahrzeug.py definiert wurden, kann man damit neue Instanzen von Fahrzeugen und Autos anlegen und ihre Methoden ausführen. Damit die Klassen aus dem Modul fahrzeug.py in dem Modul main.py verwendet werden können, müssen sie zunächst importiert werden

from fahrzeug import Fahrzeug, Auto
def main():
    # Erstelle Instanz der Basisklasse Fahrzeug
    my_bmw = Fahrzeug("BMW", "x3")
    my_bmw.ausgeben()
     # Erstelle Instanz der Subklasse Auto 
    my_audi = Auto("Audi", "A3")
    my_audi.ausgeben() 
    # Farbe ändern und erneut ausgeben
    my_audi.set_farbe("rot")
    my_audi.ausgeben()
# Einstiegspunkt der Anwendung
if __name__ == "__main__":
    main()

main.py: Erläuterung des Codes
Die main.py ist der Einstiegspunkt der Anwendung. Wir erstellen eine Instanz my_bmw der Basisklasse Fahrzeug und geben my_bmw aus, danach eine Instanz my_audi der Subklasse Auto, geben my_audi aus, ändern die Farbe, und geben my_audi nochmal aus.

Ausgabe

Hersteller: BMW , Modell: x3, 
[Auto] Hersteller: Audi , Modell: A3, Farbe:  silber
[Auto] Hersteller: Audi , Modell: A3, Farbe:  rot

Beispiel: Subklasse LKW
Wir definieren eine weitere Subklasse "LKW" der Basisklasse "Fahrzeug". Die Subklasse LKW hat zusätzlich zu den von "Fahrzeug" geerbten Eigenschaften noch das Attribut "tonnage" mit der Setter-Methode set_tonnage() und überschreibt die ausgeben()-Methode mit einer eigenen Definition. Hier wird die Methode "ausgeben()" der Basisklasse so überschrieben, dass der Text "LKW" der Ausgabe vorangestellt wird, und die Tonnage an die Ausgabe angefügt wird.

class LKW(Fahrzeug): 
    def __init__(self, hersteller, modell, tonnage = 3):
        super().__init__(hersteller, modell)
        self.tonnage = tonnage
    def set_tonnage(self, tonnage):
        self.tonnage = tonnage
    def ausgeben(self): 
         print("[LKW]:", end =" ")
         super().ausgeben()
         print("Tonnage: ", self.tonnage)
    def set_achsenkonfig(achsen):
        pass

Das Schlüsselwort pass wird in Python als Platzhalter für Code verwendet, der erst noch implementiert werden muss.

4 UML-Klassendiagramme

UML-Klassendiagramme sind nützlich, um eine visuelle vereinfachte Darstellung zusammenhängender Klassen zu erzeugen. Für die Erstellung von UML-Klassendiagrammen in Python gibt es unterschiedliche Tools und Extensions, z.B. PlantUML, Mermaid JS und pyreverse. PlantUML- und Mermaid-Diagramme werden mit Hilfe einer eigenen Markdown-Syntax erstellt, wie im folgenden Beispiel, das mit Hilfe der PlantUML-Extension in Visual Studio Code erstellt wurde.


4-1 UML-Klassendiagramm mit PlantUML

Ein UML-Klassendiagramm mit PlantUML ist einfach eine Textdatei mit der Endung *.puml, mit zwei Annotationen @startuml und @enduml, die Anfang und Ende der Datei markieren. Die Klassen mit ihren Attributen und Methoden werden mittels einer Markdown Syntax erstellt.

Eine Klasse wird mit dem Schlüsselwort class eingeleitet, gefolgt von dem Namen der Klasse, und dem Attributen und Methoden, die in geschweifte Klammern gesetzt werden. Sichtbarkeit von Attributen und Methoden wird durch das Voranstellen der Symbole - (privat), + (öffentlich), # (protected) beschrieben. Beziehungen zwischen Klassen werden durch verschiedene Pfeilverbindungen erzeugt: <|-- für Vererbung, -- für Assoziation, o-- für Aggregation, *-- für Komposition.

@startuml FahrzeugUML
class Fahrzeug {
  - hersteller
  - modell
  + Fahrzeug(hersteller, modell)
  + ausgeben()
}
class Auto {
  - farbe
  + Auto(hersteller, modell, farbe)
  + set_farbe(farbe)
  + ausgeben()
}
class LKW {
  - tonnage
  + LKW(hersteller, modell, tonnage)
  + set_tonnage(tonnage)
  + ausgeben()
}
Fahrzeug <|-- Auto
Fahrzeug <|-- LKW
@enduml

UML-Klassendiagramm mit PlantUML

UML-Klassendiagramm mit Basisklasse und Subklassen

Das Diagramm wurde in Visual Studio Code mit Hilfe der PlantUML Extension erstellt. Eine Voransicht des UML-Klassendiagramm wird mit Hilfe der Tastenkombination Alt + D angezeigt.

Die PlantUML-Markdown-Sprache bietet umfangreiche Beschreibungsmöglichkeiten zum Erstellen von Klassendiagrammen. Attribute und Parameter können mit Datentypen versehen werden, Beziehungen können beschriftet werden, und vieles mehr.



4-2 UML-Klassendiagramm mit PyReverse

UML-Klassendiagramme können in Python mit Hilfe des Kommandozeilen-Tools pyreverse erstellt werden, das als Teil des Paketes pylint mitgeliefert wird. Pylint ist ein Python-Paket für statische Codeanalyse und dazu gehört insbesondere auch das Reverse Engineering, d.h. das Erstellen von UML-Klassendiagrammen aus vorhandenem Python-Code.

Pylint installieren
Bei der Installation von pylint bzw. pyreverse sind einige Besonderheiten zu beachten.

Pyreverse-Befehle
Pyreverse-Befehle habe die allgemeine Form pyreverse [optionen] pakete und werden in der Kommandozeile eingegeben. Mittels der Optionen kann man z.B. Pakete filtern und das Ausgabe-Format festlegen.

Hilfe zu den Befehlen ausgeben:

pyreverse --help

Gehe mit cd in den Ordner, wo sich das Paket oop befindet. Erzeuge ein UML-Diagramm für das Paket oop im Ausgabeformat png.

cd \pfad\zu\oop
pyreverse oop --output png .

Gehe mit cd in den Ordner, wo sich das Paket oop befindet. Erzeuge ein UML-Diagramm für das Paket oop im Ausgabeformat svg, verwende qualifizierte Modulnamen (hier: oop.fahrzeug.Fahrzeug).

cd \pfad\zu\oop
pyreverse oop --output svg --verbose --module-names=true

Verzeichnisstruktur des Pakets oop
Der Ordner oop enthält eine Datei __init__py und wird dadurch als Paket gekennzeichnet.

Das Modul fahrzeug.py enthält die Basisklasse Fahrzeug und die Subklassen Auto und LKW.

├─ oop
│   ├─ __init__.py
│   ├─ fahrzeug.py

UML-Klassendiagramm

UML-Klassendiagramm in Python mit pyreverse

Die mit Pyreverse erzeugten Klassendiagramme sind schlichter als die mit PlantUML oder Mermaid JS erzeugten, haben jedoch den Vorteil, direkt aus dem Quellcode der Klassen generiert zu werden. Man spart sich damit die doppelte Arbeit, zusätzlich zur Programmierung die Klassen in einer Markdown-Sprache noch einmal beschreiben zu müssen.


Nächste Schritte

In Teil 2: Python Pakete installieren wird beschrieben, wie man mit Hilfe der Paketverwaltungssysteme pip und conda Python-Pakete installiert, aktualisiert und deinstalliert, wie man für verschiedene Projekte passende Anwendungsumgebungen ("environments") erstellt und wie man die installierten Pakete in Python-Skripten mit Hilfe der import-Anweisung korrekt importiert. Die Entwicklung selbstdefinierter Pakete, mit deren Hilfe man größere Projekte strukturieren kann, wird an einem Beispiel vorgestellt.

Teil 3: Python Bibliotheken dieses Python-Tutorials gibt eine Übersicht über die wichtigsten Bibliotheken für Datenanalyse und Maschinelles Lernen. Zunächst werden die Pakete NumPy, Matplotlib und Pandas vorgestellt, die für Datenvorbereitung und -Visualisierung verwendet werden. Anschließend wird die Verwendung der Machine Learning-Bibliothek Scikit-Learn am Beispiel einer Klassifikation für die Vorhersage von Ausfällen erklärt. Schließlich wird die Erstellung eines Künstlichen Neuronalen Netzwerkes für die Bilderkennung mit Hilfe der Bibliotheken Keras und Tensorflow illustriert.

Die vollständige und detaillierte Python-Dokumentation kann auf der offiziellen Python-Webseite https://docs.python.org/3/tutorial/ nachgeschlagen werden.

Autoren, Tools und Quellen

Autor:
 Prof. Dr. Eva Maria Kiss


Tools:

elab2go-Links

Quellen und weiterführende Links

  • [1] Offizielle Python Dokumentation bei python.org: docs.python.org/3/tutorial/
    sehr umfangreich, Nachschlagewerk, hier findet man Dokumentationen für verschiedene Python-Versionen
  • [2] Python Tutorial bei Google Developers: developers.google.com/edu/python/introduction
    umfangreich, für Fortgeschrittene, verweist auf die offizielle Dokumentation
  • [3] Python Tutorial bei W3Schools: w3schools.com/python/ – für Einsteiger geeignet, interaktive Ausführung im Browser
  • [4] Python Tutorial: evamariakiss.de/tutorial/python/ – Englisch, zum Nachschlagen der wichtigsten Python-Befehle, mit Selbsttest / Quiz
  • [5] NumPy: numpy.org/ – Mehrdimensionale Arrays, Mathematische Funktionen, Zufallszahlen
  • [6] Matplotlib: matplotlib.org/ – Datenvisualisierung, Plotten
  • [7] Pandas: pandas.pydata.org/ – Datenverwaltung, Datenvorbereitung, DataFrames, Series
  • [8] Scikit-Learn: scikit-learn.org – Algorithmen für Maschinelles Lernen
  • [9] Keras: keras.io – Künstliche Neuronale Netzwerke, Deep Learning