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 = -9999999Kapselung 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:
- Datenintegrität – Validierungslogik an einer Stelle, die jedes Mal durchgesetzt wird.
- Flexibilität – Die interne Darstellung kann geändert werden (z. B. Kontostände in Cent statt Euro speichern), ohne den aufrufenden Code anzupassen.
- 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äfix | Beispiel | Zugriffsebene | Bedeutung |
|---|---|---|---|
| Kein Präfix | balance | Public | Zur Verwendung durch jeden gedacht |
Einfacher Unterstrich _ | _balance | Protected | Für den internen Gebrauch und Unterklassen; von außen nutzbar, aber nicht empfohlen |
Doppelter Unterstrich __ | __pin | Private | Nur 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 methodEs 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.__pinVon 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) # 2Alle 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__secretBeide 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._celsiusAufrufer lesen t.celsius, nicht t.celsius(). Das @property macht den Methodenaufruf unsichtbar:
t = Temperature(25)
print(t.celsius) # 25 — no parentheses neededSetter 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 + 32t = 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 zerofahrenheit 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 = valueAufrufer, 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 lockedBeachten Sie:
usernameist öffentlich – es ist in Ordnung, wenn jeder es liest._login_attemptsist geschützt – eineThrottledAccount-Unterklasse könnte es lesen, um eine intelligentere Logik zu implementieren.__password_hashund__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:
| Prinzip | Kurzdefinition |
|---|---|
| Kapselung | Daten + Methoden bündeln; interne Details verbergen |
| Vererbung | Eine Klasse eine andere Klasse wiederverwenden und erweitern lassen |
| Polymorphismus | Verschiedene Typen auf denselben Methodenaufruf reagieren lassen |
| Abstraktion | Eine vereinfachte Schnittstelle bereitstellen; Komplexität verbergen |
Siehe Python-Vererbung, Python-Polymorphismus und abstrakte Python-Klassen für die anderen Säulen.
Kurzreferenz
| Konvention | Was sie signalisiert | Von Python erzwungen? |
|---|---|---|
name | Public – frei verwendbar | Nein (immer zugänglich) |
_name | Protected – interne Verwendung | Nein (zugänglich, aber konventionell nicht empfohlen) |
__name | Private – nur diese Klasse | Teilweise – Name wird zu _ClassName__name umgeschrieben |
@property | Kontrollierter Lesezugriff | Ja – Getter/Setter/Deleter-Hooks |
@name.setter | Kontrollierter Schreibzugriff mit Validierung | Ja |