unterschiedliche Leuchtmittel an einem Wagenrad

Attribute absichern



Quellen: M. Brenner, J. Schnaiter, "Softwareentwicklung mit Python", ZSL Freiburg, 2024

Um Attributwerte (Objekteigenschaften) gegen Fehler abzusichern, überprüft man diese in Methoden. Diese Methoden heißen Setter zum Ändern von Attributwerten und Getter zum Abrufen von Daten. Um nun sicherzustellen, dass die Daten immer über diese Methoden geändert und abgerufen werden kappselt man die Attribute, d.h. man macht die Attribute privat. Dadurch kann nur noch in der Klasse auf die Attribute direkt zugegriffen werden, von außerhalb nur über die Setter und Getter.

In diesem Artikel lernst Du wie man Attributwerte mit set- und get-Methoden absichert, die Bedeutung der Datenkapselung mit public und private sowie das UML-Klassendiagramm kennen.

1 Attribute absichern - Kapselung durch set-get-Methoden

unterschiedliche Leuchtmittel an einem Wagenrad
In der Klasse Leuchtmittel werden folgende Eigenschaften für die Leuchtmittel definiert: Leistungsaufnahme P in W (Watt), Lichtstrom Φ in lm (Lumen). Berechnen kann man hieraus die Lichtausbeute η in lm/W (Lumen pro Watt).

class Leuchtmittel:
    def __init__(self, leistung = 2.5, lumen = 340):
        self.leistung = leistung
        self.lumen = lumen

    def __str__(self):
        return self.__class__.__name__ + f" {self.leistung} W, 
		🔆 {self.lumen} lm und η = {self.lumen/self.leistung} lm/W"

Probleme bei der Objekterzeugung in der Beleuchtung Lampe

Eine Wagenradlampe der Klasse Beleuchtung besteht aus Objekten der Leuchtmittel. Wir erstellen nun ein Objekt:

class Beleuchtung:
    l1 = Leuchtmittel(2.5, 340)
    l2 = Leuchtmittel(3, 420)

    print(l1)
    print(l2) 

    #Die Leistung wird mit einem fehlerhaften Wert manipuliert.
    l2.leistung = -42.0
    print(l2)

Folgende Ausgabe wird erzeugt:

Leuchtmittel 2.5 W, 🔆 340 lm und η = 136.0 lm/W
Leuchtmittel 3 W, 🔆 420 lm und η = 140.0 lm/W
Leuchtmittel -42.0 W, 🔆 420 lm und η = -10.0 lm/W

Der Benutzer kann fehlerhafte Werte setzen wodurch nicht nur der Wert, sondern auch nachfolgende Berechnungen fehlerhaft sind.

Lösung mit Kapselung und set-get-Methoden in der Klasse Leuchtmittel

Wir ersetzen den sogenannten Modifier der Attribute in der Klasse Leuchtmittel durch private. Dies wird durch einen doppelten Unterstrich in Python gemacht __leistung. Dadurch wird der Attributzugriff auf die Klasse Leuchtmittel beschränkt. Diesen Vorgang nennt man Kapselung. Man schirmt die Attribute vom Zugriff aus anderen Klassen ab.

Die Änderung von gekapselten Werten erlaubt man dann nur noch in den sogenannten set-Methoden; für den Wertzugriff verwendet man get-Methoden. Der indirekte Zugriff auf die Leuchtmittelattribute erfolgt aus der Klasse Lampe:

class Beleuchtung:
    l1 = Leuchtmittel(2.5, 340)
    print(l1) 

    #Die Leistung wird mit fehlerhaft manipuliert.
    l1.setLeistung(-2.5)
    print(l1)

class Leuchtmittel:        
    def __init__(self, leistung = 2.5, lumen = 340):
        self.setLeistung(leistung)
        self.lumen = lumen
        
    #setter und getter für Leistung
    def setLeistung(self, leistung):
        if leistung >= 0:
            self.__leistung = leistung #geschütztes Attribut
        else:
            print("Wert fehlerhaft, der Wert bleibt unverändert.")
    
    def getLeistung(self):
        return self.__leistung
    
    def __str__(self):
        return self.__class__.__name__ + f" {self.__leistung} W, 
		🔆 {self.lumen} lm und η = {self.lumen/self.__leistung} lm/W"
    

