Objekte#

Midjourney: Bauhaus line drawing

The best way to predict the future is to invent it — and then encapsulate it in objects.

— Alan Kay

Folien/PDF#

Objektorientierte Programmierung (OOP) ist ein Programmierparadigma das annimmt, dass ein Programm ausschließlich aus Objekten besteht, die miteinander kooperativ interagieren. Jedes Objekt verfügt über Attribute (Eigenschaften) und Methoden. Die Attribute definieren dabei über ihre Werte den Zustand eines Objektes, die Methoden die möglichen Zustandsänderungen (Handlungen) eines Objektes.

Die Objektorientierung löst dabei einige Problem im Umgang mit sich oft wiederholenden Datenstrukturen in großen Programmen.

Herausforderungen mit sich stark wiederholenden Datenstrukturen#

Das syntaktische Problem#

Objekte werden verwendet, um festzulegen wie sich stark wiederholende Datenstrukturen gespeichert werden. Hierbei geht es darum, dass der Syntax der Datenstruktur eindeutig ist.

Wächst das Programm an, so wächst auch die Menge der Variablen und Datenstrukturen

  • zur Speicherung von Daten

  • zur Kontrolle des Programmflusses

  • zum Abspeichern von Zuständen

  • zum Verarbeiten von Ein- und Ausgaben

Dabei basieren die zugrundeliegenden Elemente meist auf sich wiederholenden Datenstrukturen. Analysiert man z.B. Baupläne oder Karten so verwaltet man viele Punkt-Koordinaten. Hierbei kann man allerdings eine Koordinate unterschiedlich z.B. als Tupel oder als Liste ausdrücken.

point_1 = (54.083336, 12.108811)
point_2 = [12.094167, 54.075211]

Will man diese Punkte verarbeiten so ergeben sich ggf. Probleme da beide unterschiedliche Datentypen besitzen. Das ist das syntaktische Problem im Umgang mit Variablen in großen Programmen. Hier will man den Syntax der Datenstruktur definieren können.

Das semantische Problem#

Objekte werden auch verwendet, um die Semantik von Werten einer Datenstrukturen eindeutig zu definieren.

Haben wir uns z.B. darauf geeinigt, dass wir ein Punkt syntaktisch durch ein Tupel repräsentieren, so ist die Bedeutung der Werte dennoch nicht bekannt.

point_1 = (54.083336, 12.108811)
point_2 = (12.094167, 54.075211)

Ein anderer Programmierer wird hier aber ggf. nicht die semantische Bedeutung verstehen. In diesem Beispiel kann man vermuten, dass der erste Wert die x-Koordinate ist und der zweite die y-Koordinate. Vieleicht ist es aber auch andersherum. Ggf. handelt es sich aber nicht um ein kartesisches Koordinatensystem, sondern um ein radiales. Um solche Unklarheiten zu beseitigen, will man der Datenstruktur eine klare semantische Definition geben, wo eindeutig ist, was die Werte bedeuten.

Das Verhaltensproblem#

Objekte werden zudem verwendet, um die Funktionen zur Verarbeitung der Datenstrukturen direkt mit dieser zu bündeln, so dass die Datenstruktur nur jene Funktionen anbietet, welche auch sinnvoll anwendbar sind.

Definieren wir z.B. eine Funktion, um die Distanz zweier Punkte zu berechnen:

import math

def distance(a, b):
    return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)

Diese Funktion kann man aufgrund der dynamischen Typisierung in Python ja auch auf andere Datenstrukturen anwenden, z.B. auf eine Line. Das führt zu semantischen oder logischen Fehlern. Um das zu vermeiden, müsste man für jede Variante eine andere Funktion definieren, diese zuordbar benennen und viele Datentyp-Tests vornehmen. Einfacher ist es, sicher zu stellen, dass eine solche Funktion nur für die entsprechende Datenstruktur verfügbar ist.

Objektorientierung#

Klassen deklarieren#

