Files
eidp-2024/Tutorium/tut09/README.md
Nils Pukropp b907cb789e init
2024-09-16 21:02:27 +02:00

515 lines
9.3 KiB
Markdown

---
marp: true
paginate: true
class: invert
# theme: uncover
footer: Tutorium 09 - 16.12.2023 - Nils Pukropp - https://s.narl.io/s/tutorium-09
header:
---
# Tutorium 09
Korrektur 08 - Vererbung, OOP, Datenkapselung
---
# Korrektur 08
---
# Punkteverteilung
![](img/pointdistribution_ex08.png)
---
# Häufige Fehler
* `@dataclass` nicht verwendet
* `__init__` überschrieben, obwohl `@dataclass` das macht und dann `super().__init__()` vergessen
* Kein Polymorphismus verwendet, also Code Duplikate oder auf `self` gematched/`isinstance()` verwendet
* Code nicht getestet, Datei nicht ausführbar => **0 Punkte**!
---
# Musterlösung - Aufgabe 8 a,b)
```py
import math
from dataclasses import dataclass
@dataclass
class Vec2:
x: float
y: float
def abs(self) -> float:
return math.sqrt(self.x * self.x + self.y * self.y)
```
---
# Musterlösung - Aufgabe 8 c)
```py
@dataclass
class GameObject:
position: Vec2
radius: int
alive: bool
color: tuple[int, int, int]
@dataclass
class Projectile(GameObject):
speed: float
@dataclass
class StaticObject(GameObject):
rotation: float
```
---
# Musterlösung - Aufgabe 8 c)
```py
class Item(StaticObject):
amount: int
class Ammunition(Item):
pass
class Health(Item):
pass
class Ship(GameObject):
shots: int
hp: int
class Asteroid(StaticObject):
special: bool
```
---
# Musterlösung - Aufgabe 8 d)
```python
class GameObject:
# ...
def update(self, width: int, height: int, delta: float):
if not (0 <= self.position.x < width and 0 <= self.position.y < height):
self.alive = False
class Projectile(GameObject):
speed: float
def update(self, width: int, height: int, delta: float):
self.position.y -= delta * self.speed
super().update(width, height, delta)
```
---
# Musterlösung - Aufgabe 8 d)
```python
class StaticObject(GameObject):
rotation: float
def update(self, width: int, height: int, delta: float):
self.position.y += delta
self.rotation += delta / self.radius
super().update(width, height, delta)
class Ship(GameObject):
shots: int
hp: int
def update(self, width: int, height: int, delta: float):
if self.hp <= 0:
self.hp = 0
self.alive = False
super().update(width, height, delta)
```
---
# Musterlösung - Aufgabe 8 e)
```python
@dataclass
class GameObject:
# ...
def is_colliding(self, other: "GameObject") -> bool:
dist = Vec2(self.position.x - other.position.x, self.position.y - other.position.y)
return dist.abs() <= self.radius + other.radius
```
---
# Musterlösung - Aufgabe 8 f)
```python
class GameObject:
# ...
def on_collision(self, other: "GameObject"):
pass
class Projectile(GameObject):
# ...
def on_collision(self, other: 'GameObject'):
if not isinstance(other, Ship):
self.alive = False
```
---
# Musterlösung - Aufgabe 8 f)
```python
class StaticObject(GameObject):
# ...
def on_collision(self, other: 'GameObject'):
self.alive = False
class Ship(GameObject):
# ...
def on_collision(self, other: 'GameObject'):
match other:
case Asteroid():
self.hp -= other.radius
case Health():
self.hp += other.amount
case Ammunition():
self.shots += other.amount
```
---
# Musterlösung - Aufgabe 8 f)
```python
@dataclass
class Asteroid(StaticObject):
special: bool
def on_collision(self, other: 'GameObject'):
if not isinstance(other, Asteroid):
self.alive = False
```
---
# Musterlösung - Aufgabe 8 g)
```python
@dataclass
class Ship(GameObject):
# ...
def shoot(self) -> Projectile:
alive = False
if self.shots:
self.shots -= 1
alive = True
pos = Vec2(self.position.x, self.position.y)
return Projectile(pos, 5, alive, (255, 0, 0), 3)
```
---
# Musterlösung - Aufgabe 8 h)
```python
@dataclass
class GameObject:
# ...
def draw(self, screen: pygame.Surface):
pygame.draw.circle(screen, self.color, (self.position.x, self.position.y), self.radius)
```
---
# Override-Dekorator
- ist in `typing`
- Wird über Methoden geschrieben, die überschrieben werden
- Pylance zeigt einen Fehler an, wenn die überschriebene Methode in keiner Oberklasse gefunden wird
- Hilft Fehler vorzubeugen - falsche Signatur, Parameter, ...
---
# Override-Dekorator - Beispiel
```python
from typing import override
from dataclasses import dataclass
@dataclass
class GameObject:
def on_collision(self, other: 'GameObject'):
pass
class StaticObject(GameObject):
@override
def on_collisoin(self, other: 'GameObject'): # Pylance-Error
self.alive = False
```
---
# Override-Dekorator - Beispiel
```python
from typing import override
from dataclasses import dataclass
@dataclass
class GameObject:
def on_collision(self, other: 'GameObject'):
pass
class StaticObject(GameObject):
@override
def on_collision(self): # Pylance-Error
self.alive = False
```
---
# Datenkapselung
- Man möchte manche Implementierung verstecken
- Wenn andere deinen Code verwenden, dann möchte man eine Schnittstelle anbieten die intuitiv ist.
```python
@dataclass
class MyList[T]:
internal_list: list[T] = []
def add(self, item: T) -> None:
self.internal_list += [other]
```
---
# Datenkapselung - warum ist das schlecht?
```python
from my_collections import MyList
xs = MyList()
xs.internal_list # ????
```
- was sollen wir mit `internal_list`?
- andere sollten nur auf `add()` zugreifen können
---
# Private Attribute
```python
@dataclass
class MyList[T]:
_internal_list: InitVar[list[T]]
_length: InitVar[int]
def __init__(self):
self.__internal_list = []
self.__length = 0
def add(self, item: T):
self.__internal_list += [item]
self.__length += 1
@property
def length(self) -> int:
return self.__length
```
---
# Private Attribute
```python
@dataclass
class MyList[T]:
_internal_list: InitVar[list[T]]
_length: InitVar[int]
def __init__(self):
self.__internal_list = []
self.__length = 0
```
---
# Private Attribute
```python
@dataclass
class MyList[T]:
_internal_list: InitVar[list[T]]
_length: InitVar[int]
def __init__(self):
self.__internal_list = []
self.__length = 0
def add(self, item: T):
self.__internal_list += [item]
self.__length += 1
@property
def length(self) -> int:
return self.__length
```
---
# Private Attribute - Setter
- Manchmal wollen wir trotzdem private Attribute setzen
- Aber vielleicht nur wenn bestimmte Bedingungen erfüllt sind
```python
class GameObject:
_position: InitVar[tuple[int, int]]
def __post__init__(self, position: tuple[int, int]):
assert (0, 0) <= position
self.__position = position
@property
def position(self) -> tuple[int, int]:
return self.__position
```
---
# Private Attribute - Setter
```python
@dataclass
class GameObject:
_position: InitVar[tuple[int, int]]
def __post_init__(self, position: tuple[int, int]):
assert (0, 0) > position
self.__position = position
@property
def position(self) -> tuple[int, int]:
return self.__position
@position.setter
def position(self, position: tuple[int, int]):
if (0, 0) > position:
return
self.__position = position
```
---
# Comprehensive-Guide to `class`
## `@dataclass`
- Attribute werden im Klassenrumpf definiert
- können mit einem Standardwert definiert werden
- `__init__`, `__post_init__`, `__repr__`, `__eq__`, `__lt__`, `__le__`, `__gt__`, `__ge__`, ... werden automatisch generiert
- In der Vorlesung benutzen wir nur `dataclass`
---
# Comprehensive-Guide to `class`
## `@dataclass`
- Attribute die im Klassenrumpf definiert werden, werden automatisch in die `__init__` generiert, auch wenn es einen Standardwert gibt!
```python
@dataclass
class A:
x: int
y: int = 0
def __init__(self, x: int, y: int = 0): # das macht @dataclass von selber!
self.x = x
self.y = y
```
---
# Comprehensive-Guide to `class`
## `Enum`
- Wenn man eine endliche Aufzählung braucht (endliche Menge)
- macht die Fallunterscheidung einfach weil es endliche Elemente gibt
- Versichert auch dass kein quatsch übergeben wird wie zb bei `str`
- **niemals** mit `@dataclass`, sonst geht alles kaputt
---
# Enum - Beispiel
```python
def eval[T: (int | float)](operator: str, x: T, y: T) -> T:
match operator:
case '+':
return x + y
case '-':
return x - y
case '*':
return x * y
case '/' if y != 0:
return x / y
case _:
return 0
```
---
# Enum - Beispiel
```python
from enum import Enum, auto
class Op(Enum):
ADD = auto()
SUB = auto()
DIV = auto()
MUL = auto()
```
Jetzt passen wir die Methodensignatur an
```python
def eval[T: (int | float)](operator: Op, x: T, y: T) -> T:
```
Jetzt kann nichts beliebiges als `operator` übergeben werden
---
# Blatt 09 - Fragen?
- Abgabe: 18.12. - 09:00
- Testet euren Code!
- Es gibt keine dummen Fragen wenns ums Verständnis geht