Home | Lehre | Videos | Texte | Vorträge | Software | Person | Impressum, Datenschutzerklärung | ![]()
Stand: 2025-04-02
weitgehend formuliert von ChatGPT-4o, verbessert von Claude 3.7 Sonnet,
redigiert/korrigiert von Jörn Loviscach
Abstrakte Klassen helfen dabei, eine Grundstruktur für andere Klassen zu erstellen. Sie legen fest, welche Methoden (Funktionen) die abgeleiteten Klassen unbedingt haben müssen. Diese abstrakten Klassen selbst können nicht direkt benutzt werden, sondern dienen nur als Vorlage für andere Klassen.
Eine abstrakte Klasse ist damit eine Art Bauplan für andere
Klassen. Normale, also konkrete
Klassen sind dagegen
Baupläne für Instanzen, also Objekte. Eine abstrakte Klasse
sagt, welche Methoden die abgeleiteten Klassen (mindestens) haben
müssen, gibt aber noch nicht an, was alle diese Methoden genau wie tun.
Diese konkreten Details werden erst in abgeleiteten Klassen
festgelegt.
Eine abstrakte Klasse verwendet man dann, wenn eine konkrete Klasse an ihrer Stelle auf unlogische Art zu allgemein wäre. Ein Beispiel dafür ist eine geometrische Form, zum Beispiel für ein Zeichenprogramm. Es gibt keine allgemeine, abstrakte geometrische Form auf dem Bildschirm, sondern nur konkrete Formen: Vierecke, Kreise usw. Aber dennoch ist die abstrakte Idee der geometrischen Form hilfreich, um festzulegen, was alle konkreten geometrischen Formen können sollen.
Zum Beispiel möchten wir sicherstellen, dass jede Form ihre Fläche berechnen kann. Das können wir mit einer abstrakten Klasse sicherstellen, von der dann alle konkreten Formen erben:
from abc import ABC, abstractmethod
class Form(ABC):
@abstractmethod
def berechne_Fläche(self):
passDiese Klasse Form erbt ihrerseits von der vordefinierten
Oberklasse ABC (das ABC
steht für Abstract Base
Class
; jemand fand diese Abkürzung wohl witzig). Das kennzeichnet
sie als abstrakte Klasse.
In dieser abstrakten Klasse Form haben wir eine
abstrakte Methode berechne_Fläche definiert. Dass
diese Methode abstrakt ist, heißt, dass sie hier in der abstrakten
Klasse nicht implementiert (das heißt: ausprogrammiert) wird. Es würde
auch keinen Sinn ergeben, die Fläche der allgemeinen Form zu bestimmen,
denn was sollte man hier rechnen?
Die Implementierung der Methode berechne_Fläche erfolgt
dann in den konkreten Unterklassen dieser abstrakten
Klasse:
import math
class Kreis(Form):
def __init__(self, radius):
self.radius = radius
def berechne_Fläche(self):
return math.pi * self.radius ** 2
class Rechteck(Form):
def __init__(self, breite, höhe):
self.breite = breite
self.höhe = höhe
def berechne_Fläche(self):
return self.breite * self.höheWir können sicher sein, dass alle Instanzen der konkreten Klassen
Kreis und Rechteck die Methode
berechne_Fläche besitzen:
kreis = Kreis(5)
rechteck = Rechteck(4, 6)
print(f"Fläche des Kreises: {kreis.berechne_Fläche()}")
print(f"Fläche des Rechtecks: {rechteck.berechne_Fläche()}")Der Versuch, etwa mit form = Form() eine Instanz der
abstrakten Klasse zu erzeugen, führt dagegen zu einem Fehler. Das ist
richtig und gut, denn sonst könnte man
form.berechne_Fläche() aufrufen, aber es ist nicht
festgelegt, was diese Methode in der allgemeinen Form
machen soll.
Wenn man in einer Unterklasse einer abstrakten Oberklasse nicht alle deren abstrakten Methoden implementiert, bleibt diese Unterklasse abstrakt. Auch von ihr lassen sich dann keine Instanzen erzeugen, sondern man muss diese Unterklasse weiter ableiten.
Sicherstellen der Methodendefinition: Abstrakte Klassen sorgen dafür, dass jede abgeleitete Klasse bestimmte Methoden implementiert. Das führt zu einem einheitlicheren und vorhersehbaren Verhalten.
Einheitliche Struktur: Abstrakte Klassen schaffen eine einheitliche Struktur, wodurch verschiedene Klassen auf gleiche Weise benutzt werden können.
Bessere Übersicht: Abstrakte Klassen helfen dabei, den Code besser zu organisieren und die Beziehungen zwischen verschiedenen Klassen klarer zu machen.
In Python gibt es mehrere Arten von Datenstrukturen, die als
Collections
bezeichnet werden. Diese Collections bieten
verschiedene Möglichkeiten, um Daten zu speichern und zu verwalten. Zu
den wichtigsten Collections gehören Liste (schon bekannt), Tupel
(Tuple), Menge (Set) und Wörterbuch (Dictionary; im Deutschen sagt man
seltenst Wörterbuch
, sondern allenfalls assoziatives
Array
, was auch nicht wirklich Deutsch ist).
Ein Tupel ähnelt einer Liste, aber es ist unveränderlich, das heißt,
einmal erzeugt, kann es nicht mehr geändert werden. Tupel werden mit
runden Klammern (...) erstellt:
# Ein einfaches Tupel
mein_tupel = (1, 2, 3)
# Ein Tupel mit verschiedenen Datentypen
gemischtes_tupel = (1, "Hallo", 3.14)
# Oder mit Typenangabe
gemischtes_tupel2: tuple[int, str, float] = (1, "Hallo", 3.14)Der Zugriff auf die Elemente erfolgt mit eckigen Klammern, wie bei Listen:
print(mein_tupel[0]) # Ausgabe: 1
print(gemischtes_tupel[1]) # Ausgabe: HalloMan kann allerdings nicht die Elemente ersetzen oder die Länge ändern:
gemischtes_tupel[0] = 23 # Fehler!
gemischtes_tupel.append("bla") # Fehler!Warum Tupel verwenden?
Vorsicht: Tupel können in Python auch erzeugt werden, ohne
dass man Klammern schreibt. Das ist bequem, aber gefährlich!
Beispielsweise führt ein versehentlich gesetztes Komma wie das in
x = 5, dazu, dass ein Tupel mit einem Element
5 angelegt wird. Dann ergibt print(2 * x)
nicht die erhoffte Ausgabe 10, sondern überraschenderweise
(5, 5).
Eine Menge ist eine Sammlung von Elementen, die es alle höchstens
einmal darin gibt. Die Elemente sind nicht auf garantierte Art geordnet,
haben also insbesondere keine Indizes [0], [1]
usw., anders als die Elemente einer Liste oder eines Tupels. Mengen
werden mit geschweiften Klammern {...} erstellt, die leere
Menge mittels set(), also nicht wie in der
Mathematik als {}, denn das wäre ein leeres Dictionary
(siehe unten).
# Eine Menge mit ein vier Elementen
mein_set = {23, 42, 'bla', 7}
# Oder mit Typenangabe
mein_set2: set[int | str] = {23, 42, 'bla', 7}
# Die leere Menge
leeres_set = set()Elemente werden mit add und remove
hinzugefügt bzw. entfernt. Mit in und not in
prüft man, ob ein gegebenes Objekt enthalten ist:
mein_set.add('blubb') # Hinzufügen eines Elements
mein_set.remove(42) # Entfernen eines Elements
print(mein_set) # Ausgabe: {'blubb', 23, 'bla', 7} in irgendeiner Reihenfolge
print('abc' not in mein_set) # Ausgabe: TruePython beherrscht viele Operationen der Mengenlehre. Unter anderem
lassen sich Mengen mit | vereinigen, mit &
schneiden, mit - voneinander abziehen:
set1 = {1, 2, 3}
set2 = {3, 4, 5}
set3 = {5, 6, 7}
print(set1 & set2 | set3) # Ausgabe: {3, 5, 6, 7} in irgendeiner ReihenfolgeWarum Mengen verwenden?
mit Milch,
mit Zucker,
mit Plätzchen.
Ein Dictionary speichert Paare von Schlüssel (Key)
und Wert (Value), so wie ein
Englisch-Deutsch-Wörterbuch zu jedem Schlüssel (das jeweilige englische
Wort) einen Wert (die jeweilige deutsche Übersetzung) enthält. Unter dem
Schlüssel schlägt man nach und liest damit den Wert aus oder ändert ihn.
Ganz entfernt ähnelt das dem struct von C, ist aber viel
flexibler.
Dictionaries sind geordnet (seit Python 3.7) und ihre Elemente sind
indiziert. Dictionaries werden mit geschweiften Klammern
{...} erstellt, was zu Verwechselungen mit Sets führen
kann. Aber in den Schweifklammern von Dictionaries stehen nicht einzelne
Werte, sondern Paare der Art Schlüssel: Wert. Mit den
leeren Schweifklammern {} erzeugt man ein leeres
Dictionary, weshalb die leere Menge stattdessen mit set()
erzeugt wird.
Der Typ für das folgende Dictionary ist
dict[str, str | int]:
# Ein Wörterbuch mit drei Schlüssel-Wert-Paaren
mein_Dict = {
'Name': 'Alex',
'Alter': 25,
'Stadt': 'Berlin'
}Wie man mit Hilfe von Zahlen-Indizes auf die Elemente von Listen und Tupeln zugreift, so greift man mit den Schlüsseln auf die Werte des Dictionary zu:
print(mein_Dict['Name']) # Ausgabe: Alex
mein_Dict['Alter'] = 26Auf diese Art fügt man Schlüssel-Wert-Paare hinzu oder entfernt sie:
mein_Dict['Beruf'] = 'Entwicklerin' # Hinzufügen eines neuen Paares
del mein_Dict['Stadt'] # Entfernen eines Paares
print(mein_Dict) # Ausgabe: {'Name': 'Alex', 'Alter': 25, 'Beruf': 'Entwicklerin'}Oft benutzt man Listen von Dictionaries (langsam nochmal lesen: Listen! von! Dictionaries!) als Datenspeicher:
personen: list[dict[str, str | int]] = [
{'Name': 'Alex', 'Alter': 25},
{'Name': 'Bob', 'Alter': 30},
{'Name': 'Charlie', 'Alter': 35},
{'Name': 'Diana', 'Alter': 40}
]
gesamtes_alter = sum(person['Alter'] for person in personen) # Das "for ... in ..." hier ist eine "Generator Expression", Erklärung siehe unten.
durchschnittsalter = gesamtes_alter / len(personen)
print(f'Das Durchschnittsalter beträgt: {durchschnittsalter:.1f}')Warum Dictionaries verwenden?
Einige der Collections beherrschen Spezialfunktionen wie Sortierung, Unpacking, Comprehension, Generator Expression. Falls eine gegebene Art Collection die jeweilige Funktion nicht unterstützt, verwandelt man diese Collection einfach zunächst in eine Liste.
Listen lassen sich sortieren. Dies sind die einfachsten Varianten, noch ohne Angabe, auf welche spezielle Art sortiert werden soll:
meine_Liste = [3, 1, 4, 1, 5]
meine_Liste.sort() # Die Liste in sich sortieren; keine neue erzeugen
print(meine_Liste) # Ausgabe: [1, 1, 3, 4, 5]
sortierte_Liste = sorted(meine_Liste) # Gibt eine neue sortierte Liste zurück
print(sortierte_Liste) # Ausgabe: [1, 1, 3, 4, 5]Listen, Tupel sowie Objekte geeignet gebauter (Iterable
)
Klassen lassen sich direkt in Einzelvariablen zerlegen:
meine_Liste = [1, 2, 3]
a, b, c = meine_Liste # Unpacking
print(a, b, c) # Ausgabe: 1 2 3Unpacking benutzt man oft, wenn eine Funktion ein zusammengesetztes
Ergebnis liefert. So kam in der Einheit zu Datenvisualisierung schon die
Funktion scipy.stats.linregress zur Bestimmung der
Regressionsgeraden vor:
steigung, achsenabschnitt, _, _, _ = stats.linregress(x, y)Die Unterstriche besagen hier, dass man die betreffenden Daten keiner Variablen zuweisen will.
Listen lassen sich per List Comprehension
in neue Listen
verwandeln:
meine_Liste = [1, 2, 3, 4, 5]
quadrate = [x ** 2 for x in meine_Liste]
print(quadrate) # Ausgabe: [1, 4, 9, 16, 25]Man kann obendrein eine Bedingung angeben, welche Elemente benutzt werden sollen:
meine_Liste = [1, 2, 3, 4, 5]
quadrate = [x ** 2 for x in meine_Liste if x > 3]
print(quadrate) # Ausgabe: [16, 25]Mengen lassen sich per Set Comprehension
erzeugen:
meine_Liste = [-2, -1, 0, 1, 2]
quadrate = {x ** 2 for x in meine_Liste if x != 0}
print(quadrate) # Ausgabe: {1, 4}Mit Dictionaries klappt das ähnlich; bei der Dictionary
Comprehension
gibt es aber Schlüssel (meist k für
key
) und Werte (meist v für value
):
mein_Dict = {'a': 1, 'b': 2, 'c': 3}
quadrate = {k.upper() + 'bla': v ** 2 for k, v in mein_Dict.items() if v > 1}
print(quadrate) # Ausgabe: {'Bbla': 4, 'Cbla': 9}Eine Generator Expression ähnelt der List Comprehension, allerdings rechnet sie nicht alle Werte vorher aus und speichert die, sondern liefert auf Anfrage einen Wert nach dem anderen. Generator Expressions können bei großen Datensätzen extrem Speicherbedarf sparen und, wenn man vor dem Ende abbricht, auch Rechenzeit sparen. Umgekehrt kosten Generator Expressions mehr Rechenzeit als Listen, wenn die Ergebnisse im Programm mehrfach benötigt werden, denn mit Generator Expressions müssten sie dann auch mehrfach berechnet werden.
Generator Expressions stehen in runden statt eckigen Klammern; in einigen Situationen können die runden Klammern sogar weggelassen werden.
meine_Liste = [1, 2, 3, 4, 5, 6]
# Hier entsteht mit [...] erst die komplette Liste
for i in [x ** 2 for x in meine_Liste if x > 3]:
print(i) # Ausgabe: 16, 25, 36
# Hier wird (...) immer wieder nach dem nächsten Element gefragt
for i in (x ** 2 for x in meine_Liste if x > 3):
print(i) # Ausgabe: 16, 25, 36Das schon aus den Schleifen bekannte range verhält sich
ähnlich wie eine solche Generator Expression.