Anstatt unübersichtlich viele verstreute Datenstrukturen und Funktionen zu benutzen, gruppieren wir diese in Objekte. Da wir die Datenstrukturen normieren wollen, muss wir die Struktur dieser Objekte vor Verwendung definiert werden. Dies geschieht über Klassen, welche eine Art Bauplan für die damit normierten Objekte darstellt. Eine Klasse definiert:

  • welche Attribute (Eigenschaften) ein Objekt dieser Klasse besitzt

  • und welche Methoden (Funktionen) ein Objekt der Klasse bereitstellt

Der erste Schritt zu einem Objekt ist das Definieren einer neuen Klasse für den Typ des Objektes. Dies geschieht über den class-Kennwort auf welches der Klassenname folgt. Danach wird wie die Klassendefinition eingerückt. Diese legt fest welche Attribute und Methoden die Klasse besitzt.

class Klassenname:
    # Klassendefinition
    pass

Konstruktor#

Eine der wichtigsten Methoden einer Klasse ist der Konstruktor __init__(). Das ist eine spezielle Methode, die festlegt, wie eine neue Instanz der Klasse erzeugt werden kann. Er wird genutzt, um Attribute initial zuzuweisen als auch Initialisierungsschritte (Tests, Berechnungen, Konfiguration, etc.) durchzuführen.

Jede Klasse muss genau einen Konstruktor haben. Wird dieser nicht definiert, so wird ein leerer Konstruktor von Python erzeugt, der nichts macht, wie das folgende Beispiel.

class Klassenname:
    # Leerer Konstruktor
    def __init__(self):
        pass

Instanzattribute#

Der Konstruktor wird als Funktion __init__(self) mit dem Parameter self definiert. self ist dabei eine Selbst-Referenz auf die neue Instanz der Klasse. Sie dient dazu, dass man Instanzattribute direkt beim Erzeugen der Instanz zuweisen kann. Instanzattribute sind Attribute, die in jeder Instanz unterschiedlich sein können, also wenn es individuelle Werte der Eigenschaft gibt.

Definieren wir z.B. die Klasse eines Punktes mit x- und y-Koordinaten. Da jede Instanz diese beiden Koordinaten haben muss und sie auch für jede Instanz unterschiedlich sein können, weisen wir sie bereits im Konstruktor als Instanzattribute zu. Damit ist festgelegt, dass jede Instanz der Klasse diese Attribute hat.

class Point:
    # Konstruktor
    def __init__(self, x, y):
        self.x = x
        self.y = y

Die Zuweisung erfolgt hierbei über den Punkt-Syntax bei dem ein Punkt die Instanzvariable (self) von dem Attributnamen x trennt. self.x ist somit eine Referenz auf das Attribut x der Instanz self. Die Zuweisung self.x = x bedeutet, dass wir dem neuen Instanzattribut x der Instanz self den Wert der Variablen x zuweise. Obwohl beide gleich heißen, sind sie nicht die gleiche Variable, weil self.x ja ein Attribut der Instanz ist und x ein Parameter der Funktion ist und nur in dieser gültig ist.

Da __init__() eine Funktion ist, wenn auch besonders, kann man auch Defaults definieren. Wollen wir z.B. definieren wir, dass x und y mit 0 initialisiert werden, wenn sie nicht angegeben werden, so können wir auch dies als Defaultwerte deklarieren

class Point:
    # Konstruktor
    def __init__(self, x = 0.0, y = 0.0):
        self.x = x
        self.y = y

Klassenattribute#

Neben Instanzattributen gibt es auch Klassenattribute. Dies sind Attribute, welche für alle Instanzen einer Klasse den gleichen Wert haben sollen.

class Point:
    # Attribut aller Instanzen
    unit = "m"

Warning

Klassenattribute gelten für alle Instanzen. Insbesondere bei zusammengesetzten Datentypen bedeutet das, wenn eine Instanz den Wert ändert, so ändert er sich in allen anderen Instanzen auch.

Methoden#

Klassen definieren häufig auch eigene Methoden, also Funktionen die speziell nur auf Instanzen dieser Klasse angewendet werden sollen und nicht auf andere Objekte.

