W3docs

Python-Kapselung

Python-Kapselung erklärt: öffentliche, geschützte und private Member, Name Mangling sowie Getter/Setter mit @property.

Kapselung ist eine der vier Säulen der objektorientierten Programmierung. Sie bedeutet, die Daten (Attribute) eines Objekts und die Methoden, die mit diesen Daten arbeiten, in einer einzigen Einheit zu bündeln – und dabei zu kontrollieren, auf welche Teile des Objekts die Außenwelt zugreifen oder diese verändern kann.

Gut umgesetzt hält Kapselung den internen Zustand eines Objekts konsistent, verbirgt Implementierungsdetails, sodass diese später geändert werden können ohne den aufrufenden Code zu beeinflussen, und macht Klassen sicherer in der Verwendung.

Dieses Kapitel behandelt:

  • Was Kapselung ist und warum sie wichtig ist
  • Öffentliche, geschützte und private Member – und die Namenskonventionen, die Python verwendet
  • Name Mangling – wie __double_underscore-Attribute wirklich funktionieren
  • Getter und Setter mit @property
  • Ein praxisnahes Beispiel, das alles zusammenführt

Stellen Sie vor dem Lesen sicher, dass Sie mit Python-Klassen und -Objekten vertraut sind. Für die Zugriffskontrolle über berechnete Attribute siehe das eng verwandte Kapitel zu @property.

Warum Kapselung wichtig ist

Betrachten Sie ein Bankkonto. Intern wird ein Kontostand verwaltet. Wenn dieser Kontostand ein einfaches Attribut wäre, das jeder setzen könnte, hindert nichts einen Fehler (oder eine böswillige Aktion) daran, Folgendes zu tun:

account.balance = -9999999

Kapselung löst dieses Problem, indem sie den Kontostand hinter einer kontrollierten Schnittstelle verbirgt. Code außerhalb der Klasse kann nur über Methoden einzahlen oder abheben, die die Geschäftsregeln durchsetzen. Der interne Speicher ist ein Implementierungsdetail – Aufrufer greifen nie direkt darauf zu.

Die drei Vorteile, die sich daraus ergeben:

  1. Datenintegrität – Validierungslogik an einer Stelle, die jedes Mal durchgesetzt wird.
  2. Flexibilität – Die interne Darstellung kann geändert werden (z. B. Kontostände in Cent statt Euro speichern), ohne den aufrufenden Code anzupassen.
  3. Geringere Kopplung – Aufrufer sind nur von der öffentlichen Schnittstelle abhängig, nicht davon, wie die Klasse intern funktioniert.

Zugriffsebenen: Public, Protected und Private

Python hat keine Zugriffsmodifikatoren wie private- oder public-Schlüsselwörter. Stattdessen verwendet es eine Namenskonvention, um die Absicht zu signalisieren:

PräfixBeispielZugriffsebeneBedeutung
Kein PräfixbalancePublicZur Verwendung durch jeden gedacht
Einfacher Unterstrich __balanceProtectedFür den internen Gebrauch und Unterklassen; von außen nutzbar, aber nicht empfohlen
Doppelter Unterstrich ____pinPrivateNur für diese Klasse; Python benennt ihn aktiv um, um einfachen Zugriff zu verhindern

Dies sind Konventionen und Mechanismen, keine strengen Regeln, die von einem Compiler durchgesetzt werden. Python vertraut darauf, dass Entwickler das Signal respektieren.

Öffentliche Member

Öffentliche Attribute und Methoden bilden die offizielle Schnittstelle der Klasse – den Teil, den Aufrufer verwenden sollen:

class BankAccount:
    account_type = 'savings'   # public class attribute

    def __init__(self, owner, balance):
        self.owner = owner     # public instance attribute

    def deposit(self, amount):
        pass                   # public method

Es ist keine besondere Benennung erforderlich. Jeder Code kann ein öffentliches Member frei lesen oder schreiben.

Geschützte Member (einfacher Unterstrich _)

Ein einzelner führender Unterstrich ist ein Signal, das besagt: „Dies ist ein internes Detail – verlassen Sie sich bitte nicht darauf von außerhalb der Klasse." Python erzwingt dies nicht; es ist reine Konvention:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self._balance = balance   # protected — internal, but subclasses may need it

    def _validate_amount(self, amount):   # protected helper
        return isinstance(amount, (int, float)) and amount > 0

_balance ist weiterhin als account._balance von außen zugänglich, aber der Unterstrich warnt andere Entwickler (und Linter), dass sie den beabsichtigten Vertrag verletzen.

Ein häufiger Anwendungsfall: Eine Basisklasse speichert Daten in einem _-Attribut, damit Unterklassen es lesen können, während es für nicht verwandten Code verborgen bleibt.

Private Member (doppelter Unterstrich __)

Ein doppelter führender Unterstrich löst Name Mangling aus – Python benennt das Attribut intern in _KlassenName__Attribut um. Dadurch wird der versehentliche Zugriff von außen deutlich erschwert:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self._balance = balance
        self.__pin = 1234       # private — not meant to be touched at all

    def verify_pin(self, pin):
        return pin == self.__pin

Von außerhalb der Klasse:

acc = BankAccount('Alice', 1000)
print(acc.owner)      # Alice   — public, fine
print(acc._balance)   # 1000    — protected, works but frowned upon
print(acc.__pin)      # AttributeError: 'BankAccount' object has no attribute '__pin'

Das Attribut existiert weiterhin, jedoch unter einem anderen Namen. Siehe den nächsten Abschnitt, um herauszufinden, wie man es findet.

Name Mangling

Wenn Python self.__name innerhalb einer Klassendefinition sieht, schreibt es dies intern in self._KlassenName__name um. Dies ist Name Mangling. Der Zweck ist es, versehentliche Namenskollisionen in Unterklassen zu vermeiden – nicht echte Sicherheit zu bieten.

class Counter:
    def __init__(self):
        self.__count = 0

    def increment(self):
        self.__count += 1

    def value(self):
        return self.__count

c = Counter()
c.increment()
c.increment()
print(c.value())          # 2

# Direct access fails:
# print(c.__count)        # AttributeError

# But mangled name still works if you know it:
print(c._Counter__count)  # 2

Alle Attribute können mit vars() oder dir() untersucht werden, um den umbenannten Namen zu entdecken:

print(list(vars(c)))
# ['_Counter__count']

Name Mangling und Vererbung

Name Mangling ist besonders nützlich bei der Vererbung. Ohne es könnte eine Unterklasse versehentlich ein privates Attribut ihrer Elternklasse überschreiben, indem sie denselben Namen verwendet. Mit Mangling erhält jede Klasse ihren eigenen Namensraum:

class Base:
    def __init__(self):
        self.__secret = 'base'

    def reveal(self):
        return self.__secret    # accesses _Base__secret

class Child(Base):
    def __init__(self):
        super().__init__()
        self.__secret = 'child'  # stored as _Child__secret, not the same thing

    def reveal_child(self):
        return self.__secret     # accesses _Child__secret

c = Child()
print(c.reveal())        # base   — Base.reveal() reads _Base__secret
print(c.reveal_child())  # child  — Child.reveal_child() reads _Child__secret

Beide Attribute koexistieren ohne Kollision, was ohne Name Mangling nicht möglich wäre.

Getter und Setter mit @property

In vielen Sprachen schreibt man explizite get_x()- und set_x()-Methoden. Python bietet einen saubereren Ansatz: Der @property-Dekorator ermöglicht es, eine Methode wie ein normales Attribut verfügbar zu machen, sodass der aufrufende Code lesbar bleibt, während die volle Kontrolle über Lesen und Schreiben erhalten bleibt.

Einfacher Getter

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

Aufrufer lesen t.celsius, nicht t.celsius(). Das @property macht den Methodenaufruf unsichtbar:

t = Temperature(25)
print(t.celsius)   # 25  — no parentheses needed

Setter mit Validierung hinzufügen

Kombinieren Sie @property mit einem .setter, um Werte vor dem Speichern zu validieren:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError('Temperature below absolute zero')
        self._celsius = value

    @property
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.celsius)      # 25
print(t.fahrenheit)   # 77.0

t.celsius = 100
print(t.fahrenheit)   # 212.0

t.celsius = -300      # ValueError: Temperature below absolute zero

fahrenheit ist eine schreibgeschützte berechnete Property – da kein Setter definiert ist, löst Python einen AttributeError aus, wenn versucht wird, ihr einen Wert zuzuweisen.

Warum @property gegenüber einfachen Gettern/Settern bevorzugen?

Man kann mit einem einfachen öffentlichen Attribut beginnen und es später auf eine Property upgraden, ohne aufrufenden Code zu ändern:

# v1 — plain attribute
class Circle:
    def __init__(self, radius):
        self.radius = radius

# v2 — property with validation, same public interface
class Circle:
    def __init__(self, radius):
        self.radius = radius   # still works from the caller's point of view

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError('Radius cannot be negative')
        self._radius = value

Aufrufer, die c.radius = 5 geschrieben haben, funktionieren weiterhin unverändert. Nur das Verhalten ändert sich – der Wert wird nun validiert.

Eine vollständige Referenz zu @property einschließlich Deletern finden Sie unter Python @property.

Ein vollständiges Beispiel: Benutzerkonto

Das folgende Beispiel zeigt alle drei Zugriffsebenen, die gemeinsam in einer realistischen Klasse eingesetzt werden:

class UserAccount:
    def __init__(self, username, password):
        self.username = username           # public
        self._login_attempts = 0           # protected — subclasses may need this
        self.__password_hash = self.__hash(password)  # private

    def __hash(self, password):
        """Private helper — implementation detail, may change."""
        return hash(password)

    def check_password(self, password):
        """Public method — part of the official interface."""
        return self.__hash(password) == self.__password_hash

    def login(self, password):
        if self._login_attempts >= 3:
            return 'Account locked'
        if self.check_password(password):
            self._login_attempts = 0
            return 'Login successful'
        self._login_attempts += 1
        return f'Wrong password ({self._login_attempts}/3)'


user = UserAccount('alice', 'secret123')
print(user.login('bad'))         # Wrong password (1/3)
print(user.login('bad'))         # Wrong password (2/3)
print(user.login('bad'))         # Wrong password (3/3)
print(user.login('secret123'))   # Account locked

Beachten Sie:

  • username ist öffentlich – es ist in Ordnung, wenn jeder es liest.
  • _login_attempts ist geschützt – eine ThrottledAccount-Unterklasse könnte es lesen, um eine intelligentere Logik zu implementieren.
  • __password_hash und __hash() sind privat – die Passwortspeicherstrategie ist rein intern. Aufrufer haben keinen Grund, sie zu sehen, und wenn später auf bcrypt umgestellt wird, müssen nur diese beiden Dinge geändert werden.

Kapselung vs. andere OOP-Säulen

Kapselung ist eines von vier OOP-Prinzipien:

PrinzipKurzdefinition
KapselungDaten + Methoden bündeln; interne Details verbergen
VererbungEine Klasse eine andere Klasse wiederverwenden und erweitern lassen
PolymorphismusVerschiedene Typen auf denselben Methodenaufruf reagieren lassen
AbstraktionEine vereinfachte Schnittstelle bereitstellen; Komplexität verbergen

Siehe Python-Vererbung, Python-Polymorphismus und abstrakte Python-Klassen für die anderen Säulen.

Kurzreferenz

KonventionWas sie signalisiertVon Python erzwungen?
namePublic – frei verwendbarNein (immer zugänglich)
_nameProtected – interne VerwendungNein (zugänglich, aber konventionell nicht empfohlen)
__namePrivate – nur diese KlasseTeilweise – Name wird zu _ClassName__name umgeschrieben
@propertyKontrollierter LesezugriffJa – Getter/Setter/Deleter-Hooks
@name.setterKontrollierter Schreibzugriff mit ValidierungJa

Übungen

Übung
Was signalisiert ein einzelner führender Unterstrich (z. B. `_balance`) in Python?
Was signalisiert ein einzelner führender Unterstrich (z. B. `_balance`) in Python?
Was this page helpful?