Modifier Sichtbarkeit von Variablen und Methoden in Python

Modifier Die Klasse selbst Unter­klassen sons­tige Klassen
private (zwei Unterstriche)
protected (ein Unterstrich)
public (kein Unterstrich)

Aufgabe 1 Die Retrobeleuchtung

Erstelle die Klasse Leuchtmittel und Beleuchtung.

  1. Erstelle die Klasse Leuchtmittel und Beleuchtung. Programmiere alle Methoden wie im Eingangsbeispiel. Vergiss dabei nicht in der to-String-Methode auch die Lichtausbeute auszugeben.
  2. In das Beispielprogramm haben sich trotz Abischerung drei Fehler eingeschlichen. Teste folgende drei Manipulationen und notiere die Fehlerart:
    l2 = Leuchtmittel(-2, 340),
    l2 = Leuchtmittel(0, 340),
    l2 = Leuchtmittel("0", 340).
  3. Sicher das Attribut __leistung gegen diese Fehler ab.
  4. Der Nutzer möchte zusätzlich für jedes Leuchtmittel einen Dimmfaktor zwischen 0 und 100 % eingeben. Gib eine Liste der Helligkeitswerte (in %) und Leistungsbedarf aus.
Retrolampen mit unterschiedlichen Schirmen

2 Mit Properties lesbare gekapselte Attribute - Verbesser die Lesbarkeit

Mit Setter und Getter wird der Zugriff auf Variablen mitunter unlesbar. Betrachten wir hierzu folgendes Beispiel. In der Klasse Beleuchtung wird die Leistung von l3 mit den Werten von l1 und l2 berechnet.

class Beleuchtung:
    l1 = Leuchtmittel(2.5,340)
    l2 = Leuchtmittel(1.5,200)
    l3 = Leuchtmittel()

    #Die Leistung von l3 wird mit den Werten von l1 und l2 berechnet.
    l3.setLeistung(l1.getLeistung()+l2.getLeistung())
    print(l3) 

Properties erstellen

Durch Properties wird der Code wieder lesbarer. Wir können die Variablen verwenden als ob sie öffentlich wären, sind sie aber nicht.

class Leuchtmittel:        
    def __init__(self, leistung = 2.5, lumen = 340):
        ...
    def setLeistung(self, leistung):
    	...
    def getLeistung(self):
        ...
    def __str__(self):
        ...

    #property
    leistung = property(getLeistung, setLeistung)

class Beleuchtung:
    l1 = Leuchtmittel(2.5,340)
    l2 = Leuchtmittel(1.5,200)
    l3 = Leuchtmittel()

    #Die Leistung von l3 wird mit den Werten von l1 und l2 berechnet.
    l3.leistung = l1.leistung + l2.leistung
    print(l3) 

Aufgabe 2 Ordne zu

Ordne die korrekte Antwort zu.

Nenne die Klasse, welche auf ein private Attribut Zugriff hat.
Wähle eine Antwort.

  1. innere Klassen
  2. die Klasse selbst
  3. Unter­klassen
  4. sons­tige Klassen

Nenne Vorteile der Kapselung.
Wähle zwei Antworten.

  1. fehlerhafte Werte werden verhindert
  2. Objekte werden nicht doppelt erzeugt
  3. Klassen werden geschützt
  4. abhängige Attribute sind geschützt

Finde die Anzahl der Fehler im folgenden Quelltext.

def setLeistung(self, __leistung):
    if leistung >= 0:
        self.leistung = leistung
    else:
        print("Wert fehlerhaft")

Finde die Anzahl der Fehler im folgenden Quelltext.

def __init__(self, leistung):
    self.leistung = leistung

def getLeistung(self, leistung):
    return self.__leistung

Bestimme die zugehörige Konstruktormmethode, zum Konstruktoraufruf
s2 = Schaf(alter = 20, gewicht = 23.3). Wähle zwei Antworten.

  1. def __init__(self, gewicht, alter)
  2. def __init__(gewicht=1.0, alter=1)
  3. def __init__(self, alter, gewicht, c="C")
  4. def __init__(self, 20, 3)

Beschreibe einen Konstruktor.
Wähle beliebig viele Antworten.

  1. ist eine Methode
  2. gibt einen Wert zurück
  3. wird durch "__init__" gekennzeichnet
  4. kennzeichnet Klassen

3 Exceptions - Optimierung der Absicherung

#Typeerror - fehlerhafte Werte
lumen = "hell"
x = lumen + 1 

#ZerodivisionError
lumen = 0
x = 4/lumen 

#Value ValueError
lumen = -20
x = math.sqrt(lumen) 

Exceptions

Fehlerhafte Syntax kann nicht ausgeführt werden. Aber auch wenn die Syntax korrekt ist, kann es bei der Ausführung des Quelltextes zu einem Fehler kommen. Dann werden sogenannte Exceptions ausgelöst. Je nach Fehlerart unterscheidet man verschiedene Exceptions.

Try und Except

Mit Hilfe dieser Exceptions lassen sich verschiedene Fehlermeldungen abfangen. Bei der Erzeugung von Objekten kann man nun mit try und except Fehler abfangen. Hierzu wird einerseits die Objekterzeugung in einen try- und except-Block geschrieben und in der set-Methode auf fehlerhafte Werte geprüft und ggf. eine Exception ausgelöst.

class Leuchtmittel:
    def __init__(self, leistung = None, lumen = None):
        self.setLeistung(leistung) 
        self.lumen = lumen

    def setLeistung(self, leistung):
        if(isinstance(leistung, (int, float)) and leistung > 0):
            self.__leistung = leistung
        else:
            raise ValueError("fehlerhafte Werte") 

    #property
    leistung = property(getLeistung, setLeistung)

class Beleuchtung:
    try:
        l1 = Leuchtmittel(12,300)  
        print(f"Objekt wurde erzeugt.")
    except ValueError as errormessage:
        print(f"Objekt wurde nicht angelegt: {errormessage}") 

Aufgabe 3 set-get-Methoden und try-except für die Schafe

Die Schafe sollen mit set-get-Fähigkeiten und Exceptionhandling ausgestattet werden.

  1. Erweitere die Klasse Schaf mit set-get-Methoden.
  2. Erweitere jede set-Methode mit einer eigenen Exception. Achte darauf, dass verschiedene Exceptions eingesetzt werden.
  3. Teste in der Klasse Schafherde die neuen Methoden und Exceptions ausführlich. Lass für jede Exception eine Erklärung anzeigen.
Zeichnung zweier Schafe

Entspann dich erstmal ...



Briefmarken aus dem Jahr 2022

Ein männlicher Briefmark erlebte
Was Schönes, bevor er klebte.
Er war von einer Prinzessin beleckt.
Da war die Liebe in ihm erweckt.

Er wollte sie wieder küssen,
Da hat er verreisen müssen.
So liebte er sie vergebens.
Das ist die Tragik des Lebens!

Aufgabe 4 Der Fuhrpark

Erweitere die Klasse Auto und Fuhrpark.

Auto SUV
  1. Erweitere die Klasse Auto mit set- und get-Methoden.
  2. Setze alle Attribute der Klasse Auto auf private
  3. und stelle so sicher, dass abhängige Attribute nicht falsche Werte haben können und sicher alle Eingaben gegen fehlerhafte Eingaben ab.
  4. Erweitere die Klasse Fuhrpark durch Aufrufe der set- und get-Methoden und teste so die Funktionalität der Klasse Auto.
  5. Erweitere die Klasse Auto mit zwei Konstruktor­methoden, den Standard­konstruktor mit Initalwerten und einem Konstruktor mit Parameterliste.

UML-Klassendiagramme Unified Modeling Language

UML (engl. unified modeling language) ist eine grafische Modellierungssprache zur Spezifikation, Konstruktion und Dokumentation von Software-Teilen und anderen Systemen. UML besteht aus vielen Diagrammtypen. Ein Typ davon ist das Klassendiagramm.

Ein Klassendiagramm stellt eine Klasse übersichtlich und strukturiert dar. Es ist von der Form her rechteckig und besteht aus drei Teilen:

  1. Klassenname,
  2. Attribute (Eigenschaften) mit Modifier (+ für public und - für private) und Datentyp,
  3. Methoden (Fähigkeiten) mit Modifier, Name, Parameterliste und Rückgabetyp.

Man beachte, das statische Methoden und Attribute unterstrichen werden. Der Vorteil statischer Methoden liegt darin, dass sie ohne die Erzeugung eines Objekts aufrufbar sind: Auto.setMwSt(19.0);. Nachteilig wirkt sich aus, dass der Speicher immer belegt wird.

