Zum Inhalt springen
aviral gupta

// I2.4 · ca. 30 Min. · Aufbau

Private Namen und Properties

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.

Lektion 4 von 6 in I2 Klassen

Danach können Sie

  • Interne Attribute mit einem Unterstrich kennzeichnen, im Wissen, dass Python Privatheit nicht erzwingt
  • Vorhersagen, wie Name Mangling __name in einer Klasse umschreibt, und warum es das gibt
  • Eine @property mit prüfendem Setter schreiben und eine berechnete Property, die nur gelesen werden kann
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus den letzten Lektionen: Nichts hindert Code von außen daran, ein Attribut zu setzen. Was gibt das aus?

    class Account:
        def __init__(self, balance):
            self.balance = balance
    
    
    acct = Account(100)
    acct.balance = -500
    print(acct.balance)
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: balance ist jetzt eine Property. Was gibt das aus?

    class Account:
        def __init__(self, balance):
            self.balance = balance
    
        @property
        def balance(self):
            return self._balance
    
        @balance.setter
        def balance(self, value):
            if value < 0:
                raise ValueError("balance must not be negative")
            self._balance = value
    
    
    acct = Account(100)
    try:
        acct.balance = -500
    except ValueError as err:
        print("refused:", err, "- balance", acct.balance)
  3. Üben · Aufgabe 3 von 7

    Füllen Sie die Zeile aus, damit Circle(2).area ohne Klammern die Fläche liefert.

    class Circle:
        def __init__(self, radius):
            self.radius = radius
    
        ____
        def area(self):
            return 3.14 * self.radius ** 2
  4. Üben · Aufgabe 4 von 7

    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)])
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jedem Namen, geschrieben in einer Klasse, seine Bedeutung zu.

  6. 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())
  7. 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
  1. 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.

  2. Hinweis 2

    Lassen Sie self.celsius = celsius in __init__ stehen: Es ruft jetzt den Setter auf, also wird Temperature(-300) abgelehnt.

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

class Temperature:
    ABSOLUTE_ZERO = -273.15

    def __init__(self, celsius: float) -> None:
        self.celsius = celsius

    @property
    def celsius(self) -> float:
        return self._celsius

    @celsius.setter
    def celsius(self, value: float) -> None:
        if value < self.ABSOLUTE_ZERO:
            raise ValueError(f"{value} is below absolute zero")
        self._celsius = value

    @property
    def fahrenheit(self) -> float:
        return self._celsius * 9 / 5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value: float) -> None:
        self.celsius = (value - 32) * 5 / 9
Auf dem eigenen Computer ausführen

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.

main.py

class Temperature:
    ABSOLUTE_ZERO = -273.15

    def __init__(self, celsius: float) -> None:
        self.celsius = celsius

test_main.py

from main import Temperature


def test_reads_celsius():
    """celsius liefert den übergebenen Wert"""
    got = Temperature(21.5).celsius
    assert got == 21.5, f"Temperature(21.5).celsius ist {got!r}"


def test_fahrenheit():
    """20 Grad Celsius sind 68 Grad Fahrenheit"""
    got = getattr(Temperature(20), "fahrenheit", None)
    assert got == 68.0, f"Temperature(20).fahrenheit ist {got!r}, erwartet: 68.0"


def test_set_fahrenheit():
    """fahrenheit auf 212 setzt celsius auf 100.0"""
    t = Temperature(0)
    t.fahrenheit = 212
    assert t.celsius == 100.0, f"nach t.fahrenheit = 212 ist t.celsius {t.celsius!r}"


def test_rejects_in_init():
    """Temperature(-300) löst ValueError aus"""
    try:
        Temperature(-300)
    except ValueError:
        pass
    else:
        assert False, "Temperature(-300) wurde angenommen"


def test_rejects_and_keeps_value():
    """Ein abgelehnter Wert lässt den alten stehen"""
    t = Temperature(10)
    try:
        t.celsius = -300
    except ValueError:
        pass
    else:
        assert False, "t.celsius = -300 wurde angenommen"
    assert t.celsius == 10, f"nach dem abgelehnten Wert ist t.celsius {t.celsius!r}, erwartet: 10"


def test_stored_privately():
    """Der Wert liegt in _celsius"""
    t = Temperature(5)
    assert vars(t) == {"_celsius": 5}, f"die Attribute des Objekts sind {vars(t)!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):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 2

Eine Box mit geheimem Code

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
  1. Hinweis 1

    Schreiben Sie in __init__ self.__code = code und self._attempts = 0. Innerhalb der Klasse verwenden Sie weiter self.__code.

  2. Hinweis 2

    Eine nur lesbare Property ist eine @property ohne Setter: @property def attempts(self) -> int: return self._attempts.

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

class SecureBox:
    MAX_ATTEMPTS = 3

    def __init__(self, code: str) -> None:
        self.__code = code
        self._attempts = 0

    @property
    def attempts(self) -> int:
        return self._attempts

    @property
    def locked(self) -> bool:
        return self._attempts >= self.MAX_ATTEMPTS

    def open(self, code: str) -> bool:
        if self.locked:
            return False
        if code == self.__code:
            return True
        self._attempts += 1
        return False
Auf dem eigenen Computer ausführen

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.

main.py

class SecureBox:
    MAX_ATTEMPTS = 3

    def __init__(self, code: str) -> None:
        self.code = code
        self.attempts = 0

    def open(self, code: str) -> bool:
        return code == self.code

test_main.py

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

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Die Property im eigenen Setter zuweisen

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.

Einer nur lesbaren Property zuweisen

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius ** 2


c = Circle(2)
c.area = 10

Was Python ausgibt

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.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

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.

Quellen

Zuletzt geprüft am 29. September 2026