Methoden werden als Funktionen innerhalb der Einrückung der Klassendefinition deklariert. Diese Methoden sind dann in allen Instanzen verfügbar. Methoden besitzen immer self als ersten Parameter. Auch hier ist das eine Referenz auf die aktuelle Instanz. Dadurch kann man dann auf die Attribute oder andere Methoden zugreifen.

So können wir zum Beispiel die am Anfang definierte distance-Funktion als Methode definieren, dass sie nun die Distanz zweier Punkte self und other berechnen.

class Point:
    # Attribut aller Instanzen
    unit = "m"
    
    # Konstruktor
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # Eigene Methode
    def distance(self, other):
        return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)

Klasseninstanzen#

Objekte selbst sind immer Instanzen einer Klasse (die Klasse ist ja nur ein Bauplan). Eine Klasse kann beliebig viele Instanzen haben oder gar keine. Alle Instanzen sind gleich aufgebaut, besitzen aber nicht unbedingt die gleiche Werte in den Attributen.

Mit der neuen Klasse Punkt können wir nun die Punkte am Anfang jetzt syntaktisch, semantisch und im Verhalten eindeutig definieren. Dabei erzeugen wir Instanzen der Klasse nicht direkt mit dem Konstruktor sondern indem wir den Klassennahmen wie eine Funktion aufrufen, mit den Parametern des Konstruktors. Dabei wird der self-Parameter weggelassen (er wird von Python zugewiesen).

Dabei können wir die Parameter auch bennenen und so semantische Unklarheiten umgehen.

point_1 = Point(x=54.083336, y=12.108811)
point_2 = Point(y=12.094167, x=54.075211) # bei bennanten Parametern können wir die Reihenfolge verändern

Auch die Werte des Objektes sind nun semantisch klar definiert. Wir können auf sie mit dem Punkt-Syntax zugreifen, wobei der Variablenname des Objektes links steht und der Attributname rechts. Um auf das Attribut x zuzugreifen schreiben wir

point_1.x
54.083336

Das funktioniert auch für die Klassenattribute. In voller Schönheit können wir dann z.B. schreiben

print(f"Der Punkt liegt bei x: {point_1.x} {point_1.unit}; y: {point_1.y} {point_1.unit}")
Der Punkt liegt bei x: 54.083336 m; y: 12.108811 m

In gleicher Weise können wir den Attributen neue Werte zuweisen.

point_1.x = 54.08
point_1.y = 12.11
print(f"Der Punkt liegt bei x: {point_1.x} {point_1.unit}; y: {point_1.y} {point_1.unit}")
Der Punkt liegt bei x: 54.08 m; y: 12.11 m

Mit dem Punkt-Syntax können wir auch die Methoden aufrufen. Wollen wir die Distanz von point_1 und point_2 berechnen, so schreiben wir

point_1.distance(point_2)
0.016541414993884864

Referenzen#

In Programmen stehen Objekte meist im Zusammenhang. Z.B. besteht eine Linie aus zwei Punkten. Diesen Zusammenhang zweier Objekt-Klassen bezeichnet man als Referenz. Diesen Zusammenhang zweier Objekt-Klassen bezeichnet man als Referenz.

Um Referenzen in Python zu erstellen, erstellt man einfach ein Attribut vom Datentyp des anderen Objektes. So kann eine Linie als Verbindung zweier Punkte definiert werden mit der Länge berechnet durch die zuvor definierte Methode distance.

class Line:
    def __init__(self, start: Point, end: Point):
        self.start = start
        self.end = end

    def length(self):
        return self.start.distance(self.end)
linie_1 = Line(start=point_1, end=point_2)
linie_1.length()
0.016541414993884864

Kapzelung von Attributen und Methoden#

Häufig möchte man in der Programmierung Kontrolle darüber haben ob und wie Attribute verändert werden und wer welche Methoden aufrufen darf.

