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).

Im UML-Klassendiagramm stellt sich diese Klasse folgendermaßen dar:

UML-Klassendiagramm für die Kunde mit dem Attribut name sowie Getter- und Setter-Methoden
UML-Klassendiagramm für die Kunde mit dem Attribut name sowie Getter- und Setter-Methoden

Vorteile 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 der name in vorname und nachname aufgeteilt werden. Es müssen nur die Getter- und Settermethoden angepasst werden, damit die Klasse 100% kompatibel zu bisherigen Aufrufen bleibt:
  • 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:
  • 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:

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:

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:
  • 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:

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”:

UML-Klassendiagramm für die Kunde mit dem Attribut name sowie Getter- und Setter-, diesmal mit Sichtbarkeitsmodifizierern
UML-Klassendiagramm für die Kunde mit dem Attribut name sowie Getter- und Setter-, diesmal mit Sichtbarkeitsmodifizierern

Der 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.

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:

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.

Ein Beispiel mit der Klasse Kunde und der Klasse Property
Ein Beispiel mit der Klasse Kunde und der Klasse Property

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).

Creative Commons Lizenzvertrag


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.

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: