Das Konzept der Kapselung in Python
https://oer-informatik.de/python_oop_kapselung
tl/dr; (ca. 15 min Lesezeit): Wer gut änderbaren Code programmieren will, der versteckt Implementierungsdetails wie den Zustand von Objekten. Veröffentlicht wird nur, was wirklich benötigt wird. In Python ist es nicht ganz einfach (und eher unüblich), den Rest wegzukapseln. Aber wie lässt sich Kapselung in Python umsetzen?
Grundidee der Kapselung
Eines der Grundprinzipien der Objektorientierung ist die Kapselung: Auf den Zustand eines Objekts sollte nur über Methoden des Objekts, nicht jedoch direkt über die Attribute zugegriffen werden. Es werden für lesende Operationen Getter-Methoden, für schreibende Operationen Setter-Methoden implementiert. In einem ersten Entwurf können diese folgendermaßen aussehen (auf die spezielle Python-Implementierung gehen wir weiter unten ein).
class Kunde:
def __init__(self):
self.name: str
def get_name(self) -> str:
return self.name
def set_name(self, name: str):
self.name = name
mein_kunde = Kunde()
mein_kunde.set_name("Max Mustermann")
Im UML-Klassendiagramm stellt sich diese Klasse folgendermaßen dar:

name
sowie Getter- und Setter-MethodenVorteile der Kapselung
Welche Vorteile ergeben sich aus dem Implementierungsaufwand für Getter- und Settermethoden?
- die konkrete Implementierung und Datenstruktur kann geändert werden, ohne dass die bisherigen Objektaufrufe angepasst werden müssen. Beispielsweise kann in der Klasse
Kunde
oben dername
invorname
undnachname
aufgeteilt werden. Es müssen nur die Getter- und Settermethoden angepasst werden, damit die Klasse 100% kompatibel zu bisherigen Aufrufen bleibt:
class Kunde:
def __init__(self):
self.vorname: str
self.nachname: str
def get_name(self) -> str:
return self.vorname + " " + self.nachname
def set_name(self, name: str):
self.vorname, self.nachname = name.split(" ", 1)
- es können (auch im Nachgang) Validierungen und Plausibilisierungen vorgenommen werden. Beispielsweise kann eine Eingabe, bevor sie als Attributwert gespeichert wird von Whitespaces am Rand befreit werden oder der Umgang mit
None
-Werten bei Ausgabe explizit geregelt werden:
def set_vorname(self, vorname: str):
self.vorname = vorname.strip() # entfernt Whitespaces am Start/Ende
def get_vorname(self) -> str:
return "unknown" if self.vorname is None else self.vorname
- Attribute, die sich aus anderen Attributen berechnen / ergeben können auf eine gemeinsame Datenbasis zugreifen. Wenn später die Datenbasis geändert werden soll, kann dies ohne Probleme getan werden:
class Rechnung:
def __init__(self):
self.rechnungssumme_netto: float = 0
self.mwst_proz: float = 19.0
def set_rechnungssumme_netto(self, rechnungssumme_netto:float):
self.rechnungssumme_netto = rechnungssumme_netto
def get_rechnungssumme_netto(self)-> float:
return self.rechnungssumme_netto
def get_rechnungssumme_brutto(self)-> float:
return self.rechnungssumme_netto*(100+self.mwst_proz)/100
Erste Umsetzung der Kapselung in Python: wir verstecken so gut es geht
Die Klasse aus obigem Beispiel sieht zunächst - ohne versteckte Attribute - so aus:
class Kunde:
def __init__(self):
self.name: str
def get_name(self) -> str:
return self.name
def set_name(self, name: str):
self.name = name
mein_kunde = Kunde()
mein_kunde.set_name("Max Mustermann")
Im Gegensatz zu anderen Programmiersprachen bietet Python keine Möglichkeit, den Zugriff auf Instanzvariablen für Aufrufe außerhalb des Objekts zu sperren. Alle Instanzvariablen sind zunächst öffentlich (public).
Um den Zugriff darauf zu erschweren gibt es eine Reihe von Konventionen:
- Instanzvariablen, die mit einem Underscore beginnen, gelten als privat (sollen also nur intern, aus dem Objekt selbst gelesen und verändert werden). Der Zugriff von außen ist weiterhin möglich, aber die Namenskonvention verrät: es ist nicht mehr gewünscht. Beides zeigt das folgende Beispiel:
class Kunde:
def __init__(self, name: str) -> None:
self.set_name(name)
def get_name(self) -> str:
return self._name
def set_name(self, name: str):
self._name = name
mein_kunde = Kunde("Hans Wurst")
print(mein_kunde.get_name())
mein_kunde._name = "Paulchen Panther" # Zugriff direkt möglich
print(mein_kunde._name) # Zugriff direkt möglich
- Instanzvariablen, die mit zwei Underscore beginnen, können von außen nur über einen abgeänderten Namen, dem ein Unterstrich und der Klassenname vorangestellt sein muss, erreicht werden. Auch hier ist der Zugriff weiterhin möglich, jedoch mit angepasstem Namen:
class Kunde:
def __init__(self, name: str) -> None:
self.__name: str
self.set_name(name)
def get_name(self) -> str:
return self.__name
def set_name(self, name: str):
self.__name = name
mein_kunde = Kunde("Hans Wurst")
print(mein_kunde.get_name())
mein_kunde._Kunde__name = "Paulchen Panther" # Zugriff über den veränderten
# Attributnamen ist direkt möglich
print(mein_kunde.get_name())
print(mein_kunde._name) # Zugriff über den unveränderten
# Attributnamen nicht möglich
Beide Fälle sind jedoch nur mehr oder weniger gut “versteckt” und nicht wirklich private. Im UML-Klassendiagramm kennzeichnet man private Attribute mit dem Sichtbarkeitsmodifizierer “Minus”, public-Methoden mit “Plus”:

name
sowie Getter- und Setter-, diesmal mit SichtbarkeitsmodifizierernDer Königsweg der Kapselung in Python: Properties
Um den Zugriff über eine zentrale Schnittstelle zu erhalten werden in Python properties genutzt. Hierzu werden neben dem Attribut optional auch die Getter- und Settermethoden über doppelte Unterstriche versteckt. Das von außen sichtbare Attribut wird als sogenannte property angelegt. Einer Property werden die jeweiligen Getter- und Settermethodennamen als Parameter übergeben. So weiß Python, welche Methode im Fall eines lesenden und eines schreibenden Zugriffs genutzt werden muss.
class Kunde:
def __init__(self, name: str) -> None:
self.__name: str
self.__set_name(name)
def __get_name(self) -> str:
print("Lese den Namen "+self.__name+" aus")
return self.__name
def __set_name(self, name: str):
print("Ändere den Namen von "+self.__name+" zu "+ name)
self.__name = name
def __del_name(self):
print("Lösche den Namen")
self.__name = ""
# del self.__name würde das ganze Attribut löschen
name = property(fget=__get_name, fset=__set_name, fdel=__del_name, doc="Name eines Kunden")
mein_kunde = Kunde("Hans Wurst")
mein_kunde._Kunde__set_name("Johnny Rotten")
mein_kunde.name="Sid Vicious"
del mein_kunde.name
print(mein_kunde.name)
In obigen Beispiel wurde zusätzlich noch ein “Deleter” und ein Docstring für die property übergeben.
Es gibt in Python noch einen eleganteren Weg, properties zu implementieren: wir dekorieren die Getter- und Setter-Methoden. Das ist eine Syntaxerweiterung von Python, um Code übersichtlicher zu machen. Wichtig hierbei ist, dass wir zuerst die Gettermethode implementieren, mit @property
dekorieren (es vor die Methode schreiben) und mit dem Namen der Property versehen:
class Kunde:
def __init__(self, name: str) -> None:
self.__name : str
self.__name = name
@property
def name(self) -> str:
print("Lese den Namen "+self.__name+" aus")
return self.__name
@name.setter
def name(self, name: str):
print("Ändere den Namen von "+self.__name+" zu "+ name)
self.__name = name
mein_kunde = Kunde("Hans Wurst")
mein_kunde.name="Sid Vicious"
print(mein_kunde.name)
print(type(mein_kunde.name))
Was passiert im Hintergrund? Ein Decorator ist ein verschachtelter Funktionsaufruf. Immer wenn name
aufgerufen wird, wird statt name
zunächst property
aufgerufen, was dann wiederum name
aufruft. Das wird vielleicht etwas deutlicher, wenn man die synonyme Schreibweise hierfür darstellt.
ist gleichbedeutend mit
Moment - das ist ja genau die Schreibweise von oben ohne Decorator und mit einem Parameter (und angepasstem Methodennamen)? Decorator sind also kein Zauberwerk sondern nur syntactic sugar: eine Vereinfachung in der Schreibweise.
Wer sich noch nicht lange mit Python beschäftigt hat mag etwa überrascht sein, dass man auch Funktionen (oder hier: Methoden) als Parameter übergeben kann. Es lohnt sich, sich mit dem Gedanken anzufreunden, da es ein sehr mächtiges Werkzeug ist.
Folgt man dem Gedanken, so können wir auch die synonyme Schreibweise für den Setter formulieren:
ist gleichbedeutend mit (Funktionsnamen angepasst, da dies sonst zu Problemen führt):
name
ist ja zu diesem Zeitpunkt bereits eine property und verfügt demnach über eigene Methoden (getter, setter, deleter). Diese Methoden dekorieren jetzt die selbst implementierte Setter-Methode (umklammern diese also und rufen sie auf). Auch bei Protperty handelt es sich nur um eine Klasse, deren Objekte Zustand (Referenzen auf die Funktionen) und Verhalten (getter/setter-Funktionen) haben.

Weitere Literatur zu object
Quellen und offene Ressourcen (OER)
Die Ursprungstexte (als Markdown), Grafiken und zugrunde liegende Diagrammquelltexte finden sich (soweit möglich in weiterbearbeitbarer Form) in folgendem git-Repository:
https://gitlab.com/oer-informatik/python-basics/erste-schritte-mit-python.
Sofern nicht explizit anderweitig angegeben sind sie zur Nutzung als Open Education Resource (OER) unter Namensnennung (H. Stein, oer-informatik.de) freigegeben gemäß der Creative Commons Namensnennung 4.0 International Lizenz (CC BY 4.0).
Hinweis zur Nachnutzung
Dieses Werk und dessen Inhalte sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Erste Schritte mit Python” von Hannes Stein, Lizenz: CC BY 4.0. Die Quellen dieses Werks sind verfügbar auf GitLab.