Hierfür erlaubt Python die Unterscheidung in private, protected und public Attribute und Methoden. Wenn nicht anders festgelegt sind alle Attribute und Methoden public. Ein public Attribut oder Methode ist außerhalb der Klasse für alle sichtbar, lesbar und veränderbar. Das haben wir oben genutzt um oben point_1.x auszulesen und mit point_1.x = 54.08 einen neuen Wert zuzuweisen. Damit diese Attribute oder Methoden nicht mehr zugreifbar sind müssen wir sie als private oder protected deklarieren, womit sie nur innerhalb der Klasse (private) oder Unterklasse (protected) über self zugreifbar sind. Um ein Attribut oder Methode als privat zu deklarieren fügen wir dem Namen ein __ am Anfang hinzu oder ein _ für protected.

Zum Beispiel wollen wir sicherstellen, dass die Position des Objektes Point nach Erstellung nicht mehr veränderbar ist. Ein solches unveränderliches Objekt nennt man immutable. Ein Grund dafür kann sein, dass eine Änderung Inkonsistenzen und potenzielle Fehler erzeugen würden. Wollen wir z.B. ein Rechteck aus den Punkten erzeugen, so müssen wir in der Init-Funktion prüfen, ob alle Linien rechtwinklig sind. Wären die Punkte verschiebbar, so müssten wir bei einer Änderung entweder neu prüfen oder wir verbieten die Veränderung einfach.

Um sicher zu stellen, dass niemand mehr die Koordinaten ändern kann, müssen wir x und y als privat zu deklarieren, indem wir sie in __x und __y benennen. Jetzt kann aber niemand außerhalb der Instanz die Werte lesen. Um das zu ermöglichen definieren wir zwei public Getter-Methoden get_x() und get_y() die nur den Wert zurück geben. Sollten wir den Wert veränderbar machen, müssten wir auch noch Setter-Methoden definieren, welche die Werte setzen würden.

class ImmutablePoint:
    unit = "m"
    
    def __init__(self, x, y):
        self.__x = x
        self.__y = y

    def distance(self, other):
        return math.sqrt((self.__x - other.__x)**2 + (self.__y - other.__y)**2)

    def get_x(self):
        return self.__x

    def get_y(self):
        return self.__y

Definieren wir nun wieder unsere zwei Instanzen

point_1 = ImmutablePoint(54.083336, 12.108811)

und versuchen den Wert __x oder __y zu lesen, so erhalten wir nun eine Fehlermeldung.

print(f"Der Punkt liegt bei x: {point_1.__x} {point_1.unit}; y: {point_1.__y} {point_1.unit}")
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[21], line 1
----> 1 print(f"Der Punkt liegt bei x: {point_1.__x} {point_1.unit}; y: {point_1.__y} {point_1.unit}")

AttributeError: 'ImmutablePoint' object has no attribute '__x'

Zum Auslesen der Werte müssen wir stattdessen die Getter-Methode benutzen.

print(f"Der Punkt liegt bei x: {point_1.get_x()} {point_1.unit}; y: {point_1.get_y()} {point_1.unit}")
Der Punkt liegt bei x: 54.083336 m; y: 12.108811 m

Wir können die Werte auch nicht verändern. Wir können es zwar versuchen:

point_1.__x = 54.08
point_1.__y = 12.11

aber es hat keine Auswirkung.

print(f"Der Punkt liegt bei x: {point_1.get_x()} {point_1.unit}; y: {point_1.get_y()} {point_1.unit}")
Der Punkt liegt bei x: 54.083336 m; y: 12.108811 m

Vererbung, Generalisierung und Polymorphismus#

Vererbung ist eine der wichtigsten Eigenschaften von Objektorientierung. Ziel der Vererbung ist es Attribute und Methoden, die in vielen ähnlichen Klassen vorkommen nur einmal definieren zu müssen.

Hierfür unterteilt man die Klassen in Oberklassen (Eltern) welche Attribute und Methoden an Unterklassen (Kinder) vererben. Das heißt jede Unterklasse (Kind) besitzt alle Attribute und Methoden der Oberklasse (Eltern), wir müssen sie also nicht noch einmal definieren. Neue Attribute und Methoden, die für eine Unterklasse definiert werden, werden jedoch nicht an die Eltern oder Geschwister übertragen. Deshalb spricht man bei den Unterklassen von einer Spezialisierung und den Oberklassen von einer Generalisierung, da sie für mehrere Klassen gelten.

