diff --git a/Tutorium/tut09/README.md b/Tutorium/tut09/README.md index e69de29..589718e 100644 --- a/Tutorium/tut09/README.md +++ b/Tutorium/tut09/README.md @@ -0,0 +1,514 @@ +--- +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 \ No newline at end of file diff --git a/Tutorium/tut09/img/pointdistribution_ex07.png b/Tutorium/tut09/img/pointdistribution_ex07.png new file mode 100644 index 0000000..732c3e7 Binary files /dev/null and b/Tutorium/tut09/img/pointdistribution_ex07.png differ diff --git a/Tutorium/tut09/img/pointdistribution_ex08.png b/Tutorium/tut09/img/pointdistribution_ex08.png new file mode 100644 index 0000000..1922770 Binary files /dev/null and b/Tutorium/tut09/img/pointdistribution_ex08.png differ diff --git a/Tutorium/tut09/src/classes.py b/Tutorium/tut09/src/classes.py new file mode 100644 index 0000000..b604a35 --- /dev/null +++ b/Tutorium/tut09/src/classes.py @@ -0,0 +1,31 @@ +from dataclasses import dataclass + +@dataclass +class A: + x: int + y: int = 0 + + # die init die von `dataclass` generiert wird, also unnötig + # siehe class B + def __init__(self, x: int, y: int = 0): + self.x = x + self.y = y + +@dataclass +class B: + x: int + y: int = 0 + +if __name__ == '__main__': + my_a = A(1) + assert my_a.x == 1 + assert my_a.y == 0 + my_other_a = A(1, y = 1) + assert my_other_a.x == 1 + assert my_other_a.y == 1 + my_b = B(1) + assert my_b.x == 1 + assert my_b.y == 0 + my_other_b = B(1, y = 1) + assert my_other_b.x == 1 + assert my_other_b.y == 1 \ No newline at end of file diff --git a/Tutorium/tut09/src/my_collections.py b/Tutorium/tut09/src/my_collections.py new file mode 100644 index 0000000..a8986c0 --- /dev/null +++ b/Tutorium/tut09/src/my_collections.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass, InitVar + + +@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 + +@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 + +if __name__ == "__main__": + xs: MyList[int] = MyList() + xs.add(100) + assert xs.length == 1 + position = (0, 0) + my_obj = GameObject(position) + assert my_obj.position == (0, 0) + try: + GameObject((0, -1)) + except AssertionError: + pass + else: + raise AssertionError(f"{my_obj} should have thrown a assertation error") + my_obj.position = (-1, 0) + assert my_obj.position == (0, 0) + \ No newline at end of file