Nach dieser Lektion kennzeichnen Sie interne Attribute auf Python-Art, sagen vorher, was Name Mangling aus __name macht, und schützen ein Attribut mit einer Property und einem prüfenden Setter.
vars(obj) zeigt die Attribute, die auf einem Objekt gespeichert sind. Was gibt das aus?
class Vault:
def __init__(self):
self.__pin = "1234"
v = Vault()
print([name for name in vars(v)])
Üben · Aufgabe 5 von 7
Ordnen Sie jedem Namen, geschrieben in einer Klasse, seine Bedeutung zu.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Base und Child setzen beide self.__id. Was gibt das aus?
class Base:
def __init__(self):
self.__id = "base"
def base_id(self):
return self.__id
class Child(Base):
def __init__(self):
super().__init__()
self.__id = "child"
def child_id(self):
return self.__id
c = Child()
print(c.base_id(), c.child_id())
Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie eine Klasse Temperature mit einer Property celsius. Ihr Setter löst bei Werten unter -273.15 ValueError aus und speichert den Wert in self._celsius. Ergänzen Sie eine nur lesbare Property fahrenheit, berechnet als celsius * 9 / 5 + 32. Temperature(25) ergibt 25 und 77.0; celsius auf -300 zu setzen, wird abgelehnt.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Ein Produkt mit geschütztem Preis und Bestand
price ist eine Property mit prüfendem Setter, also wird schon der Preis aus __init__ geprüft und gerundet. stock ist eine nur lesbare Property: Ändern lässt sich der Bestand nur über restock() und sell(), die ihn stimmig halten. Die letzte Zeile liest lamp._stock direkt, um zu zeigen, dass der Unterstrich eine Konvention ist und kein Schloss.
main.py
class Product:
"""Price is checked on every change; stock changes only through methods."""
def __init__(self, name: str, price: float, stock: int = 0) -> None:
self.name = name
self.price = price # goes through the setter below
self._stock = stock
@property
def price(self) -> float:
return self._price
@price.setter
def price(self, value: float) -> None:
if value <= 0:
raise ValueError(f"price must be positive, got {value}")
self._price = round(value, 2)
@property
def stock(self) -> int:
"""Read-only: change it with restock() and sell()."""
return self._stock
def restock(self, amount: int) -> None:
self._stock += amount
def sell(self, amount: int) -> None:
if amount > self._stock:
raise ValueError(f"only {self._stock} {self.name} left")
self._stock -= amount
lamp = Product("lamp", 24.999, stock=3)
print(lamp.price, lamp.stock)
lamp.sell(2)
lamp.restock(5)
print(lamp.stock)
try:
lamp.price = -5
except ValueError as err:
print("refused:", err)
try:
lamp.stock = 99 # type: ignore[misc]
except AttributeError as err:
print("refused:", err)
try:
lamp.sell(10)
except ValueError as err:
print("refused:", err)
print(lamp.price, lamp._stock) # the underscore is a convention, not a lock
Ausführen mit
python main.py
Ausgabe
25.0 3
6
refused: price must be positive, got -5
refused: property 'stock' of 'Product' object has no setter
refused: only 6 lamp left
25.0 6
Aus 24.999 wurde 25.0: Der Setter rundet, und __init__ lief durch ihn.
Die Zuweisung an lamp.stock scheitert, weil stock keinen Setter hat.
Auch mypy lehnt die Zuweisung an eine nur lesbare Property ab; der Kommentar type: ignore lässt die Vorführung trotzdem laufen.
lamp._stock funktioniert von außen: Nichts wird erzwungen, der Unterstrich bittet nur.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übungen
Übung 1 von 2
Eine geprüfte Temperatur
Machen Sie aus celsius eine Property. Ihr Setter löst bei Werten unter ABSOLUTE_ZERO ValueError aus und speichert gültige Werte in self._celsius; auch Temperature(-300) muss scheitern. Ergänzen Sie eine Property fahrenheit (celsius * 9 / 5 + 32) mit einem Setter, der zurückrechnet und celsius setzt, damit dieselbe Prüfung greift: fahrenheit auf 212 setzt celsius auf 100.0.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Schreiben Sie @property def celsius(self), das self._celsius liefert, und dann @celsius.setter def celsius(self, value), das prüft und speichert.
Hinweis 2
Lassen Sie self.celsius = celsius in __init__ stehen: Es ruft jetzt den Setter auf, also wird Temperature(-300) abgelehnt.
Hinweis 3
Der Setter von fahrenheit rechnet um und weist self.celsius = (value - 32) * 5 / 9 zu; so greift die Prüfung erneut.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
Schreiben Sie SecureBox neu. Speichern Sie den Code als self.__code, damit er umbenannt wird. Zählen Sie Fehlversuche in self._attempts und zeigen Sie sie über eine nur lesbare Property attempts. Ergänzen Sie eine nur lesbare Property locked, die nach MAX_ATTEMPTS Fehlversuchen True ist. open(code) liefert für den richtigen Code True, aber immer False, sobald gesperrt. Ein falscher Code zählt einen Versuch.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Schreiben Sie in __init__ self.__code = code und self._attempts = 0. Innerhalb der Klasse verwenden Sie weiter self.__code.
Hinweis 2
Eine nur lesbare Property ist eine @property ohne Setter: @property def attempts(self) -> int: return self._attempts.
Hinweis 3
Prüfen Sie in open zuerst self.locked. Liefern Sie für den richtigen Code True; sonst erhöhen Sie self._attempts um eins und liefern False.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
from main import SecureBox
def test_opens():
"""Der richtige Code öffnet die Box"""
assert SecureBox("4711").open("4711") is True, "open mit dem richtigen Code lieferte nicht True"
def test_counts_wrong_tries():
"""Falsche Codes werden gezählt"""
box = SecureBox("4711")
box.open("0000")
box.open("1234")
assert box.attempts == 2, f"nach zwei falschen Codes ist attempts {box.attempts!r}"
def test_locks():
"""Nach drei falschen Codes scheitert auch der richtige"""
box = SecureBox("4711")
for _ in range(3):
box.open("0000")
assert box.locked is True, "locked ist nach drei falschen Codes nicht True"
assert box.open("4711") is False, "eine gesperrte Box ließ sich mit dem richtigen Code öffnen"
def test_attempts_read_only():
"""attempts lässt sich von außen nicht zurücksetzen"""
box = SecureBox("4711")
try:
box.attempts = 0 # type: ignore[misc]
except AttributeError:
pass
else:
assert False, "box.attempts = 0 wurde angenommen; machen Sie attempts zu einer Property ohne Setter"
def test_code_is_mangled():
"""Der Code liegt in _SecureBox__code"""
names = sorted(vars(SecureBox("4711")))
assert names == ["_SecureBox__code", "_attempts"], f"die Attribute des Objekts sind {names!r}"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.py
Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):
class Account:
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
self.balance = value
acct = Account()
acct.balance = 10
Was Python ausgibt
RecursionError: maximum recursion depth exceeded
Warum, und die Lösung
self.balance = value im Setter ruft den Setter erneut auf, der ihn wieder aufruft, bis Python aufgibt. Die Property ist das öffentliche Gesicht; der Wert braucht einen eigenen Speicherplatz unter anderem Namen. Schreiben Sie self._balance = value im Setter und return self._balance im Getter. Probieren Sie dieses Beispiel in Ihrem eigenen Python: Im Browser erschöpft die endlose Rekursion den Stack der Seite.
AttributeError: property 'area' of 'Circle' object has no setter
Warum, und die Lösung
area hat nur einen Getter und lässt sich nicht zuweisen. Für einen berechneten Wert ist das meist gewollt: Ändern Sie stattdessen den Radius. Soll die Zuweisung wirklich gehen, ergänzen Sie eine Methode mit @area.setter, die den neuen Radius berechnet.
Einen umbenannten Namen von außen lesen
class Vault:
def __init__(self):
self.__pin = "1234"
print(Vault().__pin)
Was Python ausgibt
AttributeError: 'Vault' object has no attribute '__pin'
Warum, und die Lösung
Umgeschrieben wird nur Code im Klassenrumpf; außerhalb wird __pin so gesucht, wie es dasteht, und das Objekt hat kein solches Attribut, sondern _Vault__pin. Braucht Code von außen den Wert, bieten Sie eine Methode oder eine nur lesbare Property an. Wenn nicht, ist genau dieser Fehler der Sinn der Sache.
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.
Kernideen
Privatheit ist eine Konvention
Python kennt keine privaten Attribute. Ein führender Unterstrich wie in self._balance ist ein Signal an andere Programmierer: intern, kann sich ändern, verlassen Sie sich von außen nicht darauf. Nichts hindert Code daran, es zu lesen oder zu ändern, aber sauberer Code respektiert das Signal. Öffentliche Attribute sind in Python normal. Beginnen Sie mit einem einfachen Attribut und machen Sie erst dann eine Property daraus, wenn Sie Kontrolle brauchen: Aufrufer schreiben weiter obj.balance und merken nichts davon.
Name Mangling für __name
Im Klassenrumpf wird ein Name mit zwei führenden und höchstens einem abschließenden Unterstrich, etwa self.__pin, in _ClassName__pin umgeschrieben. Von außen löst obj.__pin einen AttributeError aus, aber obj._Account__pin funktioniert weiterhin; Sicherheit ist das also nicht. Der Zweck sind Namenskonflikte: Eine Unterklasse mit eigenem __pin bekommt _Sub__pin, und die beiden überschreiben einander nie. Namen mit zwei Unterstrichen an beiden Enden wie __init__ werden nicht umbenannt, ebenso wenig Namen mit nur einem Unterstrich.
Properties: Attribute, die Code ausführen
@property macht aus einer Methode ein Attribut: Das Lesen von acct.balance ruft die Methode auf, ohne Klammern. Mit einem Setter, einer zweiten gleichnamigen Methode mit @balance.setter, führt acct.balance = -5 diese aus, etwa um falsche Werte abzulehnen. Der Wert selbst liegt in einem Attribut mit Unterstrich wie self._balance; self.balance im Setter riefe den Setter erneut auf. Eine Property ohne Setter ist nur lesbar, eine Zuweisung löst AttributeError aus. Weist __init__ self.balance zu, durchläuft schon der erste Wert die Prüfung.