Zum Beispiel stellen wir fest, dass verschiedenen geometrischen Objekte wie Dreieck, Viereck, Fünfeck, etc. alle Polygone darstellen, welche sich aus mit Linien verbundene Punkte darstellen. Dreieck, Viereck, Fünfeck sind hierbei Spezialisierungen der Klasse Polygon. Wir definieren also zuerst die generische Klasse Polygon welche als Instanzattribut eine list von mehr als 2 ImmutablePoint akzeptiert und als privates Attribut __points abspeichert. Wir definieren weiterhin ein optionales Attribut name welches public und veränderbar ist.

from typing import List

class Polygon:
    def __init__(self, points: List[Point], name="Polygon"):
        if not isinstance(points, list):
            raise TypeError("points not of type list")
        if not len(points) > 2:
            raise TypeError("points need to contain at least 2 Points")
        for point in points:
            if not isinstance(point, ImmutablePoint):
                raise TypeError("Point not of type ImmutablePoint")
        self.__points = points
        self.name = name

    def get_points(self):
        return self.__points

    # Fläche des Polygons entsprechend der Gaußsche Trapezformel
    def area(self):
        n = len(self.__points) # of corners
        area = 0.0
        for i in range(n):
            j = (i + 1) % n
            area += self.__points[i].get_x() * self.__points[j].get_y()
            area -= self.__points[j].get_x() * self.__points[i].get_y()
        area = abs(area) / 2.0
        return area

    # Überschriebene Standardmethode zur Erzeugung eines Strings
    def __str__(self):
        description = f"{self.name} has an area of {self.area()} and is defined by\n" # \n ist ein Zeilenumbruch
        for i,point in enumerate(self.__points):
            description += f"  Point {i} at x: {point.get_x()} {point.unit}; y: {point.get_y()} {point.unit}\n"
        return description

Hierbei überschreiben wir auch die Standardmethode __str__(self) eines Objektes, welche immer aufgerufen wird, wenn das Objekt in einen String konvertiert werden soll. Die Funktion wird auch von der print()-Anweisung genutzt, weshalb wir diese lesbarer gestalten können. Dieses Fähigkeit des Überschreiben von Methoden nennt man Polymorphismus oder auch Methodenüberladung (Overloading). Damit haben wir alle Programmierparadigmen der Objektorientierung kennen gelernt.

Vererbung

Generalisierung

Polymorphismus

Kapzelung

Attribute und Methoden von Elternklassen werden an Kinder vererbt. Das hilft Redundanzen und Fehler zu vermeiden.

Gemeinsamkeiten werden in generalisierten Eltern-Klassen implementiert

Kinderklassen können Methoden überschreiben und somit umdefinieren.

Kapselung von Daten und Methoden in Objekten ist ein Schutzmechanismus, um schadhafte Änderungen einzuschränken.

Wir definieren auch die Funktion area() welches die Fläche des Polygons (konkav und ohne Löcher) mit der Gaußchen Trapezformel berechnet.

Jetzt wollen wir die Untertypen Dreieck, Viereck und Fünfeck definieren. Hierzu geben wir beim Deklarieren der neuen Klasse die Oberklasse in Klammern mit an. Wir können dann auch einen neuen Konstruktor definieren (sonst wird der der Oberklasse genommen). Dabei müssen wir immer den Konstruktor der Oberklasse mit aufrufen, welche wir mit der Funktion super() erhalten. Wir definieren z.B Konstruktoren welche genau die richtige Anzahl an Punkten akzeptiert.

class Triangle(Polygon):
    def __init__(self, p1, p2, p3, name= "Triangle"):
        super().__init__(points=[p1, p2, p3], name=name)

class Tetragon(Polygon):
    def __init__(self, p1, p2, p3, p4, name= "Tetragon"):
        super().__init__(points=[p1, p2, p3, p4], name=name)

