"| | __repr__| gibt die Zeichenkette zurück, die durch print ausgegeben wird|\n",
"| | __repr__| gibt die Zeichenkette zurück, die durch print ausgegeben wird|\n",
"| |__init__ | wird automatisch beim erzeugen eines Objekts aufgerufen |\n",
"| |__init__ | wird automatisch beim erzeugen eines Objekts aufgerufen |\n",
"| |__del__ | wird automatisch aufgerufen, wenn das Objekt gelöscht wird |\n",
"| |__del__ | wird automatisch aufgerufen, wenn das Objekt gelöscht wird |\n",
"| [] | __getattr__(self,index),__setattr__(self,index,val) | wird durch den Indexoperator aufgerufen|\n",
"| [] | __getitem__(self,index),__setitem__(self,index,val) | wird durch den Indexoperator aufgerufen|\n",
"| | __len__| wird aufgerufen, um die Länge des Objektes mit len zu ermitteln|\n",
"| | __len__| wird aufgerufen, um die Länge des Objektes mit len zu ermitteln|\n",
"\n",
"\n",
"\n",
"\n",
"Da die Methoden Objektmethoden sind muss ihr erster Parameter das angesprochene ```self``` sein. Weitere Parameter kommen je nach Operator hinzu. Alle Rechenoperatoren sind z.B. binäre Operatoren. Die magic methods haben einen zweiten Parameter, der das Objekt auf der rechten Seite des + enthält\n",
"Da die Methoden Objektmethoden sind muss ihr erster Parameter das angesprochene ```self``` sein. Weitere Parameter kommen je nach Operator hinzu. Alle Rechenoperatoren sind z.B. binäre Operatoren. Die magic methods haben einen zweiten Parameter, der das Objekt auf der rechten Seite des + enthält\n",
"\n",
"\n",
"Die verfügbaren Magic methods sind fest bestimmt (siehe Liste ganz unten auf https://www.tutorialsteacher.com/python/magic-methods-in-python), und beginnen und enden immer mit zwei Unterstrichen ```__```. Die bereits bekannte Methode ```__init__()``` gehört demnach auch zu diesen Methoden. Im Fallbeispiel oben ist dies beispielhaft für die Verwendung in ```print( )``` gezeigt. Das ist die Methode ```__repr__(self)```.\n",
"Die verfügbaren Magic methods sind fest bestimmt (siehe Liste ganz unten auf https://rszalski.github.io/magicmethods/#operators), und beginnen und enden immer mit zwei Unterstrichen ```__```. Die bereits bekannte Methode ```__init__()``` gehört demnach auch zu diesen Methoden. Im Fallbeispiel oben ist dies beispielhaft für die Verwendung in ```print( )``` gezeigt. Das ist die Methode ```__repr__(self)```.\n",
### **Einleitendes Thema - Struktur und Übersicht mittels Objektorientierter Programmierung**
### **Einleitendes Thema - Struktur und Übersicht mittels Objektorientierter Programmierung**
Objektorientierte Programmierung folgt der Idee, zusammengehörige Daten (also Variablen) gemeinsam mit Funktionalität in Klassen zusammenzufassen.\
Objektorientierte Programmierung folgt der Idee, zusammengehörige Daten (also Variablen) gemeinsam mit Funktionalität in Klassen zusammenzufassen.\
Dabei werden mehrere Ziele verfolgt. Zum einen ergibt sich eine einfachere Möglichkeit ein Programm in logische Teilaspekte zu zerlegen. Wir haben mit den Listen schon eine Klasse kennengelernt. Bei der Objektorientierten Programmierung können wir uns eigene Klassen nach belieben definieren. Nehmen wir an, wir wollen die Beziehung mehrerer Menschen untereinander in einem Programm abbilden. Jeder Mensch hat einen Namen, ein Alter, vieleicht Kinder und kann verheiratet sein. Das sind 4 Variablen für jeden Menschen, die zusammengehören. Immer wenn ein weiterer Mensch diesem Programm hinzugefügt wird, müssen diese 4 Variablen definiert werden. Es wäre daher sinvoll, sie auch zusammen zu gruppieren. Das ermöglichen Klassen. Eine Klasse ist eine Art Schablone, z.B. für einen Menschen. Erschaffen wir in unserem Programm einen konkreten Menschen, nennt man diesen eine Instanz der Klasse Mensch. Die 4 Variablen, die zusammengefasst diesen Menschen beschreiben nennt man dann Attribute. Einer Klasse können auch sogenannte Methoden zugeordnet werden. Das sind Funktionen, die spezifisch etwas mit dieser speziellen Klasse machen. Z.B. hat die Klasse ```list``` eine Methode ```append```, die ihr schon kennengelernt habt
Dabei werden mehrere Ziele verfolgt. Zum einen ergibt sich eine einfachere Möglichkeit ein Programm in logische Teilaspekte zu zerlegen. Wir haben mit den Listen schon eine Klasse kennengelernt. Bei der Objektorientierten Programmierung können wir uns eigene Klassen nach belieben definieren. Nehmen wir an, wir wollen die Beziehung mehrerer Menschen untereinander in einem Programm abbilden. Jeder Mensch hat einen Namen, ein Alter, vieleicht Kinder und kann verheiratet sein. Das sind 4 Variablen für jeden Menschen, die zusammengehören. Immer wenn ein weiterer Mensch diesem Programm hinzugefügt wird, müssen diese 4 Variablen definiert werden. Es wäre daher sinvoll, sie auch zusammen zu gruppieren. Das ermöglichen Klassen. Eine Klasse ist eine Art Schablone, z.B. für einen Menschen. Erschaffen wir in unserem Programm einen konkreten Menschen, nennt man diesen eine Instanz der Klasse Mensch. Die 4 Variablen, die zusammengefasst diesen Menschen beschreiben nennt man dann Attribute. Einer Klasse können auch sogenannte Methoden zugeordnet werden. Das sind Funktionen, die spezifisch etwas mit dieser speziellen Klasse machen. Z.B. hat die Klasse ```list``` eine Methode ```append```, die ihr schon kennengelernt habt
# **Übersicht - Objektoriente Programmierung mit Python**
# **Übersicht - Objektoriente Programmierung mit Python**
In diesem Grundlagen Notebook wird ein Überblick über das Objekt orientierte Programmieren (OOP) gegeben. Wir beschäftigen uns mit dem Erstellen von Klassen, zusammen mit Ihren Attributen und Methoden.
In diesem Grundlagen Notebook wird ein Überblick über das Objekt orientierte Programmieren (OOP) gegeben. Wir beschäftigen uns mit dem Erstellen von Klassen, zusammen mit Ihren Attributen und Methoden.
Die wichtige Konstruktormethode ```__init__(self, ...)``` wird eingeführt und es werden verschiedene Beispiele für Klassen gegeben.
Die wichtige Konstruktormethode ```__init__(self, ...)``` wird eingeführt und es werden verschiedene Beispiele für Klassen gegeben.
Klassen kann man sich wie selbst definierte (komplexe) Datentypen vorstellen. Die Objekte sind dann Variablen dieses Datentyps. Im Fallbeispiel wurde z.B. die Klasse ```Human``` definiert, ```jenny``` und ```paul``` werden dann als Objekte dieser Klasse instanziiert. Es ist eine Konvention, Klassennamen mit einem Großbuchstaben zu beginnen, und Objektnamen mit einem Kleinbuchstaben zu beginnen.
Klassen kann man sich wie selbst definierte (komplexe) Datentypen vorstellen. Die Objekte sind dann Variablen dieses Datentyps. Im Fallbeispiel wurde z.B. die Klasse ```Human``` definiert, ```jenny``` und ```paul``` werden dann als Objekte dieser Klasse instanziiert. Es ist eine Konvention, Klassennamen mit einem Großbuchstaben zu beginnen, und Objektnamen mit einem Kleinbuchstaben zu beginnen.
Für eine Klassendefinition reicht prinzipiell ein Klassenname aus
Für eine Klassendefinition reicht prinzipiell ein Klassenname aus
``` python
``` python
classExampleClass:
classExampleClass:
pass
pass
```
```
> Mit dem Stichwort ```pass``` wird hier ein Platzhalter für noch kommenden Code erzeugt, mit dem wir später der Klasse Elemente hinzufügen werden. <br>
> Mit dem Stichwort ```pass``` wird hier ein Platzhalter für noch kommenden Code erzeugt, mit dem wir später der Klasse Elemente hinzufügen werden. <br>
Ein Objekt ```exampleObject``` der Klasse ```ExampleClass``` kann nun mittels
Ein Objekt ```exampleObject``` der Klasse ```ExampleClass``` kann nun mittels
```python
```python
exampleObject=ExampleClass()
exampleObject=ExampleClass()
```
```
initialisiert werden.
initialisiert werden.
%% Cell type:markdown id:d48b0115 tags:
%% Cell type:markdown id:d48b0115 tags:
### **Attribute**
### **Attribute**
Attribute sind Variablen, die zu einer Klasse oder einem Objekt gehören. Sie können beispielsweise die Eigenschaften eines Objektes beschreiben, wie zum Beispiel in dem Fallbeispiel das Alter ```self.age```.
Attribute sind Variablen, die zu einer Klasse oder einem Objekt gehören. Sie können beispielsweise die Eigenschaften eines Objektes beschreiben, wie zum Beispiel in dem Fallbeispiel das Alter ```self.age```.
Um den Objekten einer Klasse eigene Attribute zuzuordnen, werden Variableninitialisierungen innerhalb einer Klassendefinition in die Definition des Klassen-Konstruktors ```__init__(self, ...)``` (vgl. Abschnitt zu Methoden) geschrieben:
Um den Objekten einer Klasse eigene Attribute zuzuordnen, werden Variableninitialisierungen innerhalb einer Klassendefinition in die Definition des Klassen-Konstruktors ```__init__(self, ...)``` (vgl. Abschnitt zu Methoden) geschrieben:
> Jedes Objekte der Klasse ```ExampleClass``` hat ein individuelles Objektattribute```exampleObjectAttribute```.<br>
> Jedes Objekte der Klasse ```ExampleClass``` hat ein individuelles Objektattribute```exampleObjectAttribute```.<br>
> Objektattribute werden auch Instanzvariablen genannt. <br>
> Objektattribute werden auch Instanzvariablen genannt. <br>
> Ein Objektattribut kann mittels der Punktnotation ```exampleObject.exampleObjectAttribute``` aufgerufen werden und so im Hauptprogramm oder auch dann innerhalb der Klasse mit self.exampleObjectAttribute verwendet werden.
> Ein Objektattribut kann mittels der Punktnotation ```exampleObject.exampleObjectAttribute``` aufgerufen werden und so im Hauptprogramm oder auch dann innerhalb der Klasse mit self.exampleObjectAttribute verwendet werden.
Um einer gesamten Klasse Attribute zuzuordnen, werden Variableninitialisierungen innerhalb der Klassendefinition geschrieben:
Um einer gesamten Klasse Attribute zuzuordnen, werden Variableninitialisierungen innerhalb der Klassendefinition geschrieben:
```python
```python
classExampleClass:
classExampleClass:
exampleClassAttribute=0# Klassenattribut
exampleClassAttribute=0# Klassenattribut
```
```
> Das Klassenattribut ```exampleClassAttribute``` teilen sich alle Objekte der Klasse ```ExampleClass```.<br>
> Das Klassenattribut ```exampleClassAttribute``` teilen sich alle Objekte der Klasse ```ExampleClass```.<br>
> Klassenattribute werden auch statische Attribute genannt. <br>
> Klassenattribute werden auch statische Attribute genannt. <br>
> Das Klassenattribute kann mittels ```ExampleClass.exampleClassAttribute``` aufgerufen werden und so dann innerhalb der Klasse oder auch im Hauptprogramm von allen Objekten der Klasse verwendet werden.
> Das Klassenattribute kann mittels ```ExampleClass.exampleClassAttribute``` aufgerufen werden und so dann innerhalb der Klasse oder auch im Hauptprogramm von allen Objekten der Klasse verwendet werden.
Methoden sind Funktionen einer Klasse, die auf die Klassen- und Objektattribute zugreifen können. Sie werden genutzt, um mit der Klasse verbundene Operationen durchzuführen.
Methoden sind Funktionen einer Klasse, die auf die Klassen- und Objektattribute zugreifen können. Sie werden genutzt, um mit der Klasse verbundene Operationen durchzuführen.
Der Konstruktor ```__init__(self, ...)``` ist beispielsweise eine Methode seiner Klasse und wird bei der Initialisierung eines Objektes von diesem aufgerufen.
Der Konstruktor ```__init__(self, ...)``` ist beispielsweise eine Methode seiner Klasse und wird bei der Initialisierung eines Objektes von diesem aufgerufen.
Es können jedoch auch eigene Methoden formuliert werden.
Es können jedoch auch eigene Methoden formuliert werden.
Dazu werden mit "Funktionsdefinitionen" innerhalb der Klassendefinition neue Methoden erzeugt.
Dazu werden mit "Funktionsdefinitionen" innerhalb der Klassendefinition neue Methoden erzeugt.
Eine Funktion, deren Definition innerhalb einer Klassendefinition steht, wird somit Methode genannt und wird auch anders verwendet als eine reguläre Funktion:
Eine Funktion, deren Definition innerhalb einer Klassendefinition steht, wird somit Methode genannt und wird auch anders verwendet als eine reguläre Funktion:
#### **Punktnotation: Methodenaufrufe und Attributzugriffe**
#### **Punktnotation: Methodenaufrufe und Attributzugriffe**
Methoden können von Objekten, oder manchmal auch Klassen, aufgerufen werden.
Methoden können von Objekten, oder manchmal auch Klassen, aufgerufen werden.
Dazu wird die "dot-notation" verwendet, also die Punktnotation.
Dazu wird die "dot-notation" verwendet, also die Punktnotation.
Möchte man mit einem Objekt ```exampleObject``` der Klasse ```ExampleClass``` die Methode ```exampleObjectMethod()``` der Klasse ```ExampleClass``` aufrufen, geschieht das mit der Punktnotation: ```exampleObject.exampleObjectMethod()```.
Möchte man mit einem Objekt ```exampleObject``` der Klasse ```ExampleClass``` die Methode ```exampleObjectMethod()``` der Klasse ```ExampleClass``` aufrufen, geschieht das mit der Punktnotation: ```exampleObject.exampleObjectMethod()```.
Dabei wird eine Referenz, auf das aufrufende Objekt selber, lokal beim Methodenaufruf in ```self``` gespeichert.
Dabei wird eine Referenz, auf das aufrufende Objekt selber, lokal beim Methodenaufruf in ```self``` gespeichert.
Während nun die Methode ```exampleObjectMethod()``` ausgeführt wird, steht somit das Objekt ```exampleObject``` über ```self``` weiterhin zur Verfügung.
Während nun die Methode ```exampleObjectMethod()``` ausgeführt wird, steht somit das Objekt ```exampleObject``` über ```self``` weiterhin zur Verfügung.
Es können somit weitere Methoden aufgerufen werden oder es kann auch auf Attribute zugegriffen werden.
Es können somit weitere Methoden aufgerufen werden oder es kann auch auf Attribute zugegriffen werden.
Das passiert ohne, dass beim Schreiben der Methoden bekannt sein musste, welche Objekte später diese Methode aufrufen werden.
Das passiert ohne, dass beim Schreiben der Methoden bekannt sein musste, welche Objekte später diese Methode aufrufen werden.
Da erst bei Ausführung der Methode die Referenz des aufrufenden Objektes in ```self``` lokal gespeichert wird.\
Da erst bei Ausführung der Methode die Referenz des aufrufenden Objektes in ```self``` lokal gespeichert wird.\
Auch auf die Attribute eines Objektes wird mit der Punktnotation zugegriffen.
Auch auf die Attribute eines Objektes wird mit der Punktnotation zugegriffen.
Entweder mit dem Objekt direkt selber ```exampleObject.exampleObjectAttribute``` oder über die lokale Referenz ```self.exampleObjectAttribute```.
Entweder mit dem Objekt direkt selber ```exampleObject.exampleObjectAttribute``` oder über die lokale Referenz ```self.exampleObjectAttribute```.
Ist der Inhalt einer Methode unabhängig von dem Objekt das sie aufruft, dann wird/muss kein ```(self...)``` in der Parameterliste übergeben werden.
Ist der Inhalt einer Methode unabhängig von dem Objekt das sie aufruft, dann wird/muss kein ```(self...)``` in der Parameterliste übergeben werden.
Es handelt sich bei einer Methode um eine Klassenmethode, wenn kein ```(self...)``` in der Parameterliste vorhanden ist.
Es handelt sich bei einer Methode um eine Klassenmethode, wenn kein ```(self...)``` in der Parameterliste vorhanden ist.
Bei Klassenmethoden werden keine Attribute eines aufrufenden Objektes verwendet oder bearbeitet, da die Information eines aufrufenden Objektes, also das ```self```, nicht vorhanden sind.\
Bei Klassenmethoden werden keine Attribute eines aufrufenden Objektes verwendet oder bearbeitet, da die Information eines aufrufenden Objektes, also das ```self```, nicht vorhanden sind.\
Klassenmethoden können über den Klassennamen aufgerufen werden: ```ExampleClass.exampleClassMethod()```.
Klassenmethoden können über den Klassennamen aufgerufen werden: ```ExampleClass.exampleClassMethod()```.
Jedoch nicht über Objekte der Klasse: ~```exampleObject.exampleClassMethod()```~. Bei Ersterem war nie die Information eines aufrufenden Objektes von Interesse. Bei Letzterem findet die Information, über das aufrufende Objekt, keinen passenden Parameter in der Klassenmethodendefinition, wo diese Info gespeichert werden könnte, sodass die Methoden-Signatur nicht zum Methoden-Aufruf passt.
Jedoch nicht über Objekte der Klasse: ~```exampleObject.exampleClassMethod()```~. Bei Ersterem war nie die Information eines aufrufenden Objektes von Interesse. Bei Letzterem findet die Information, über das aufrufende Objekt, keinen passenden Parameter in der Klassenmethodendefinition, wo diese Info gespeichert werden könnte, sodass die Methoden-Signatur nicht zum Methoden-Aufruf passt.
Objektmethoden müssen bei der Definition in ihrer Parameterliste immer als erstes Argument eine Referenz auf das Objekt selbst haben, um auf dessen Elemente zugreifen zu können. Diese Referenz ist ```self```. Bei Aufruf der Objektmethode, wird das ```self``` automatisch übergeben und wird daher nicht explizit in der Parameterliste angegeben. Die Methode ```exampleObjectMethof(self)``` dem Beispiel aus dem vorherigen Abschnitt würde also im Programm folgendermaßen aufgerufen:
Objektmethoden müssen bei der Definition in ihrer Parameterliste immer als erstes Argument eine Referenz auf das Objekt selbst haben, um auf dessen Elemente zugreifen zu können. Diese Referenz ist ```self```. Bei Aufruf der Objektmethode, wird das ```self``` automatisch übergeben und wird daher nicht explizit in der Parameterliste angegeben. Die Methode ```exampleObjectMethof(self)``` dem Beispiel aus dem vorherigen Abschnitt würde also im Programm folgendermaßen aufgerufen:
```python
```python
exampleObject=exampleClass()
exampleObject=exampleClass()
exampleObject.exampleObjectMethod()
exampleObject.exampleObjectMethod()
```
```
Es ist übrigends nicht nötig, die Referenz auf das eigene Objekt ```self``` zu nennen. Es ist einfach immer der erste Parameter der Objektmethode. Die Benennung ist allerdings eine Konvention und es ist sinnvoll sich daran zu halten
Es ist übrigends nicht nötig, die Referenz auf das eigene Objekt ```self``` zu nennen. Es ist einfach immer der erste Parameter der Objektmethode. Die Benennung ist allerdings eine Konvention und es ist sinnvoll sich daran zu halten
%% Cell type:markdown id:fd40aee3 tags:
%% Cell type:markdown id:fd40aee3 tags:
#### **Spezielle Methoden - Magic methods**
#### **Spezielle Methoden - Magic methods**
Klassen kann man sich wie einen selbst definierten Datentypen vorstellen. Datentypen, die wir bereits kennen, können oft mit Operatoren interagieren. Dabei hängt es vom Datentyp ab, was der Operator genau tut. Z.B. können zwei Listen durch ein + aneinandergehängt werden. Ein solches Verhalten kann man auch mit selbst definierten Klassen erreichen. Neben Operatoren gibt es auch verschiedene vordefinierte Funktionen, die ähnliches Verhalten zeigen. Z.B. ```len()``` oder ```print```.\
Klassen kann man sich wie einen selbst definierten Datentypen vorstellen. Datentypen, die wir bereits kennen, können oft mit Operatoren interagieren. Dabei hängt es vom Datentyp ab, was der Operator genau tut. Z.B. können zwei Listen durch ein + aneinandergehängt werden. Ein solches Verhalten kann man auch mit selbst definierten Klassen erreichen. Neben Operatoren gibt es auch verschiedene vordefinierte Funktionen, die ähnliches Verhalten zeigen. Z.B. ```len()``` oder ```print```.\
Um die Verwendung von Operatoren zusammen mit eigenen Klassen zu ermöglichen müssen spezielle Objektmethoden programmiert werden, sogenannte "magic methods". Betrachten wir die Programmzeile\
Um die Verwendung von Operatoren zusammen mit eigenen Klassen zu ermöglichen müssen spezielle Objektmethoden programmiert werden, sogenannte "magic methods". Betrachten wir die Programmzeile\
```a + b```
```a + b```
Falls a keine einfache Zahl ist (dort funktioniert der Mechanismus etwas anders) wird diese Zeile automatisch ersetzt durch:
Falls a keine einfache Zahl ist (dort funktioniert der Mechanismus etwas anders) wird diese Zeile automatisch ersetzt durch:
```a.__add__(b)```
```a.__add__(b)```
Also durch einen Methodenaufruf. Sofern die Klasse, von der a eine Instanz ist, diese Objektmethode implementiert, kann sie zusammen mit Operatoren verwendet werden. Das funktioniert nicht nur für das +. Eine Tabelle mit einigen magic methods
Also durch einen Methodenaufruf. Sofern die Klasse, von der a eine Instanz ist, diese Objektmethode implementiert, kann sie zusammen mit Operatoren verwendet werden. Das funktioniert nicht nur für das +. Eine Tabelle mit einigen magic methods
|Operator | Methode| Anmerkung|
|Operator | Methode| Anmerkung|
|---------|----------|----------|
|---------|----------|----------|
|+ |__add__ | |
|+ |__add__ | |
|- |__sub__ | |
|- |__sub__ | |
|/ |__truediv__| |
|/ |__truediv__| |
|* |__mul__ | |
|* |__mul__ | |
|//| __floordiv__| |
|//| __floordiv__| |
|% |__mod__| |
|% |__mod__| |
|** |__pow__| |
|** |__pow__| |
|>= | __ge__| |
|>= | __ge__| |
|< | __lt__| |
|< | __lt__| |
|==| __eq__| |
|==| __eq__| |
| | __repr__| gibt die Zeichenkette zurück, die durch print ausgegeben wird|
| | __repr__| gibt die Zeichenkette zurück, die durch print ausgegeben wird|
| |__init__ | wird automatisch beim erzeugen eines Objekts aufgerufen |
| |__init__ | wird automatisch beim erzeugen eines Objekts aufgerufen |
| |__del__ | wird automatisch aufgerufen, wenn das Objekt gelöscht wird |
| |__del__ | wird automatisch aufgerufen, wenn das Objekt gelöscht wird |
| [] | __getattr__(self,index),__setattr__(self,index,val) | wird durch den Indexoperator aufgerufen|
| [] | __getitem__(self,index),__setitem__(self,index,val) | wird durch den Indexoperator aufgerufen|
| | __len__| wird aufgerufen, um die Länge des Objektes mit len zu ermitteln|
| | __len__| wird aufgerufen, um die Länge des Objektes mit len zu ermitteln|
Da die Methoden Objektmethoden sind muss ihr erster Parameter das angesprochene ```self``` sein. Weitere Parameter kommen je nach Operator hinzu. Alle Rechenoperatoren sind z.B. binäre Operatoren. Die magic methods haben einen zweiten Parameter, der das Objekt auf der rechten Seite des + enthält
Da die Methoden Objektmethoden sind muss ihr erster Parameter das angesprochene ```self``` sein. Weitere Parameter kommen je nach Operator hinzu. Alle Rechenoperatoren sind z.B. binäre Operatoren. Die magic methods haben einen zweiten Parameter, der das Objekt auf der rechten Seite des + enthält
Die verfügbaren Magic methods sind fest bestimmt (siehe Liste ganz unten auf https://www.tutorialsteacher.com/python/magic-methods-in-python), und beginnen und enden immer mit zwei Unterstrichen ```__```. Die bereits bekannte Methode ```__init__()``` gehört demnach auch zu diesen Methoden. Im Fallbeispiel oben ist dies beispielhaft für die Verwendung in ```print( )``` gezeigt. Das ist die Methode ```__repr__(self)```.
Die verfügbaren Magic methods sind fest bestimmt (siehe Liste ganz unten auf https://rszalski.github.io/magicmethods/#operators), und beginnen und enden immer mit zwei Unterstrichen ```__```. Die bereits bekannte Methode ```__init__()``` gehört demnach auch zu diesen Methoden. Im Fallbeispiel oben ist dies beispielhaft für die Verwendung in ```print( )``` gezeigt. Das ist die Methode ```__repr__(self)```.
> <font color='grey'>*Weiter Informationen zu Klassen können in der [offiziellen Dokumentation von Python](https://docs.python.org/3/tutorial/classes.html) nachgelesen werden*</font>
> <font color='grey'>*Weiter Informationen zu Klassen können in der [offiziellen Dokumentation von Python](https://docs.python.org/3/tutorial/classes.html) nachgelesen werden*</font>
Vererbung ist ein Feature, das in Python nicht so häufig verwendet wird. Es ist möglich aus selbst definierten Klassen ganze Hierarchien aufzubauen. Um die Idee zu verdeutlichen konnen wir auf das Beispiel mit der Klasse Human zurück. Man könnte sagen ein Student ist auch ein Mensch, ein Mensch ist aber nicht zwingend ein Student. Ein Student teilt alle Merkmale eines Menschen, kann aber zusätzliche Attribute und Methoden besitzen. In Python kann diese Beziehung folgendermaßen ausgedrückt werden (Die Zelle funktioniert nur, wenn du die Zelle am Anfang des Notebooks ausführst, da die Klasse Human definiert sein muss)
Vererbung ist ein Feature, das in Python nicht so häufig verwendet wird. Es ist möglich aus selbst definierten Klassen ganze Hierarchien aufzubauen. Um die Idee zu verdeutlichen konnen wir auf das Beispiel mit der Klasse Human zurück. Man könnte sagen ein Student ist auch ein Mensch, ein Mensch ist aber nicht zwingend ein Student. Ein Student teilt alle Merkmale eines Menschen, kann aber zusätzliche Attribute und Methoden besitzen. In Python kann diese Beziehung folgendermaßen ausgedrückt werden (Die Zelle funktioniert nur, wenn du die Zelle am Anfang des Notebooks ausführst, da die Klasse Human definiert sein muss)
Zusammengefasst erbt die neue Klasse (auch sub-Klasse) sämtliche Methoden von der Elternklasse (super-Klasse). Werden Methoden in der sub-Klasse neu definiert, dann werden diese Methoden sozusagen überschrieben. Das alleine würde wenig neue Funktionalität bieten. Die Klasse Student definiert z.B. die Methode ```__init__``` neu. Deshalb wird die Methode der Klasse Human auch nicht mehr aufgerufen, wenn die Klasse Student instanziiert wird. Gleiches gilt für die Methode ```mary```.\
Zusammengefasst erbt die neue Klasse (auch sub-Klasse) sämtliche Methoden von der Elternklasse (super-Klasse). Werden Methoden in der sub-Klasse neu definiert, dann werden diese Methoden sozusagen überschrieben. Das alleine würde wenig neue Funktionalität bieten. Die Klasse Student definiert z.B. die Methode ```__init__``` neu. Deshalb wird die Methode der Klasse Human auch nicht mehr aufgerufen, wenn die Klasse Student instanziiert wird. Gleiches gilt für die Methode ```mary```.\
Es ist allerdings trotzdem möglich die Methoden gleichen Namens der super-Klasse aufzurufen. Das funktioniert mit ```super().Methode()```, wie in der ```__init``` Methode von Student gezeigt. Gerade wenn die ```__init__``` Methode in einer sub-Klasse neu definiert wird sollte genau diese Zeile an der ersten Stelle stehen, damit das Konzept der Vererbung wirklich funktioniert. Wird die ```__del``` Methode in einer sub-Klasse neu definiert, sollte ein ```super().__del__()``` als letzte Zeile dieser Methode eingeplant werden. Die meißten anderen Programmiersprachen machen genau das automatisch, bei Python muss man selbst daran denken. Praktisch gesehen ist Vererbung ein Konzept, dem ihr vermutlich in Python nicht so häufig begegnen werdet. Ihr könnt damit eigentlich nur bestehende Klassen nachträglich erweitern.
Es ist allerdings trotzdem möglich die Methoden gleichen Namens der super-Klasse aufzurufen. Das funktioniert mit ```super().Methode()```, wie in der ```__init``` Methode von Student gezeigt. Gerade wenn die ```__init__``` Methode in einer sub-Klasse neu definiert wird sollte genau diese Zeile an der ersten Stelle stehen, damit das Konzept der Vererbung wirklich funktioniert. Wird die ```__del``` Methode in einer sub-Klasse neu definiert, sollte ein ```super().__del__()``` als letzte Zeile dieser Methode eingeplant werden. Die meißten anderen Programmiersprachen machen genau das automatisch, bei Python muss man selbst daran denken. Praktisch gesehen ist Vererbung ein Konzept, dem ihr vermutlich in Python nicht so häufig begegnen werdet. Ihr könnt damit eigentlich nur bestehende Klassen nachträglich erweitern.
"## <font color='blue'>**Die Grundlagen von Objektorientierter Programmierung**</font>\n",
"\n",
"In den vorherigen Übungen wurden die grundlegenden Elemente zur Ablaufsteuerung von Programmen behandelt. In dieser Übung führen wir ein wichtiges Konzept der Programmierung ein, die Objektorientierte Programmierung. Diese hilft sowohl bei der Strukturierung komplexer Projekte, der Verständlichkeit von Quellcode und dem gemeinsamen Arbeiten. Die Grundidee dahinter ist es, zusammengehörende Daten zusammen mit dazu passenden Operationen in (selbst) definierten Datentypen zusammenzufassen. \n",
"\n",
"### **Weitere Notebooks, die dir helfen könnten**\n",
"Für den ersten Kontakt mit Klassen und Objekten wollen wir mit einem einfachen Alltagsbeispiel beginnen.\n",
"\n",
"Erstelle einen Datentyp, mit dem ein Supermarkt seine Artikel verwalten kann. Die Artikel sollen einen Namen, eine Menge und einen Preis pro Mengeneinheit haben. Dazu soll die Datenstruktur Methoden besitzen, mit denen der Supermarkt den Lagerbestand auffüllen kann, sowie eine bestimmte Menge eines Artikels (soweit vorhanden) verkaufen kann. Beim Verkaufen soll eine Ausgabe über die verkaufte Menge, den Preis und die verbleibende Menge gegeben werden. Außerdem soll ermittelbar sein, wieviele verschiedene Artikel es gibt.\n",
"\n",
"Teste das Programm anhand eines Supermarktes der zu Beginn 500 kg Zucker zu 1 Euro pro kg, und 1000 kg Mehl zu 1,50 Euro pro kg auf Lager hat. Er füllt das Lager um 600 kg Zucker auf, und ein Kunde kauft 4 kg Mehl. Nutze die erstellte Klasse, um die neuen Lagerbestände zu ermitteln, sowie den vom Kunden gezahlten Preis. "
]
},
{
"cell_type": "markdown",
"id": "b29c819a-aedd-4232-bcd7-eedcd810695c",
"metadata": {},
"source": [
"## <font color='blue'>*Lösung*</font>\n",
"\n",
"Klassendefinition und Instanziierung:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "6b0c8c9d-f3b6-47fc-9de7-04f557ed5511",
"metadata": {},
"outputs": [],
"source": [
"class Artikel:#definiert eine Klasse namens artikel\n",
" \n",
" anz_artikel = 0 #Eine Klassenvariable, die mitzählt, wie viele verschiedene Objekte der Klasse Artikel existieren\n",
" \n",
" def __init__(self, name, menge, preis):#Der Konstruktor der Klasse. Wird immer aufgerufen, wenn ein Objekt der Klasse erzeugt wird\n",
" self.name = name#legt eine Objektvariable an, die den Namen enthalten soll, der dem Konstruktor übergeben wurde\n",
" if menge > 0:#überprüft, ob die angegebene Startmenge größer 0 ist\n",
" self.menge = menge#wenn ja wird eine Objektvariable angelegt, die die Startmenge enthält\n",
" else:\n",
" self.menge = 0#Falls die angegebene Menge nicht größer 0 war, wird die Startmenge auf 0 gesetzt. Es soll keine negative menge möglich sein\n",
" self.preis = preis#Der Preis des Artikels wird auch in objektvariablen gespeichert\n",
" Artikel.anz_artikel += 1#Die Anzahl der existierenden Artikel um eins erhöhen\n",
" \n",
" def auffuellen(self, menge):#Eine Objektmethode, die die menge der Waren auffüllt\n",
" self.menge += menge#addiert die aufzufüllende Menge auf die bestehende Menge des Artikels\n",
" \n",
" def verkaufen(self, verkaufsmenge):#Eine Objektmethode zum Verkaufen\n",
" if verkaufsmenge > self.menge:#Falls mehr verkauft werden soll als überhaupt vorhanden ist\n",
" verkaufsmenge = self.menge#die Verkaufsmenge auf die gerade verfügbare Menge reduzieren\n",
" self.menge -= verkaufsmenge#die Verkaufsmenge von der Vorratsmenge des Artikels abziehen\n",
" print(f\"{verkaufsmenge} kg {self.name} fuer {self.preis*verkaufsmenge} Euro verkauft. Es verbleiben {self.menge} kg auf Lager.\")#Ausgabe was verkauft wurde und wie viel es gekostet hat. Diese Schreibweise für Zeichenketten ist sehr praktisch, wenn man viele Zahlen einbinden möchte\n",
" def __del__(self):#der Destruktor. Wird aufgerufen, wenn ein Objekt gelöscht wird\n",
" Artikel.anz_artikel-=1#Die Gesamtzahl der vorhandenen Artikel muss um eins heruntergezählt werden, da ein Artikel gerade gelöscht wird\n",
"zucker = Artikel(\"Zucker\", 500., 1.)#Erzeugt zucker. Ein Objekt der Klasse Artikel. Die Parameter sind nötig, da __init__ von Artikel 3 Parameter benötigt (+ das self, das bei Objektmethodenaufrufen implizit der Parameterliste hinzugefügt wird)\n",
"mehl = Artikel(\"Mehl\", 1000., 1.5)\n"
]
},
{
"cell_type": "markdown",
"id": "d046ee6a",
"metadata": {},
"source": [
"Auffüll- und Verkaufsoperationen, sowie Testausgaben (beliebig oft wiederholbar):"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "7b68c5f0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4 kg Mehl fuer 6.0 Euro verkauft. Es verbleiben 996.0 kg auf Lager.\n",
"Bestand Zucker: 1100.0 kg\n",
"Bestand Mehl: 996.0 kg\n"
]
}
],
"source": [
"zucker.auffuellen(600)#Ruft eine Objektmethode auf. Der Parameter self der Funktion auffuellen wird hierbei mit dem Objekt zucker initialisiert, da zucker vor dem Punkt steht\n",
"mehl.verkaufen(4)#Das gleiche fügr Mehl mit der verkaufen-Methode\n",
"\n",
"print(f\"Bestand Zucker: {zucker.menge} kg\")#Auf Objektvariablen kann mit dem punkt zugegriffen werden. zucker.menge ist die Variable menge des Objetkes zucker\n",
"print(f\"Bestand Mehl: {mehl.menge} kg\")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "59fd42a8-6344-40a7-af32-5be4e24f2fa1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Anzahl der definierten Artikel 2\n",
"Anzahl der definierten Artikel 2\n",
"Anzahl der definierten Artikel 2\n",
"Anzahl der definierten Artikel 1\n"
]
}
],
"source": [
"#Ein kurzes Beispiel um dir noch einmal die Funktion von Python Variablen zu verdeutlichen\n",
"print(\"Anzahl der definierten Artikel\",Artikel.anz_artikel)# Es gibt 2 Artikel, mehl und zucker. \n",
"mehl2=mehl#Erzeugt keinen neuen Artikel mehl2 und mehl verweisen auf das selbe Objekt der Klasse Artikel\n",
"print(\"Anzahl der definierten Artikel\",Artikel.anz_artikel)# Es gibt immer noch 2 Artikel. mehl2 und mehl sind der identische Artikel\n",
"mehl=2#Belegt die Variable mehl mit einem neun Wert bzw. Objekt. Einer 2\n",
"print(\"Anzahl der definierten Artikel\",Artikel.anz_artikel)# Es gibt immer noch 2 Artikel. mehl2 und zucker. Da das \"Mehl\"-Objekt mit mehl2 einen zweiten Namen hat wurde es in der letzten Zeile nicht gelöscht\n",
"mehl2=4#Die Variable mehl2 wird jetzt auch neu belegt. Für das \"Mehl\"-Objekt gibt es jetzt keinen Variablennamen mehr. Es wird dadurch gelöscht\n",
"print(\"Anzahl der definierten Artikel\",Artikel.anz_artikel)# Es gibt nur noch einen Artikel. Den Zucker"
]
},
{
"cell_type": "markdown",
"id": "c665968c",
"metadata": {},
"source": [
"## <font color='blue'>*Hinweise*</font>\n",
"\n",
"Es soll eine Klasse und ein Hauptprogramm erstellt werden. In dem Hauptprogramm sollen Objekte der Klasse verwendet werden, um die beispielhafte Testaufgabe zu erfüllen. Natürlich kannst du dich hier auch frei austoben und mehrere Verkäufe machen, oder testen, ob die Klasse beim Verkaufen korrekt erkennt, wenn nicht genug auf Lager ist. Es darf keine negativen Mengen geben.\n",
"\n",
"Wir machen folgende Vorschläge für die Namen der Klasse, Attribute und Methoden:\n",
"Für die Syntax benötigst du Informationen aus dem Grunlagen OOP Notebook. Für diesen Abschnitt benötigst du Klassen und Objekte, Klassen- und Objektattribute, sowie Objektmethoden.\n"
"Nachdem du nun erste Erfahrungen mit der Objektorientierten Programmierung gemacht hast, wollen wir uns einem mathematischeren Thema widmen. Dabei wollen wir auch das Thema spezielle Methoden (auch magic method genannt) einführen. Diese erlauben es uns in dieser Aufgabe, Vektorrechnung mit den bekannten Operatoren wie mit \"normalen\" Variablen auch, durchzuführen."
]
},
{
"cell_type": "markdown",
"id": "6e25b06f",
"metadata": {},
"source": [
"## <font color='blue'>*Aufgabe*</font>\n",
"\n",
"Berechne den Betrag des Vektors c = a-b, mit den Vektoren a = (1,2,3) und b = (5,-4,3). Außerdem soll das Skalarprodukt a * b berechnet werden. Erstelle dazu eine Klasse ```Vektor3``` für die dreidimensionalen Vektoren so, dass die bereits gegebenen Codezeilen erfolgreich ausgeführt werden können."
]
},
{
"cell_type": "markdown",
"id": "e7333454-be6d-487d-a39e-ba509472aa30",
"metadata": {
"tags": []
},
"source": [
"## <font color='blue'>*Lösung*</font>"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "dc85e62c-a188-46ab-b7c5-55fc44d8bbbf",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Betrag des Vektors c: 7.211102550927978\n",
"Skalarprodukt a * b: 6\n"
]
}
],
"source": [
"class Vektor3:#Eine Klasse Vektor\n",
" def __init__(self, coords):\n",
" \n",
" if len(coords)!= 3:#Überprüft, ob der parameter coords 3 Werte enthält. Das kann eine Liste oder ein Tupel sein\n",
" raise ValueError('Invalid input.')#Falls nicht kann so ein Fehler ausgelöst werden. Ihr könnt es selbst ausprobieren, indem ihr versucht einen Vektor3 aus einer Liste mit mehr als 3 Einträgen zu erzeugen\n",
" \n",
" self.x = coords[0]#Setzt für x,y und z Objektvariablen\n",
" self.y = coords[1]\n",
" self.z = coords[2]\n",
" \n",
" def get_betrag(self):#Eine Methode, um die Länge des Vektors zu berechnen\n",
" def __len__(self):#Falls len() mit einem Vector3-Objekt aufgerufen wird, kommt das Ergebnis 3 heraus. Der Vec3 ist 3 Elemente lang\n",
" return 3\n",
" def __getitem__(self,index):#durch diese Funktion kann der Indexoperator verwendet werden, um Werte aus dem Vektor zu bekommen. Nicht ganz perfekt da jeder index größer 2 das z zurückliefert\n",
" if index==0:\n",
" return self.x\n",
" elif index==1:\n",
" return self.y\n",
" else:\n",
" return self.z\n",
" def __setitem__(self,index,value):#durch diese Methode kann man auch Elementen des Vektors mit dem Indexoperator Werte zuweisen\n",
" if index==0:\n",
" self.x=value\n",
" elif index==1:\n",
" self.y=value\n",
" else:\n",
" self.z=value\n",
"a = Vektor3([1,2,3])\n",
"b = Vektor3([5,-4,3])\n",
"\n",
"c = a-b\n",
"\n",
"print(\"Betrag des Vektors c: \" + str(c.get_betrag()))\n",
"print(\"Skalarprodukt a * b: \" + str(a*b))\n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "a2987eb5-b92f-47fd-b32e-eefeb02d09f7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n",
"4\n"
]
}
],
"source": [
"#Ein paar kleine Extras\n",
"print(a[0])#Durch das __getitem__ funktioniert der Zugriff auf die einzelnen Komponenten des Vektors\n",
"a[0]=4#In diesem Fall wird a.__setitem__(4) aufgerufen\n",
"Eine vollständige Liste sämtlicher magic-functions von Python Objekten findet ihr https://rszalski.github.io/magicmethods/#operators\n",
"Auch das in der Vorlesung angesprochene Problem, dass in Falle des Vektor3 ein Aufruf eines Operators nur funktioniert, wenn das Vector3-Objekt auf der linken Seite steht, also\n",
"```\n",
"a=Vector3([1,2,3])\n",
"b= a+4 #wird zu a.__add__(4)\n",
"b= 4+a #Funktioniert nicht, da die 4 keine __add__ Methode hat, bzw. diese nicht für Objekte der Klasse Vector3 gedacht ist\n",
"```\n",
"kann über magic-functions mit Einschränkungen gelöst werden. Die entsprechende Funktion würde ```__radd__``` heißen. Näheres findet ihr unter dem Link"
]
},
{
"cell_type": "markdown",
"id": "ac9ba52d",
"metadata": {},
"source": [
"## <font color='blue'>*Hinweise*</font>\n",
"\n",
"Nun benötigst du noch den Abschnitt über spezielle Methoden aus dem OOP-Grundlagen Notebook. Diese erlauben es, die Methoden so zu definieren, dass die Operationen wie a-b mit den selbst definierten Vektoren funktionieren.\n",
"\n",
"Es ist dir selbst überlassen, ob du die Einträge der Vektoren als eine Liste, oder als drei einzelne Werte speicherst.\n",
"\n",
"Das Ergebnis einer Summe oder Differenz ist ein neuer Vektor.\n"
]
},
{
"cell_type": "markdown",
"id": "595a83db-5a61-4048-84d3-e5f1ca14c263",
"metadata": {},
"source": [
"# <font color='blue'>**Aufgabe zum selbst probieren**</font>\n"
]
},
{
"cell_type": "markdown",
"id": "ca559a03-b79b-4f1a-a128-4e3e3e177017",
"metadata": {},
"source": [
"Erweitere die Vektorklasse um weitere Operationen, die dir sinnvoll erscheinen. Ebenso könnte es interessant sein, die Beschränkung auf 3 Dimensionen aufzuheben. Beachte dabei, dass die Operationen bestimmte Anforderungen haben. So funktioniert das Kreuzprodukt nur mit Vektoren mit drei Dimensionen, Addition und Substraktion nur für Vektoren gleicher Dimension etc. Die Funktionen müssen das dann überprüfen und ansonsten eine Warnung ausgeben, dass die Operation keine sinnvolle Lösung hat.\n",
## <font color='blue'>**Die Grundlagen von Objektorientierter Programmierung**</font>
In den vorherigen Übungen wurden die grundlegenden Elemente zur Ablaufsteuerung von Programmen behandelt. In dieser Übung führen wir ein wichtiges Konzept der Programmierung ein, die Objektorientierte Programmierung. Diese hilft sowohl bei der Strukturierung komplexer Projekte, der Verständlichkeit von Quellcode und dem gemeinsamen Arbeiten. Die Grundidee dahinter ist es, zusammengehörende Daten zusammen mit dazu passenden Operationen in (selbst) definierten Datentypen zusammenzufassen.
Für den ersten Kontakt mit Klassen und Objekten wollen wir mit einem einfachen Alltagsbeispiel beginnen.
Erstelle einen Datentyp, mit dem ein Supermarkt seine Artikel verwalten kann. Die Artikel sollen einen Namen, eine Menge und einen Preis pro Mengeneinheit haben. Dazu soll die Datenstruktur Methoden besitzen, mit denen der Supermarkt den Lagerbestand auffüllen kann, sowie eine bestimmte Menge eines Artikels (soweit vorhanden) verkaufen kann. Beim Verkaufen soll eine Ausgabe über die verkaufte Menge, den Preis und die verbleibende Menge gegeben werden. Außerdem soll ermittelbar sein, wieviele verschiedene Artikel es gibt.
Teste das Programm anhand eines Supermarktes der zu Beginn 500 kg Zucker zu 1 Euro pro kg, und 1000 kg Mehl zu 1,50 Euro pro kg auf Lager hat. Er füllt das Lager um 600 kg Zucker auf, und ein Kunde kauft 4 kg Mehl. Nutze die erstellte Klasse, um die neuen Lagerbestände zu ermitteln, sowie den vom Kunden gezahlten Preis.
classArtikel:#definiert eine Klasse namens artikel
anz_artikel=0#Eine Klassenvariable, die mitzählt, wie viele verschiedene Objekte der Klasse Artikel existieren
def__init__(self,name,menge,preis):#Der Konstruktor der Klasse. Wird immer aufgerufen, wenn ein Objekt der Klasse erzeugt wird
self.name=name#legt eine Objektvariable an, die den Namen enthalten soll, der dem Konstruktor übergeben wurde
ifmenge>0:#überprüft, ob die angegebene Startmenge größer 0 ist
self.menge=menge#wenn ja wird eine Objektvariable angelegt, die die Startmenge enthält
else:
self.menge=0#Falls die angegebene Menge nicht größer 0 war, wird die Startmenge auf 0 gesetzt. Es soll keine negative menge möglich sein
self.preis=preis#Der Preis des Artikels wird auch in objektvariablen gespeichert
Artikel.anz_artikel+=1#Die Anzahl der existierenden Artikel um eins erhöhen
defauffuellen(self,menge):#Eine Objektmethode, die die menge der Waren auffüllt
self.menge+=menge#addiert die aufzufüllende Menge auf die bestehende Menge des Artikels
defverkaufen(self,verkaufsmenge):#Eine Objektmethode zum Verkaufen
ifverkaufsmenge>self.menge:#Falls mehr verkauft werden soll als überhaupt vorhanden ist
verkaufsmenge=self.menge#die Verkaufsmenge auf die gerade verfügbare Menge reduzieren
self.menge-=verkaufsmenge#die Verkaufsmenge von der Vorratsmenge des Artikels abziehen
print(f"{verkaufsmenge} kg {self.name} fuer {self.preis*verkaufsmenge} Euro verkauft. Es verbleiben {self.menge} kg auf Lager.")#Ausgabe was verkauft wurde und wie viel es gekostet hat. Diese Schreibweise für Zeichenketten ist sehr praktisch, wenn man viele Zahlen einbinden möchte
def__del__(self):#der Destruktor. Wird aufgerufen, wenn ein Objekt gelöscht wird
Artikel.anz_artikel-=1#Die Gesamtzahl der vorhandenen Artikel muss um eins heruntergezählt werden, da ein Artikel gerade gelöscht wird
zucker=Artikel("Zucker",500.,1.)#Erzeugt zucker. Ein Objekt der Klasse Artikel. Die Parameter sind nötig, da __init__ von Artikel 3 Parameter benötigt (+ das self, das bei Objektmethodenaufrufen implizit der Parameterliste hinzugefügt wird)
mehl=Artikel("Mehl",1000.,1.5)
```
%% Cell type:markdown id:d046ee6a tags:
Auffüll- und Verkaufsoperationen, sowie Testausgaben (beliebig oft wiederholbar):
%% Cell type:code id:7b68c5f0 tags:
``` python
zucker.auffuellen(600)#Ruft eine Objektmethode auf. Der Parameter self der Funktion auffuellen wird hierbei mit dem Objekt zucker initialisiert, da zucker vor dem Punkt steht
mehl.verkaufen(4)#Das gleiche fügr Mehl mit der verkaufen-Methode
print(f"Bestand Zucker: {zucker.menge} kg")#Auf Objektvariablen kann mit dem punkt zugegriffen werden. zucker.menge ist die Variable menge des Objetkes zucker
print(f"Bestand Mehl: {mehl.menge} kg")
```
%% Output
4 kg Mehl fuer 6.0 Euro verkauft. Es verbleiben 996.0 kg auf Lager.
#Ein kurzes Beispiel um dir noch einmal die Funktion von Python Variablen zu verdeutlichen
print("Anzahl der definierten Artikel",Artikel.anz_artikel)# Es gibt 2 Artikel, mehl und zucker.
mehl2=mehl#Erzeugt keinen neuen Artikel mehl2 und mehl verweisen auf das selbe Objekt der Klasse Artikel
print("Anzahl der definierten Artikel",Artikel.anz_artikel)# Es gibt immer noch 2 Artikel. mehl2 und mehl sind der identische Artikel
mehl=2#Belegt die Variable mehl mit einem neun Wert bzw. Objekt. Einer 2
print("Anzahl der definierten Artikel",Artikel.anz_artikel)# Es gibt immer noch 2 Artikel. mehl2 und zucker. Da das "Mehl"-Objekt mit mehl2 einen zweiten Namen hat wurde es in der letzten Zeile nicht gelöscht
mehl2=4#Die Variable mehl2 wird jetzt auch neu belegt. Für das "Mehl"-Objekt gibt es jetzt keinen Variablennamen mehr. Es wird dadurch gelöscht
print("Anzahl der definierten Artikel",Artikel.anz_artikel)# Es gibt nur noch einen Artikel. Den Zucker
```
%% Output
Anzahl der definierten Artikel 2
Anzahl der definierten Artikel 2
Anzahl der definierten Artikel 2
Anzahl der definierten Artikel 1
%% Cell type:markdown id:c665968c tags:
## <font color='blue'>*Hinweise*</font>
Es soll eine Klasse und ein Hauptprogramm erstellt werden. In dem Hauptprogramm sollen Objekte der Klasse verwendet werden, um die beispielhafte Testaufgabe zu erfüllen. Natürlich kannst du dich hier auch frei austoben und mehrere Verkäufe machen, oder testen, ob die Klasse beim Verkaufen korrekt erkennt, wenn nicht genug auf Lager ist. Es darf keine negativen Mengen geben.
Wir machen folgende Vorschläge für die Namen der Klasse, Attribute und Methoden:
Für die Syntax benötigst du Informationen aus dem Grunlagen OOP Notebook. Für diesen Abschnitt benötigst du Klassen und Objekte, Klassen- und Objektattribute, sowie Objektmethoden.
Nachdem du nun erste Erfahrungen mit der Objektorientierten Programmierung gemacht hast, wollen wir uns einem mathematischeren Thema widmen. Dabei wollen wir auch das Thema spezielle Methoden (auch magic method genannt) einführen. Diese erlauben es uns in dieser Aufgabe, Vektorrechnung mit den bekannten Operatoren wie mit "normalen" Variablen auch, durchzuführen.
%% Cell type:markdown id:6e25b06f tags:
## <font color='blue'>*Aufgabe*</font>
Berechne den Betrag des Vektors c = a-b, mit den Vektoren a = (1,2,3) und b = (5,-4,3). Außerdem soll das Skalarprodukt a * b berechnet werden. Erstelle dazu eine Klasse ```Vektor3``` für die dreidimensionalen Vektoren so, dass die bereits gegebenen Codezeilen erfolgreich ausgeführt werden können.
iflen(coords)!=3:#Überprüft, ob der parameter coords 3 Werte enthält. Das kann eine Liste oder ein Tupel sein
raiseValueError('Invalid input.')#Falls nicht kann so ein Fehler ausgelöst werden. Ihr könnt es selbst ausprobieren, indem ihr versucht einen Vektor3 aus einer Liste mit mehr als 3 Einträgen zu erzeugen
self.x=coords[0]#Setzt für x,y und z Objektvariablen
self.y=coords[1]
self.z=coords[2]
defget_betrag(self):#Eine Methode, um die Länge des Vektors zu berechnen
return (self.x**2+self.y**2+self.z**2)**0.5
def__mul__(self,vec2):#Eine magic method, die Aufgerufen wird, wenn ein Vec3 auf der linken Seite des * zeichens steht
def__len__(self):#Falls len() mit einem Vector3-Objekt aufgerufen wird, kommt das Ergebnis 3 heraus. Der Vec3 ist 3 Elemente lang
return3
def__getitem__(self,index):#durch diese Funktion kann der Indexoperator verwendet werden, um Werte aus dem Vektor zu bekommen. Nicht ganz perfekt da jeder index größer 2 das z zurückliefert
ifindex==0:
returnself.x
elifindex==1:
returnself.y
else:
returnself.z
def__setitem__(self,index,value):#durch diese Methode kann man auch Elementen des Vektors mit dem Indexoperator Werte zuweisen
ifindex==0:
self.x=value
elifindex==1:
self.y=value
else:
self.z=value
a=Vektor3([1,2,3])
b=Vektor3([5,-4,3])
c=a-b
print("Betrag des Vektors c: "+str(c.get_betrag()))
Eine vollständige Liste sämtlicher magic-functions von Python Objekten findet ihr https://rszalski.github.io/magicmethods/#operators
Auch das in der Vorlesung angesprochene Problem, dass in Falle des Vektor3 ein Aufruf eines Operators nur funktioniert, wenn das Vector3-Objekt auf der linken Seite steht, also
```
a=Vector3([1,2,3])
b= a+4 #wird zu a.__add__(4)
b= 4+a #Funktioniert nicht, da die 4 keine __add__ Methode hat, bzw. diese nicht für Objekte der Klasse Vector3 gedacht ist
```
kann über magic-functions mit Einschränkungen gelöst werden. Die entsprechende Funktion würde ```__radd__``` heißen. Näheres findet ihr unter dem Link
%% Cell type:markdown id:ac9ba52d tags:
## <font color='blue'>*Hinweise*</font>
Nun benötigst du noch den Abschnitt über spezielle Methoden aus dem OOP-Grundlagen Notebook. Diese erlauben es, die Methoden so zu definieren, dass die Operationen wie a-b mit den selbst definierten Vektoren funktionieren.
Es ist dir selbst überlassen, ob du die Einträge der Vektoren als eine Liste, oder als drei einzelne Werte speicherst.
Das Ergebnis einer Summe oder Differenz ist ein neuer Vektor.
Erweitere die Vektorklasse um weitere Operationen, die dir sinnvoll erscheinen. Ebenso könnte es interessant sein, die Beschränkung auf 3 Dimensionen aufzuheben. Beachte dabei, dass die Operationen bestimmte Anforderungen haben. So funktioniert das Kreuzprodukt nur mit Vektoren mit drei Dimensionen, Addition und Substraktion nur für Vektoren gleicher Dimension etc. Die Funktionen müssen das dann überprüfen und ansonsten eine Warnung ausgeben, dass die Operation keine sinnvolle Lösung hat.