Zum Inhalt
wannacode

Kapselung

Kurz gesagt: Kapselung bedeutet, dass die Daten eines Objekts nur über seine Methoden geändert werden: in Python mit der Abmachung _name und mit Properties über @property.

Problem

Minus 500 €

In der letzten Lektion haben wir die Prüfung beim Abheben in eine Methode gesteckt. Das Feld ist aber offen, und jeder kann direkt hineinschreiben:

account = Account("Maria", 100)
account.withdraw(500)
account.balance = -500
print(account.balance)
Ausgabe
-500

Die Methode hat das Abheben von 500 € richtig abgelehnt. Die nächste Zeile hat den Kontostand einfach von Hand gesetzt. Die Prüfung ist da, aber man kann sie umgehen. Wir brauchen eine Möglichkeit zu sagen: „Nur das Konto fasst den Kontostand an.“

Die Idee

_balance: intern für die Klasse

Schreib einen Unterstrich vor den Namen des Feldes: _balance.

class Account:
def __init__(self, owner, balance):
self._owner = owner
self._balance = balance
# ...

Python hat kein private. Der Unterstrich ist eine Abmachung unter Programmierern: „Das ist intern für die Klasse, von außen nicht anfassen.“ Python hält niemanden auf. Die Zeile

account._balance = -500

läuft, und der Kontostand wird -500. Aber jeder, der sie liest, sieht, dass die Regel gebrochen ist (PyCharm zum Beispiel unterstreicht sie). Code außerhalb der Klasse benutzt nur die Methoden der Klasse.

Die Klasse versteckt ihre Daten und zeigt sie nur über ihre eigenen Methoden. Das nennt man Kapselung. Java und C# haben dafür Schlüsselwörter, die Zugriffsmodifizierer (private, public). In Python übernimmt der Unterstrich diese Rolle.

Genauer, für Neugierige
  • Mit zwei Unterstrichen (self.__balance) benennt Python das Feld in _Account__balance um. Von außen gibt es account.__balance nicht mehr, aber account._Account__balance kommt trotzdem hin. Das schützt vor einem zufälligen Namenskonflikt in Unterklassen, nicht davor, dass jemand das Feld absichtlich anfasst. Meist reicht ein Unterstrich.
  • Die Abmachung gilt auch für Methoden: _check() ist eine Methode, die nur für die Klasse selbst gedacht ist.

Die Idee

Lesen, ohne anzufassen

Von außen sollten wir den Kontostand jetzt nicht einmal lesen. Deshalb gibt die Klasse eine Methode, die ihn nur zurückgibt:

bank.py
class Account:
def __init__(self, owner, balance):
self._owner = owner
self._balance = balance
@property
def owner(self):
return self._owner
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
def withdraw(self, amount):
if amount > 0 and amount <= self._balance:
self._balance -= amount
return True
return False
account = Account("Maria", 100)
account.withdraw(30)
account.deposit(50)
print(f"{account.owner}: {account.balance} €")
Ausgabe
Maria: 120 €

balance ist eine Property. @property über der Methode bedeutet, dass du sie von außen wie ein Feld liest: account.balance, ohne Klammern. Innen gibt sie den Wert von _balance zurück. Das ist der Getter in Python. Der Kontostand ändert sich nur über deposit und withdraw, und die prüfen.

In eine Property ohne Setter kann man nicht schreiben. Die Zeile account.balance = -500 bricht ab mit:

Fehler
AttributeError: property 'balance' of 'Account' object has no setter

withdraw gibt jetzt True oder False zurück: ob das Abheben geklappt hat. So weiß jeder, der abhebt, ob es funktioniert hat.

account = Account("Maria", 100)
account.withdraw(500)
account.deposit(50)
print(account.balance)
Schritt für Schritt, in Worten
  1. _balance ist intern: Nach der Abmachung fasst Code von außen das Feld nicht an. Er geht über die Methoden.
  2. Das Abheben geht durch die Prüfung in der Methode: 500 ist mehr als 100. Abgelehnt, und der Kontostand bleibt, wie er ist.
  3. Auch die Einzahlung wird geprüft: 50 ist ein positiver Betrag. Die Methode addiert ihn.
  4. Die Property balance gibt eine Kopie des Kontostands heraus. Das Feld _balance selbst bleibt drinnen.

Probier es aus

Abgelehnt

Was gibt das Programm aus?

account = Account("Maria", 100)
print(account.withdraw(150))
account.deposit(-20)
print(account.balance)
Antwort

Die Idee

Setter: eine Änderung mit Prüfung