class Pentagon(Polygon):
    def __init__(self, p1, p2, p3, p4, p5, name="Pentagon"):
        super().__init__(points=[p1, p2, p3, p4, p5], name=name)

Das entscheidende bei der Vererbung ist nun, dass alle Instanzen von Triangle, Tetragon oder Pentagon die Attribute __points (es ist allerdings nicht sichtbar), name und die Methoden get_points und __str__ haben.

Definieren wir z.B. ein Dreieck und wenden darauf print() an, so wird intern die geerbte Funktion __str__ der Oberklasse Polygon angewendet.

dreieck_1 = Triangle(ImmutablePoint(0,0), ImmutablePoint(1,1),ImmutablePoint(2,0), "Dreieck")
print(dreieck_1)
dreieck_1.area()
Dreieck has an area of 1.0 and is defined by
  Point 0 at x: 0 m; y: 0 m
  Point 1 at x: 1 m; y: 1 m
  Point 2 at x: 2 m; y: 0 m
1.0

Das Gleiche funktioniert auch für Vierecke und Fünfecke:

viereck_1 = Tetragon(ImmutablePoint(0,0), ImmutablePoint(0,1),ImmutablePoint(1,1), ImmutablePoint(1,0), "Viereck")
print(viereck_1)
viereck_1.area()
Viereck has an area of 1.0 and is defined by
  Point 0 at x: 0 m; y: 0 m
  Point 1 at x: 0 m; y: 1 m
  Point 2 at x: 1 m; y: 1 m
  Point 3 at x: 1 m; y: 0 m
1.0
fuenfeck_1 = Pentagon(ImmutablePoint(0,0), ImmutablePoint(-1,1),ImmutablePoint(1,2), ImmutablePoint(2,1), ImmutablePoint(2,0), "Fünfeck")
print(fuenfeck_1)
fuenfeck_1.area()
Fünfeck has an area of 4.0 and is defined by
  Point 0 at x: 0 m; y: 0 m
  Point 1 at x: -1 m; y: 1 m
  Point 2 at x: 1 m; y: 2 m
  Point 3 at x: 2 m; y: 1 m
  Point 4 at x: 2 m; y: 0 m
4.0

Quiz#