Was das Klassendiagramm nicht sagt:

  • es beschreibt nicht was in den Methoden genau passiert und wie diese zu ihrem Ergebnis kommen,
  • es sagt nichts über die Objekte aus, die von der Klasse erstellt werden.
Auto
- farbe : str
- leistung : int
- preis : float
- mehrwertsteuer : float
+ Auto (str farbe, int leistung, float preis)

+ setFarbe (str color) : void
+ setLeistung (int power) : void
+ setMwSt (float mwst) : void

+ getFarbe () : str
+ getLeistung () : int
+ getMwSt () : float

+ fahrenStrecke (int km) : void
+ tanken (int menge) : void

Aufgabe 5 UML-Klassendiagramm

Schreibe UML-Klassendiagramme.

  1. Schreibe ein UML Klassendiagramm für die Klasse Auto.
  2. Schreibe ein UML Klassendiagramm für die Klasse Fuhrpark.
  3. Schreibe ein UML Klassendiagramm für die Klasse Schaf.
Zeichnung zweier Autos

Aufgabe 6 Weitere Projekte

Schreibe weitere Klassenpaare.

Pizzastücke

Neben den Klassenpaaren Schaf und Schafherde sowie Auto und Fuhrpark, gibt es weitere Klassenpaare.

  1. Schreibe ein UML Klassendiagramm für die Klassen Wohnmobil und Campingplatz.
  2. Schreibe ein UML Klassendiagramm für die Klassen Pizza und Menue.
  3. Schreibe ein UML Klassendiagramm für die Klassen Sensor und Home.
  4. Nenne drei weitere Klassenpaare aus der Praxis und programmiere eins davon.

Automatisierte Objektauswertung Objekte aus einer JSON-Liste erzeugen

In der Praxis werden Objektdaten häufig im JSON-Format geliefert. Sind diese Daten in einer Liste kann man beim Erzeugen der Objekte über das Dictonary iterieren und so übersichtlich die Objekte erzeugen.

class Schafherde:
  schafliste = [
    {"name": "Alma", "alter": 2, "gewicht": 2.3},
    {"name": "Schaun", "alter": 9, "gewicht": 17.4},
    {"name": "Schaun", "alter": 9, "gewicht": 0}
  ]
Zeichnung zweier Schafe

Schafobjekte erzeugen - mit Schlüssel auf JSON-Werte zugreifen

Um nun auf die einzelnen Werte im Dictonary zuzugreifen, wird über die Liste iteriert und mit den Schlüsselwerten auf die einzelnen Objektwerte zugegriffen: Schaf(s["name"],s["alter"],s["gewicht"]). Des weiteren wird das try-except-Konezpt bei der Erstellung verwendet, so dass nur einwandfreie Objekte erzeugt werden.

Die Anzeige der erzugten Objekte erfolt wie gewohnt über die for-Schleife. In diesem Fall wurde in der Klasse Schaf eine to-String-Methode hinterlegt sowie eine nummerierte Ausgabe mit der Funktion enumerate(..) erzeugt, so dass alle erzeugten Objekte nummeriert ausgegeben werden.

In der Praxis werden Objektdaten häufig im JSON-Format geliefert. Sind diese Daten in einer Liste kann man beim Erzeugen der Objekte über das Dictonary iterieren und so übersichtlich die Objekte erzeugen.

#Schafe als Objekt anlegen
schafherde = []
for s in schafliste:
    try:
        schafherde.append(Schaf(s["name"],s["alter"],s["gewicht"]))
    except TypeError as errorMessage:
        print(f"Objekt nicht angelegt: {errorMessage}")
    except ValueError as errorMessage:
        print(f"Objekt nicht angelegt: {errorMessage}")
    except:
        print("Sonstige Fehler")
#Schafherde anzeigen
for i,s in enumerate(schafherde, start=1):
    print(i,s)

Aufgabe 7 Bücherei

Realisiere eine Bücherei in der Bücher mit Titel, Autor und Bewertung gespeichert werden sollen.