Manchmal muss Code von außen ein Feld ändern, aber nicht auf jeden beliebigen Wert. Das Alter eines Tieres kann sich ändern, aber es kann nicht negativ sein:

class Animal:
def __init__(self):
self._age = 0
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value >= 0:
self._age = value
cat = Animal()
cat.age = 4
cat.age = -3
print(cat.age)
Ausgabe
4

@age.setter macht einen Setter für die Property age: eine Methode mit demselben Namen, die bei cat.age = -3 aufgerufen wird. value ist der Wert rechts von =, hier -3. Die Prüfung schlägt fehl, und das Alter bleibt 4. Von außen sieht das Schreiben aus wie das Schreiben in ein einfaches Feld, aber es geht durch die Prüfung.

Die Idee

Welches Feld bekommt einen Setter?

Nicht jedes Feld braucht einen Setter, und manche brauchen nicht einmal einen Getter. Überleg, was Code von außen mit dem Produkt tun können soll:

  • Name und Preis werden beim Erzeugen des Produkts gesetzt und danach nur gelesen: nur ein Getter;
  • die Stückzahl im Warenkorb ändert sich, ist aber mindestens 1: ein Getter und ein Setter mit Prüfung.
class Product:
def __init__(self, name, price):
self._name = name
self._price = price
self._quantity = 1
@property
def name(self):
return self._name
@property
def price(self):
return self._price
@property
def quantity(self):
return self._quantity
@quantity.setter
def quantity(self, value):
if value >= 1:
self._quantity = value
coffee = Product("Kaffee", 7.50)
coffee.quantity = 3
coffee.quantity = 0
print(f"{coffee.name} x{coffee.quantity}")
Ausgabe
Kaffee x3

Die Regel lautet: Fang mit _ für alles an und öffne nur, was du brauchst. Ein Setter, der ohne Prüfung alles speichert, bringt das Problem aus der ersten Karte zurück.

Probier es aus

Was fehlt?

Der Kontostand ist intern für die Klasse. Wie heißt das Feld nach der Abmachung?
class Account:
    def __init__(self, balance):
        self. = balance

Probier es aus

Finde den Fehler

Das Feld ist _balance, und trotzdem wird der Kontostand -500:

account = Account()
account.balance = 100
account.withdraw(30)
account.balance = -500
print(account.balance)
Ausgabe
-500

Welche Zeile in der Klasse bricht den Schutz?

Wähle die Zeile mit dem Fehler.

Übung

Versteckte Daten

Löse die Aufgaben in deinem Editor. Alle Felder beginnen mit einem Unterstrich.

1. Person. Person mit Name und Alter, ein Konstruktor, die schreibgeschützten Properties name und age und introduce().

Ausgabe
Maria ist 34 Jahre alt.
Hallo, ich bin Maria und bin 34 Jahre alt.

2. Ein Konto ohne Minus. Account mit Inhaber und Kontostand. Eine Einzahlung nimmt nur einen positiven Betrag an. Eine Abhebung gibt zurück, ob sie geklappt hat, und lässt den Kontostand nie unter 0 fallen. Code von außen kann den Kontostand lesen, aber nicht schreiben.

Das Programm
account = Account("Peter", 50)
account.deposit(25)
account.deposit(-10)
print(account.withdraw(100))
print(account.withdraw(40))
print(f"Kontostand: {account.balance} €")
Ausgabe
False
True
Kontostand: 35 €

3. Tier. Animal mit Art und Alter. Die Art wird im Konstruktor gesetzt und danach nur gelesen. Das Alter hat einen Setter (@age.setter), der eine negative Zahl ablehnt und eine Meldung ausgibt. Auch der Konstruktor geht durch dieselbe Prüfung: Schreib in __init__ self.age = age, ohne Unterstrich.

Das Programm
cat = Animal("Katze", 3)
cat.age = 4
cat.age = -1
print(f"{cat.species}, {cat.age} Jahre")
Ausgabe
Ungültiges Alter: -1
Katze, 4 Jahre

Weitere Aufgaben

  • Note. Eine Klasse Grade mit dem Namen eines Fachs und einer Note von 2 bis 6. Der Setter lehnt alles außerhalb dieser Grenzen ab. Probier es mit 5, 7 und 1.
  • Produkt mit Rabatt. Gib dem Product aus der Lektion eine Methode, die einen Rabatt in Prozent anwendet und nur 1 bis 90% annimmt. Der Preis hat keinen Setter.

Test

Teste dich

1. Das Feld heißt _balance. Kann Code außerhalb der Klasse es ändern?
2. Warum hat das Konto keinen Setter für den Kontostand?
3. Was ist value in @age.setter def age(self, value):?
4. Was bedeutet Kapselung?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·