shuffleQuestions: true shuffleAnswers: true ### Was beschreibt ein Objekt in der objektorientierten Programmierung? - [x] Eine Einheit mit Zustand (Attribute) und Verhalten (Methoden) - [ ] Eine reine Datenstruktur ohne Funktionen - [ ] Nur eine Methode in einem Programm - [ ] Eine Zeile in einer Datenbank ### Wozu dienen Methoden in einem Objekt? - [x] Zur Änderung des Objektzustands - [ ] Zur Definition von Klassen - [ ] Zum Kompilieren des Codes - [ ] Zur Initialisierung von Attributen ### Warum verwendet man Objekte bei wiederholenden Datenstrukturen? - [x] Um missverständliche Strukturen zu vermeiden - [x] Um redundante Strukturen zu vermeiden - [ ] Um Daten alphabetisch zu sortieren - [ ] Um den Programmfluss zu verlangsamen ### Welcher Nachteil ergibt sich aus unterschiedlichen Datentypen wie Liste und Tupel zur Darstellung eines Punkts? - [x] Sie haben unterschiedliche Syntax und Verhalten - [x] Sie führen zu uneinheitlichem Zugriff - [ ] Sie sind nicht speicherbar in Python - [ ] Sie brauchen zwingend gleiche Länge ### Warum helfen Objekte bei semantischen Problemen? - [x] Sie machen Bedeutung der Werte durch Attributnamen klar - [ ] Sie ersetzen alle Werte automatisch durch Strings - [ ] Sie benötigen keine Werte zur Laufzeit - [ ] Sie wandeln alles in Listen um ### Warum kann die Funktion `distance(a, b)` problematisch sein? ```python def distance(a, b): return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2) ``` - [x] Sie ist nicht auf einen bestimmten Typ beschränkt - [ ] Sie verwendet `math` und das ist nicht erlaubt - [ ] Sie benötigt Strings statt Zahlen - [ ] Sie berechnet die Distanz nicht korrekt ### Sortiere die folgenden Zeilen, um eine einfache Klasse `Punkt` korrekt zu definieren: ```python # Instanziiere ein Objekt ``` 1. `class Punkt:` 2. ` def __init__(self, x, y):` 3. ` self.x = x` 4. ` self.y = y` 5. `p = Punkt(1, 2)` ### Welcher Fehler steckt in diesem Beispiel? ```python class Hund: def bellen(): print("Wuff") h = Hund() h.bellen() ``` - [x] Der Methode fehlt der Parameter `self` - [ ] Die Methode heißt falsch - [ ] Die Klasse `Hund` darf nicht so definiert werden - [ ] Methoden dürfen nicht `print` verwenden ### Welcher Fehler kann in folgendem Beispiel auftreten? ```python import math def distance(a, b): return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2) print(distance("Berlin", "Paris")) ``` - [x] Strings sind keine gültigen Punktkoordinaten - [ ] Der Funktionsname ist reserviert - [ ] `math.sqrt` funktioniert nicht - [ ] Die Funktion hat keine Rückgabe ### Was ist der Zweck des Konstruktors `__init__()`? - [x] Initialisierung von Attributen beim Erzeugen eines Objekts - [x] Durchführung von Startaufgaben wie Berechnungen oder Prüfungen - [ ] Erstellen von Klassennamen - [ ] Kompilieren von Methoden ### Was passiert, wenn kein Konstruktor definiert wird? - [x] Python erzeugt automatisch einen leeren Konstruktor - [ ] Das Programm wird nicht ausgeführt - [ ] Es wird ein Fehler geworfen - [ ] Alle Attribute werden automatisch gesetzt ### Was beschreibt ein Instanzattribut? - [x] Eine Eigenschaft, die in jeder Objektinstanz unterschiedlich sein kann - [ ] Eine Eigenschaft, die alle Klassen gemeinsam haben - [ ] Ein Modul-Import - [ ] Eine Klasse im Konstruktor ### Warum verwendet man `self` im Konstruktor? - [x] Um Attribute der Instanz zu definieren - [ ] Um Klassenmethoden zu importieren - [ ] Um globale Variablen zu ersetzen - [ ] Um den Konstruktor aufzurufen ### Warum verwendet man Klassen in Python? - [x] Um Objekte zu strukturieren und Daten + Verhalten zu bündeln - [x] Um einen Bauplan für gleichartige Objekte zu schaffen - [ ] Um schneller zu kompilieren - [ ] Um Variablennamen zu ersetzen ### Was gehört typischerweise zur Definition einer Klasse? - [x] Welche Attribute ein Objekt hat - [x] Welche Methoden ein Objekt bereitstellt - [ ] Welche Module importiert werden - [ ] Welche Variablen global verfügbar sind ### Sortiere die folgenden Zeilen, um eine vollständige Klasse `Hund` mit Konstruktor zu definieren: ```python # Erzeuge ein Objekt mit Eigenschaften ``` 1. `class Hund:` 2. ` def __init__(self, name):` 3. ` self.name = name` 4. `mein_hund = Hund("Fiffi")` ### Was passiert, wenn bei folgender Klasse keine Argumente übergeben werden? ```python class Point: def __init__(self, x = 0.0, y = 0.0): self.x = x self.y = y p = Point() ``` - [x] Die Werte `0.0` werden für `x` und `y` verwendet - [ ] Es wird ein Fehler ausgegeben - [ ] Die Attribute bleiben undefiniert - [ ] Die Konstruktion ist in Python nicht erlaubt ### Was ist ein Klassenattribut? - [x] Ein Attribut, das allen Instanzen einer Klasse gemeinsam ist - [ ] Ein Attribut, das innerhalb der Methode `__init__()` definiert wird - [ ] Eine Methode mit Funktionsinhalt - [ ] Ein lokal verwendetes Attribut ### Welche Gefahr besteht bei veränderlichen Klassenattributen? - [x] Änderungen durch eine Instanz wirken sich auf alle anderen aus - [ ] Sie sind nicht zugänglich von Methoden - [ ] Sie werden nicht gespeichert - [ ] Sie führen zu Syntaxfehlern ### Was ist eine Methode in einer Klasse? - [x] Eine Funktion, die auf eine Instanz der Klasse angewendet wird - [ ] Eine globale Funktion - [ ] Eine Variable mit Funktionsinhalt - [ ] Eine Schleife mit Klassenname ### Warum hat eine Methode in einer Klasse immer `self` als ersten Parameter? - [x] Damit sie auf Attribute der Instanz zugreifen kann - [ ] Damit der Konstruktor korrekt aufgerufen wird - [ ] Damit man sie nur einmal aufrufen kann - [ ] Damit sie von außen nicht sichtbar ist ### Was ist eine Klasseninstanz? - [x] Ein konkretes Objekt, das nach dem Bauplan der Klasse erstellt wurde - [ ] Eine Methode einer Klasse - [ ] Eine spezielle Variable im Konstruktor - [ ] Eine Liste von Methoden ### Wie wird eine Instanz einer Klasse erzeugt? - [x] Durch Aufruf des Klassennamens mit passenden Argumenten - [ ] Durch Aufruf von `__init__()` direkt - [ ] Durch eine Zuweisung im Konstruktor - [ ] Durch Deklaration mit `new` ### Was ist der Vorteil benannter Parameter bei der Objekterzeugung? - [x] Die Reihenfolge der Argumente spielt keine Rolle mehr - [ ] Die Parameter werden automatisch validiert - [ ] Es wird automatisch `self` übergeben - [ ] Nur benannte Parameter sind erlaubt in Python ### Wie greift man auf das Attribut `x` eines Objekts `p` zu? - [x] Mit `p.x` - [ ] Mit `x.p` - [ ] Mit `p->x` - [ ] Mit `get(x, p)` ### Was gibt folgender Ausdruck zurück? ```python point_1.unit ``` - [x] Das Klassenattribut `unit` von `point_1` - [ ] Den Standardwert von `x` - [ ] Die Methode `unit()` als Funktion - [ ] Eine Fehlermeldung ### Was passiert bei folgender Anweisung? ```python point_1.x = 54.08 ``` - [x] Der Wert des Attributs `x` von `point_1` wird auf `54.08` gesetzt - [ ] Es wird ein neues Attribut `x` erstellt - [ ] Das Klassenattribut wird überschrieben - [ ] Eine neue Instanz wird erzeugt ### Was ist Polymorphismus? - [x] Die Fähigkeit, dass verschiedene Klassen die gleiche Methode unterschiedlich implementieren - [ ] Eine spezielle Art von Schleife - [ ] Eine Methode, die nur in einer Klasse existiert - [ ] Eine Variable, die mehrere Typen annehmen kann ### Was ist Kapselung? - [x] Das Verbergen von Implementierungsdetails und Zugriff auf Attribute über Methoden - [ ] Eine spezielle Art von Datenstruktur - [ ] Eine Methode, die nur in einer Klasse existiert - [ ] Eine Variable, die nur innerhalb der Klasse sichtbar ist ### Was ist Vererbung? - [x] Die Möglichkeit, eine Klasse von einer anderen abzuleiten und deren Eigenschaften zu übernehmen - [ ] Eine spezielle Art von Schleife - [ ] Eine Methode, die nur in einer Klasse existiert - [ ] Eine Variable, die mehrere Typen annehmen kann - [ ] Eine Klasse, die keine Attribute hat ### Was sind objektorientierte Programmierparadigmen? - [x] Polymorphismus - [x] Kapselung - [ ] Multiplizität - [ ] Redundanz - [x] Vererbung - [x] Generalisierung - [ ] Spezialisierung ### Was ist UML? - [x] Eine grafische Notation für Software-Design - [ ] Die Programmiersprache - [ ] Die Datenbank-Sprache - [ ] Eine Art von Algorithmus