Bücherstapel
  1. Erstelle das Klassenpaar Book und Library.
  2. Füge zur Klasse Book die Attribute title, author, rating hinzu sowie entsprechende set- und get-Methoden.
  3. Füge zur Klasse Library ein Dictonary namens booklist mit drei Einträge wie bspw. booklist.add(new Book("Herr der Ringe", "J.R.R. Tolkien", 4.8)); hinzu und erzeuge drei Objekte.
  4. Sicher die Erzeugen mit dem try-except-Konzept ab.
  5. Gib alle Einträge aus.
  6. Berechne die durchschnittliche Bewertung aller Bücher.

Aufgabe 8 Ordne zu

Die Firma BestSolution4IT hat einen Programmcode erhalten. Anhand der folgdenden Fragen soll sichergestellt werden, dass der Quellcode verstanden wurde. Ordne die korrekten Antworten zu.

class Schaf:    
    def __init__(self, name, alter, gewicht):
        self.name = name
        self.alter = alter
        self.setGewicht(gewicht)

    def setGewicht(self, gewicht):
        gewicht = float(gewicht) 
        if gewicht <= 0:
            raise ValueError("Gewicht unzulässig")
        else:
            self.__gewicht = gewicht
           
class Schafherde:
    schafliste = [
        {"name": "Alma", "alter": 2, "gewicht": 2.3},
        {"name": "Schaun", "alter": 9, "gewicht": -17.4},
        {"name": "Emil", "alter": 20, "gewicht": "vier"},
    ]
    
    schafherde = []
    for s in schafliste:
        try:
            schafherde.append(Schaf(s["name"],s["alter"],s["gewicht"]))
        except TypeError as errorMessage:
            print(f"Objekt nicht angelegt: {errorMessage}")
        except ValueError as errorMessage:
            print(f"Objekt nicht angelegt: {errorMessage}")
        except:
            print("Sonstige Fehler")

Was passiert, wenn das Gewicht eines Schafs kleiner oder gleich 0 ist?
Wähle eine Antwort.

  1. Das Gewicht wird auf 1 gesetzt.
  2. Es wird eine TypeError-Exception ausgelöst.
  3. Eine ValueError-Exception wird ausgelöst.
  4. Das Objekt wird trotzdem erstellt.

Welche Aussage trifft auf self.__gewicht zu?
Wähle eine Antwort.

  1. Es kann direkt außerhalb der Klasse geändert werden.
  2. Es ist ein geschütztes Attribut.
  3. Es ist ein privates Attribut und folgt Namenskonventionen.
  4. Es ist eine Klassenvariable.

Welche Methode prüft die Gültigkeit des Gewichts?
Wähle eine Antwort.

  1. __init__()
  2. setGewicht()
  3. __str__()
  4. validate()

Welche Art von Fehler tritt bei gewicht = float("vier") auf?
Wähle eine Antwort.

  1. SyntaxError
  2. TypeError
  3. NameError
  4. ValueError

Wie wird ein neues Schaf korrekt erzeugt?
Wähle eine Antwort.

  1. Schaf("Berta", "alt", "leicht")
  2. Schaf("Berta", 3, 4.0)
  3. Schaf()
  4. Schaf(name="Berta", gewicht=3.5)

Wortliste und Satzbausteine



das Objekt, -e Objekte haben Eigenschaften (engl. states) und Fähigkeiten (engl. behaviors). Diese werden in einer zugehörigen Klasse festgelegt.
die Klasse, -n Eine Klasse beschreibt, welche Eigenschaften und Fähigkeiten ein Objekt haben darf und stellt somit den Bauplan von Objekten bereit.
die Methode, -en Fähigkeiten von Objekten werden in Methoden beschrieben.
das Attribut, -en Eigenschaften von Objekten werden in Attributen (Variablen) festgelegt.
der Modifier, ~ legt die Sichtbarkeit von Eigenschaften und Fähigkeiten fest, also ob diese bspw. privat oder öffentlich zugänglich sind
die Kapse­lung, - die Sichtbarkeit der Attribute einer Klasse wird auf private gesetzt und so schirmt man die Attribute vom Zugriff aus anderen Klassen ab.
die set-, get-Me­thoden, - Methoden die die abgeschirmten Attribute setzen oder auslesen.
der Konstruk­tor, -en Methode welche die Initialisierung der Parameter bei Objekterzeugung ermöglicht
das Dictonary speichert Daten im Objektformat (JSON)