Compare commits
13 Commits
60d3bab464
...
main
Author | SHA1 | Date | |
---|---|---|---|
cda9457c27 | |||
8536a1ec88 | |||
c01f023948 | |||
9484d5cb1b | |||
25753d0181 | |||
04ae6c63d9 | |||
78447f44fc | |||
8ddae35266 | |||
89ad048474 | |||
5e531ce0c6 | |||
12feb4c1c5 | |||
c7d9b69c6d | |||
7ab54d324d |
140
Notes/Unterschiede.md
Normal file
140
Notes/Unterschiede.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Unterschiede zu Vorjahren
|
||||
|
||||
## Typalias
|
||||
|
||||
Manchmal braucht man eine Kombination aus Datentypen
|
||||
|
||||
### Vorher
|
||||
|
||||
Erstellen von Typaliasen mit `Union`
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
|
||||
# `RealNumber` ist also eine `int` oder eine `float`
|
||||
RealNumber = Union[int, float]
|
||||
|
||||
# also geht hier
|
||||
# `add(1, 1) -> 2`
|
||||
# `add(1.0, 1.0) -> 2.0`
|
||||
# aber auch
|
||||
# `add(1, 1.0) -> 2.0`
|
||||
def add(x: RealNumber, y: RealNumber) -> RealNumber:
|
||||
return x + y
|
||||
```
|
||||
|
||||
### In Python3.12
|
||||
|
||||
Wir haben jetzt das `type`-Keyword für das wir nichts importieren müssen und können einfach Datentypen mit `|` *odern*
|
||||
|
||||
```python
|
||||
# `type` statt `Union`
|
||||
type NewRealNumber = int | float
|
||||
|
||||
# sonst sieht alles gleich aus
|
||||
def new_add(x: NewRealNumber, y: NewRealNumber) -> NewRealNumber:
|
||||
return x + y
|
||||
```
|
||||
|
||||
## Generiche Typ-Parameter (Generics, Typvariabeln, ...)
|
||||
|
||||
### Vorher
|
||||
|
||||
Erstellen von generischen Typvariabeln/Typ-Parametern mit `TypeVar` aus `typing`:
|
||||
|
||||
```python
|
||||
from typing import Generic, TypeVar, Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# T ist unser Platzhalten für einen beliebigen Datentyp
|
||||
T = TypeVar('T')
|
||||
|
||||
# jetzt können wir `T` einfach als Platzhalter für einen beliebigen Datentyp verwenden
|
||||
# praktisch weil wir so wissen dass `x`, `y` und der `return` zumindest den **selben** Datentyp verwenden
|
||||
# aber welcher genau wissen wir nicht bis jemand die Funktion mit z.b. `int` oder `float`!
|
||||
# Zum Beispiel bedeutet dass
|
||||
# `add(1, 2) == 3` => `T == int`
|
||||
# `add(1.0, 2.0) == 3.0` => `T == float`
|
||||
# ...
|
||||
def add(x: T, y: T) -> T:
|
||||
return x + y
|
||||
|
||||
# Ebenso können wir `T` auch für Klassen verwenden
|
||||
@dataclass
|
||||
# wir müssen hier festlegen dass T in der Klasse verwendet wird
|
||||
# indem `MyList` von `Generic[UnsereGenerischeVariabel]` erbt
|
||||
class MyList(Generic[T]):
|
||||
|
||||
def __post_init__(self, ):
|
||||
# wir haben intern eine `internal_list` die von außen nicht sichtbar ist
|
||||
self.__internal_list: list[T] = field(default_factory=list)
|
||||
# hier speichern wir uns die aktuelle Länge unserer Liste
|
||||
self.__length: int = 0
|
||||
|
||||
def append(self, value: T):
|
||||
"""fügt ein element mit dem Typ `T` hinzu"""
|
||||
self.__internal_list += [value]
|
||||
self.__length += 1
|
||||
|
||||
def remove(self, value: T):
|
||||
"""entfert das element `value`, wenn es in unserer Liste ist"""
|
||||
if value in self.__internal_list:
|
||||
self.__internal_list.remove(value)
|
||||
|
||||
def get(self, i: int) -> Optional[T]:
|
||||
"""gibt das element an `i` Stelle zurück. Wenn Liste kleiner als `i` ist, dann `None`"""
|
||||
if i < len(self):
|
||||
return self[i]
|
||||
return None
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.__length
|
||||
|
||||
def __getitem__(self, i: int) -> T:
|
||||
return self.__internal_list[i]
|
||||
```
|
||||
|
||||
### In Python3.12
|
||||
|
||||
Deklaration der Typvariabel direkt im Funktionskopf/Klassenkopf mit `[]`.
|
||||
Sprich wir brauchen `TypeVar` nicht mehr
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
# hier wird `V` durch `[V]` deklariert und kann in der Funktion benutzt werden
|
||||
def new_add[V](x: V, y: V) -> V:
|
||||
return x + y
|
||||
|
||||
|
||||
# hier wird `T` für die Klasse `NewMyList` deklariert durch `[T]` und kann überall in der Klasse genutzt werden
|
||||
# der Rest ist genau gleich, das einzige was sich eben geändert hat ist dass man dieses `TypeVar` nicht mehr braucht
|
||||
# sondern direkt in der Deklaration von Klassen/Funktionen/Methoden die generischen Typvariabeln mit deklariert
|
||||
@dataclass
|
||||
class NewMyList[T]:
|
||||
|
||||
def __post_init__(self, ):
|
||||
self.__internal_list: list[T] = field(default_factory=list)
|
||||
self.__length: int = 0
|
||||
|
||||
def append(self, value: T):
|
||||
self.__internal_list += [value]
|
||||
self.__length += 1
|
||||
|
||||
def remove(self, value: T):
|
||||
if value in self.__internal_list:
|
||||
self.__internal_list.remove(value)
|
||||
|
||||
def get(self, i: int) -> Optional[T]:
|
||||
if i < len(self):
|
||||
return self[i]
|
||||
return None
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.__length
|
||||
|
||||
def __getitem__(self, i: int) -> T:
|
||||
return self.__internal_list[i]
|
||||
```
|
68
Notes/src/unterschiede.py
Normal file
68
Notes/src/unterschiede.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from typing import Generic, TypeVar, Optional, Union
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
@dataclass
|
||||
class MyList(Generic[T]):
|
||||
|
||||
def __post_init__(self, ):
|
||||
self.__internal_list: list[T] = field(default_factory=list)
|
||||
self.__length: int = 0
|
||||
|
||||
def append(self, value: T):
|
||||
self.__internal_list += [value]
|
||||
self.__length += 1
|
||||
|
||||
def remove(self, value: T):
|
||||
if value in self.__internal_list:
|
||||
self.__internal_list.remove(value)
|
||||
|
||||
def get(self, i: int) -> Optional[T]:
|
||||
if i < len(self):
|
||||
return self[i]
|
||||
return None
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.__length
|
||||
|
||||
def __getitem__(self, i: int) -> T:
|
||||
return self.__internal_list[i]
|
||||
|
||||
@dataclass
|
||||
class MyListNew[T]:
|
||||
|
||||
def __post_init__(self, ):
|
||||
self.__internal_list: list[T] = field(default_factory=list)
|
||||
self.__length: int = 0
|
||||
|
||||
def append(self, value: T):
|
||||
self.__internal_list += [value]
|
||||
self.__length += 1
|
||||
|
||||
def remove(self, value: T):
|
||||
if value in self.__internal_list:
|
||||
self.__internal_list.remove(value)
|
||||
|
||||
def get(self, i: int) -> Optional[T]:
|
||||
if i < len(self):
|
||||
return self[i]
|
||||
return None
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.__length
|
||||
|
||||
def __getitem__(self, i: int) -> T:
|
||||
return self.__internal_list[i]
|
||||
|
||||
|
||||
RealNumber = Union[int, float]
|
||||
|
||||
def add(x: RealNumber, y: RealNumber) -> RealNumber:
|
||||
return x + y
|
||||
|
||||
type NewRealNumber = int | float
|
||||
|
||||
# sonst sieht alles gleich aus
|
||||
def new_add(x: NewRealNumber, y: NewRealNumber) -> NewRealNumber:
|
||||
return x + y
|
@@ -1,8 +1,8 @@
|
||||
# EidP-2024
|
||||
# EidP-2023
|
||||
|
||||
Repository zum Tutorium für EidP für das Wintersemester 2024
|
||||
|
||||
## Kontakt
|
||||
|
||||
* [Mail](mailto:nils@narl.io)
|
||||
* [Discord](https://discord.gg/amykAk3EvC)
|
||||
* [Discord](https://discord.gg/amykAk3EvC)
|
||||
|
@@ -0,0 +1,424 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
# class: invert
|
||||
theme: rose-pine
|
||||
footer: Tutorium 13 - 26.01.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-13
|
||||
header:
|
||||
math: mathjax
|
||||
---
|
||||
|
||||
# Tutorium 13 - 26.01.2024
|
||||
|
||||
Orga - Wiederholung Types - Functions! - Decorator
|
||||
|
||||
---
|
||||
|
||||
# Orga
|
||||
|
||||
---
|
||||
## Wegen fehlendem Tutorium am 19.01.
|
||||
|
||||
- Jeder kriegt die 6 Punkte für Anwesenheit
|
||||
- Auf Blatt 13 als Extrapunkte unter *Anmerkungen*
|
||||
- Sorry fürs nicht beantworten von manchen Nachrichten
|
||||
- Falls ihr glaubt ihr bekommt knapp nicht genug Punkte schreibt mich einfach an, man wird schon noch irgendwo Punkte finden
|
||||
|
||||
---
|
||||
|
||||
## Syntax-Fehler
|
||||
|
||||
- Für **Syntax-Fehler** habe ich im allgemeinen **0 Punkte** in der jeweiligen Datei vergeben
|
||||
- Das euer Programm ausführbar ist sollte das mindeste sein!
|
||||
- Ihr sollt euer Programm sowieso selbständig testen und ich geh mal davon aus das ist nicht passiert wenn sich die Datei nichtmal ausführen lässt
|
||||
- Zeitdruck kann ich voll nachvollziehen
|
||||
|
||||
---
|
||||
|
||||
## Nachträgliches ausbessern
|
||||
|
||||
- Ihr verbessert euren SyntaxFehler (eure Python-Datei ist ausführbar)
|
||||
- Ihr schickt mir eine `.zip` oder eine `.tar.gz` mit dem verbesserten Code an [nils@narl.io](mailto:nils@narl.io)
|
||||
- verbessert nichts anderes!
|
||||
- Schreibt kurz in die Mail welches Blatt + Aufgabe + Kürzel
|
||||
- Ich korrigiere eure Abgabe nachträglich und ihr bekommt zumindest mehr als 0 Punkte
|
||||
- Bitte nur wenn ihr wirklich die Punkte braucht und habt etwas Geduld mit der Korrektur
|
||||
|
||||
---
|
||||
|
||||
## Allgemeines
|
||||
|
||||
- biete euch Übungen passend zur Klausur
|
||||
- kein genaues Datum, aber vor dem 09.02
|
||||
- Klausur ist *wahrscheinlich* am 19.02.
|
||||
- Short-Link zu der Übung [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
|
||||
- aktuell noch nicht online
|
||||
|
||||
---
|
||||
|
||||
# Link: [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
|
||||
|
||||
---
|
||||
|
||||
# Type annotations
|
||||
(Wiederholung)
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Was ist das?
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Was ist das?
|
||||
|
||||
* Jedes **Objekt** lässt sich mindestens einem **Typ** zuordnen
|
||||
* Objekte im mathematischen Sinne wie z.B. Variablen, Funktionen, ...
|
||||
* Dieser **schränkt** den Wertebereich ein
|
||||
* z.B. ist eine Variable `x` von Typ `int` eine Ganzzahl
|
||||
* ähnlich zur mathematischen Schreibweise $x \in \mathbb{Z}$
|
||||
* In der Informatik nennt man das **Typisierung**
|
||||
* Es gibt verschiedene Arten der Typisierung
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Typisierung
|
||||
|
||||
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
|
||||
- also erst wenn das Programm *läuft*
|
||||
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
|
||||
- also während wir den Quellcode übersetzen
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Typisierung
|
||||
|
||||
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
|
||||
- also erst wenn das Programm *läuft*
|
||||
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
|
||||
- also während wir den Quellcode übersetzen
|
||||
|
||||
### Was ist nun Python?
|
||||
|
||||
---
|
||||
|
||||
### Was ist nun Python?
|
||||
|
||||
- **dynamisch typisiert**
|
||||
- wir müssen unsere `.py` Datei ausführen bevor wir wissen ob alles korrekt ist
|
||||
- **Pylance** ist ein eigenes Programm
|
||||
- es soll beim Schreiben bereits **Typverletzungen** erkennen
|
||||
- **unvollständige** Typüberprüfung, es soll nur den Entwickler unterstützen
|
||||
|
||||
---
|
||||
|
||||
## Variabeln Typannotieren
|
||||
|
||||
* `variable_name: <Type> = ...`
|
||||
* Beispiele:
|
||||
```python
|
||||
x: int = 3
|
||||
y: int = 5
|
||||
string: str = "Hello World!"
|
||||
|
||||
# aber auch eigene Objekte (OOP)
|
||||
point: Point = Point(3, 1)
|
||||
```
|
||||
* diese Annotation ist für uns **optional**
|
||||
|
||||
---
|
||||
|
||||
## Funktionen Typannotieren
|
||||
|
||||
* `def func_name(param1: <Type>, param2: <Type>, ...) -> <Type>`
|
||||
* Beispiele:
|
||||
```python
|
||||
def add(x: int, y: int) -> int:
|
||||
return x + y
|
||||
|
||||
def div(x: float, y: float) -> Optional[float]:
|
||||
if y == 0.0:
|
||||
return None
|
||||
return x / y
|
||||
```
|
||||
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
|
||||
|
||||
---
|
||||
|
||||
## Klassen Typannotieren
|
||||
|
||||
*
|
||||
```
|
||||
class ClassName:
|
||||
attribute_name1: <Type>
|
||||
attribute_name2: <Type>
|
||||
...
|
||||
```
|
||||
* Beispiel:
|
||||
```python
|
||||
@dataclass
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
```
|
||||
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
|
||||
|
||||
---
|
||||
|
||||
## Methoden Typannotieren
|
||||
|
||||
* `def method_name(self, param1: <Type>, ...) -> <Type>`
|
||||
* Beispiel:
|
||||
```python
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def distance_from(self, other: 'Point') -> float:
|
||||
return math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
|
||||
```
|
||||
* `self` muss **nicht** Typannotiert werden, kann aber
|
||||
* `other` hingegen schon, wegen Python muss in der Klasse mit `'` annotiert werden
|
||||
* diese Annotation ist **verpflichtend**
|
||||
|
||||
---
|
||||
|
||||
## Datentypen von Datentypen
|
||||
|
||||
* Manche Datentypen bauen sich aus anderen Datentypen auf
|
||||
* z.B. `list` ist eine Liste von Elementen mit einem Typ
|
||||
* hierfür verwenden wir `[]` um den Datentyp in `list` zu annotieren
|
||||
```python
|
||||
def sum(xs: list[int]) -> int:
|
||||
total: int = 0
|
||||
for x in xs:
|
||||
total += x
|
||||
return total
|
||||
```
|
||||
* hierbei ist es wichtig so genau wie möglich zu annotieren!
|
||||
* diese Annotation ist **verpflichtend**
|
||||
|
||||
---
|
||||
|
||||
## Häufige Fehler mit verschachtelten Typen
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - `tuple[...]`
|
||||
|
||||
* Tuple haben eine feste größe
|
||||
* Tuple sind endlich
|
||||
* Tuple können Elemente mit unterschiedlichen Typen haben
|
||||
* Die Datentypen der Elemente werden mit einem `,` in `[]` getrennt
|
||||
* Beispiel:
|
||||
```python
|
||||
tup: tuple[int, int, float, str] = (1, 2, 3.0, "hello world")
|
||||
```
|
||||
* Diese Annotation ist **verpflichtend**
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - `dict[...]`
|
||||
|
||||
* Dictionary haben genau zwei zu definierende Typen
|
||||
* **Key**
|
||||
* **Value**
|
||||
* Beispiel:
|
||||
```python
|
||||
number_dictionary: dict[int, str] = {
|
||||
0: "zero",
|
||||
1: "one",
|
||||
2: "two",
|
||||
}
|
||||
```
|
||||
* Diese Annotation ist **verpflichtend**
|
||||
* Diese kann weiter geschachtelt werden durch z.B. `list` als `Value`:
|
||||
* `dict[int, list[str]]`
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - Typvariabeln (generische Typen)
|
||||
|
||||
* manchmal wollen wir nicht genau wissen welchen Datentypen wir haben
|
||||
* dieser wird dann implizit von Python erkannt
|
||||
* wir stellen damit sicher dass eine Typvariable **beliebig** aber **fest** ist
|
||||
* Beispiel:
|
||||
```python
|
||||
def add[T](x: T, y: T) -> T:
|
||||
return x + y
|
||||
```
|
||||
* `T` kann nur ein Datentyp sein, also muss `type(x) == type(y)` gelten
|
||||
* **außer** wir schrenken `T` mit `|` ein: `T: (int | str)` damit müssen x und y nicht den gleichen Datentypen haben
|
||||
* `T` lässt sich weiter einschränken durch `T: (int, str)`, hierbei ist `T` entweder ein `int` oder (exklusiv) `str`
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - Was ist TypeVar?
|
||||
|
||||
* `TypeVar` ist aus früheren Python-Versionen
|
||||
* Typvariablen wurden vor der Python 3.12 so definiert:
|
||||
```python
|
||||
T = TypeVar('T')
|
||||
```
|
||||
* sieht dumm aus, ist es auch, benutzt es nicht!
|
||||
|
||||
---
|
||||
|
||||
## Fragen zu Typannotationen?
|
||||
|
||||
---
|
||||
|
||||
# Funktionale Programmierung
|
||||
|
||||
---
|
||||
|
||||
## Funktionale Programmierung - was ist das?
|
||||
|
||||
- Funktionen sind äquivalent zu Datenobjekten
|
||||
- anonyme Funktionen aka Lambdas
|
||||
- Closures
|
||||
- Programmablauf mit Verkettung und Komposition von Funktionen
|
||||
|
||||
---
|
||||
|
||||
## Funktionen sind Datenobjekte
|
||||
|
||||
- Jede Funktion hat den Datentyp `Callable`
|
||||
- Wir können Funktionen wie alle anderen Objekte variabeln zuweisen
|
||||
```python
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
add_but_variable = add
|
||||
|
||||
print(add_but_variable(3, 2)) # 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anonyme Funktionen - `lambda`
|
||||
|
||||
- Mit dem `lambda` Keyword lassen sich anonyme Funktionen definieren ohne `def`
|
||||
- Bietet sich vor allem an für kleine Funktionen und Kompositionen von Funktionen
|
||||
```python
|
||||
print(reduce(lambda x, y: x + y, [1, 2, 3, 4])) # 10
|
||||
```
|
||||
- hat als Datentyp auch `Callable`
|
||||
```python
|
||||
add: Callable[[int, int], int] = lambda x, y: x + y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Closures
|
||||
|
||||
- Verkettete Funktionen, bei denen die Variabeln aus vorherigen benutzt werden können
|
||||
```python
|
||||
def poly(x: float) -> Callable[[float, float], Callable[[float], float]]:
|
||||
return lambda a, b: lambda c: a * x ** 2 + b * x + c
|
||||
|
||||
print(poly(3)(2, 3)(5)) # 2 * 3 ** 2 + 3 * 3 + 5 = 32
|
||||
```
|
||||
- kein wirklich schönes Beispiel, ein besseres ist `compose` für Kompositionen
|
||||
|
||||
---
|
||||
|
||||
## Komposition
|
||||
|
||||
- Verketten von Funktionen
|
||||
```python
|
||||
def compose[T](*funcs: Callable[[T], T]) -> Callable[[T], T]:
|
||||
return fold(lambda f, g: lambda n: f(g(n)), funcs)
|
||||
|
||||
f: Callable[[int], int] = lambda n: n + 42
|
||||
g: Callable[[int], int] = lambda n: n ** 2
|
||||
h: Callable[[int], int] = lambda n: n - 3
|
||||
|
||||
print(compose(f, g, h)(0))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Higher-Order Functions
|
||||
|
||||
- nehmen eine oder mehrere `Callable` als Argument
|
||||
- geben ein `Callable` zurück
|
||||
|
||||
### Higher-Order-Function - `map`
|
||||
|
||||
- Wendet ein `Callable` auf jedes Element in einem `Iterable` an
|
||||
|
||||
```python
|
||||
def map[T, R](func: Callable[[T], R], xs: Iterable[T]) -> Iterable[R]:
|
||||
return [func(x) for x in xs]
|
||||
|
||||
numeric_list = list(map(lambda e: int(e), ['1', '2', '3']))
|
||||
print(numeric_list) # [1, 2, 3]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Higher-Order-Function - `filter`
|
||||
|
||||
- `filter` verarbeitet Datenstrukturen anhand eines Prädikats (`Callable`)
|
||||
- behält nur Elemente die das Prädikat erfüllen
|
||||
```python
|
||||
def filter[T](predicate: Callable[[T], bool], xs: Iterable[T]) -> Iterable[T]:
|
||||
return [x for x in xs if predicate(x)]
|
||||
|
||||
predicate: Callable[[int | None] bool] = lambda e: e is not None
|
||||
none_free_list: list[int] = list(filter(predicate, [1, 2, 3, None, 5, 6]))
|
||||
print(none_free_list) # [1, 2, 3, 5, 6] - kein None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Higher-Order-Function - `fold`
|
||||
|
||||
- Kombiniert Elemente einer Datenstruktur
|
||||
```python
|
||||
def fold[T](func: Callable[[T, T], T], xs: Iterable[T]) -> T:
|
||||
it: Iterator[T] = iter(xs)
|
||||
value: T | None = None
|
||||
for x in it:
|
||||
match value:
|
||||
case None:
|
||||
value = x
|
||||
case _:
|
||||
value = func(value, x)
|
||||
if not value:
|
||||
raise TypeError("can't fold empty list")
|
||||
return value
|
||||
|
||||
sum: Callable[[Iterable[int]], int] = lambda xs: fold(lambda x, y: x + y, xs)
|
||||
print(sum([1, 2, 3, 4])) # 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### keine Higher-Order-Function - `flatten`
|
||||
|
||||
- Nimmt mehrdimensionale Listen und macht eine Liste draus
|
||||
```python
|
||||
def flatten(xs: Iterable[Any]) -> Iterable[Any]:
|
||||
new_list = []
|
||||
for s in xs:
|
||||
if isinstance(s, Iterable):
|
||||
new_list += flatten(s)
|
||||
else:
|
||||
new_list.append(s)
|
||||
return new_list
|
||||
|
||||
flattened = list(flatten([[1, 2, 3], 4, [[5, 6], 7, [8, 9]]]))
|
||||
print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
```
|
||||
- nimmt weder `Callable` als Argumente
|
||||
- gibt kein `Callable` zurück
|
||||
- ist keine Higher-Order-Function
|
||||
|
||||
---
|
||||
|
||||
# Fragen zur funktionalen Programmierung?
|
||||
|
||||
---
|
||||
|
||||
# Weitere allgemeine Fragen?
|
||||
|
21
Tutorium/tut12/notes.md
Normal file
21
Tutorium/tut12/notes.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Notes for Tutorium 12 - 19.01.2024
|
||||
|
||||
## Topics
|
||||
|
||||
- Type Annotation - The Full Guide
|
||||
- Basics
|
||||
- parameter
|
||||
- variable
|
||||
- return type
|
||||
-
|
||||
- Advanced
|
||||
- Collections
|
||||
- ~~args, kwargs~~
|
||||
- Functional programming
|
||||
- `lambda`
|
||||
- map
|
||||
- filter
|
||||
- reduce
|
||||
- flatten
|
||||
- function composition
|
||||
- Maybe-Type
|
BIN
Tutorium/tut12/slides.pdf
Normal file
BIN
Tutorium/tut12/slides.pdf
Normal file
Binary file not shown.
84
Tutorium/tut12/src/functional.py
Normal file
84
Tutorium/tut12/src/functional.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from typing import Any, Callable, Iterable, Iterator
|
||||
|
||||
|
||||
def map[T, R](func: Callable[[T], R], xs: Iterable[T]) -> Iterable[R]:
|
||||
return [func(x) for x in xs]
|
||||
|
||||
|
||||
def filter[T](predicate: Callable[[T], bool], xs: Iterable[T]) -> Iterable[T]:
|
||||
return [x for x in xs if predicate(x)]
|
||||
|
||||
|
||||
def fold[T](func: Callable[[T, T], T], xs: Iterable[T]) -> T:
|
||||
it: Iterator[T] = iter(xs)
|
||||
value: T | None = None
|
||||
for x in it:
|
||||
match value:
|
||||
case None:
|
||||
value = x
|
||||
case _:
|
||||
value = func(value, x)
|
||||
if not value:
|
||||
raise TypeError("can't fold empty list")
|
||||
return value
|
||||
|
||||
def flatten(xs: Iterable[Any]) -> Iterable[Any]:
|
||||
new_list = []
|
||||
for s in xs:
|
||||
if isinstance(s, Iterable):
|
||||
new_list += flatten(s)
|
||||
else:
|
||||
new_list.append(s)
|
||||
return new_list
|
||||
|
||||
def compose[T](*funcs: Callable[[T], T]) -> Callable[[T], T]:
|
||||
return fold(lambda f, g: lambda n: f(g(n)), funcs)
|
||||
|
||||
|
||||
def poly(x: float) -> Callable[[float, float], Callable[[float], float]]:
|
||||
return lambda a, b: lambda c: a * x ** 2 + b * x + c
|
||||
|
||||
def main():
|
||||
f: Callable[[int], int] = lambda n: n + 42
|
||||
g: Callable[[int], int] = lambda n: n ** 2
|
||||
h: Callable[[int], int] = lambda n: n - 3
|
||||
|
||||
fhg: Callable[[int], int] = compose(f, g, h)
|
||||
|
||||
# f(g(h(0))) <=> ((0 - 3) ** 2) + 42 = 51
|
||||
assert (tmp := fhg(0)) == 51
|
||||
assert compose(f, g, h)(0) == 51
|
||||
predicate = lambda e: e
|
||||
assert list(filter(predicate, [1, 2, 3, None, 5, 6])) == [1, 2, 3, 5, 6]
|
||||
assert list(filter(lambda e: e is None, [1, 2, 3, None, 5, 6])) == [None]
|
||||
|
||||
assert list(map(lambda e: str(e), [1, 2, 3, 4, 5, 6, "hello_functional"])) == ["1", "2", "3", "4", "5", "6", "hello_functional"]
|
||||
|
||||
assert list(
|
||||
filter(lambda e: len(e) > 1,
|
||||
map(lambda e: str(e),
|
||||
[1, 2, 3, 4, "hello_world"]))) == ["hello_world"]
|
||||
|
||||
assert list(filter(lambda e: isinstance(e, int), [1, 2, 3, "hello"])) == [1, 2, 3]
|
||||
assert (tmp := list(flatten([[1, 2, 3], 4, [[5, 6], 7, [8, 9]]]))) == [1, 2, 3, 4, 5, 6, 7, 8, 9], f"{tmp}"
|
||||
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
add_but_variable: Callable[[int, int], int] = add
|
||||
|
||||
assert add_but_variable(3, 2) == 5
|
||||
|
||||
add2: Callable[[int, int], int] = lambda x, y: x + y
|
||||
|
||||
assert add2(2, 3) == 5
|
||||
|
||||
assert (lambda x, y: x + y)(3, 4) == 7
|
||||
|
||||
sum: Callable[[Iterable[int]], int] = lambda xs: fold(lambda x, y: x + y, xs)
|
||||
assert sum([1, 2, 3, 4]) == 10
|
||||
|
||||
assert poly(3)(2, 3)(5) == 2 * 3 ** 2 + 3 * 3 + 5
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -0,0 +1,424 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
# class: invert
|
||||
theme: rose-pine
|
||||
footer: Tutorium 13 - 26.01.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-13
|
||||
header:
|
||||
math: mathjax
|
||||
---
|
||||
|
||||
# Tutorium 13 - 26.01.2024
|
||||
|
||||
Orga - Wiederholung Types - Functions!
|
||||
|
||||
---
|
||||
|
||||
# Orga
|
||||
|
||||
---
|
||||
## Wegen fehlendem Tutorium am 19.01.
|
||||
|
||||
- Jeder kriegt die 6 Punkte für Anwesenheit
|
||||
- Auf Blatt 13 als Extrapunkte unter *Anmerkungen*
|
||||
- Sorry fürs nicht beantworten von manchen Nachrichten
|
||||
- Falls ihr glaubt ihr bekommt knapp nicht genug Punkte schreibt mich einfach an, man wird schon noch irgendwo Punkte finden
|
||||
|
||||
---
|
||||
|
||||
## Syntax-Fehler
|
||||
|
||||
- Für **Syntax-Fehler** habe ich im allgemeinen **0 Punkte** in der jeweiligen Datei vergeben
|
||||
- Das euer Programm ausführbar ist sollte das mindeste sein!
|
||||
- Ihr sollt euer Programm sowieso selbständig testen und ich geh mal davon aus das ist nicht passiert wenn sich die Datei nichtmal ausführen lässt
|
||||
- Zeitdruck kann ich voll nachvollziehen
|
||||
|
||||
---
|
||||
|
||||
## Nachträgliches ausbessern
|
||||
|
||||
- Ihr verbessert euren SyntaxFehler (eure Python-Datei ist ausführbar)
|
||||
- Ihr schickt mir eine `.zip` oder eine `.tar.gz` mit dem verbesserten Code an [nils@narl.io](mailto:nils@narl.io)
|
||||
- verbessert nichts anderes!
|
||||
- Schreibt kurz in die Mail welches Blatt + Aufgabe + Kürzel
|
||||
- Ich korrigiere eure Abgabe nachträglich und ihr bekommt zumindest mehr als 0 Punkte
|
||||
- Bitte nur wenn ihr wirklich die Punkte braucht und habt etwas Geduld mit der Korrektur
|
||||
|
||||
---
|
||||
|
||||
## Allgemeines
|
||||
|
||||
- biete euch Übungen passend zur Klausur
|
||||
- kein genaues Datum, aber vor dem 09.02
|
||||
- Klausur ist *wahrscheinlich* am 19.02.
|
||||
- Short-Link zu der Übung [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
|
||||
- aktuell noch nicht online
|
||||
|
||||
---
|
||||
|
||||
# Link: [https://s.narl.io/s/eidp-ub](https://s.narl.io/s/eidp-ub)
|
||||
|
||||
---
|
||||
|
||||
# Type annotations
|
||||
(Wiederholung)
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Was ist das?
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Was ist das?
|
||||
|
||||
* Jedes **Objekt** lässt sich mindestens einem **Typ** zuordnen
|
||||
* Objekte im mathematischen Sinne wie z.B. Variablen, Funktionen, ...
|
||||
* Dieser **schränkt** den Wertebereich ein
|
||||
* z.B. ist eine Variable `x` von Typ `int` eine Ganzzahl
|
||||
* ähnlich zur mathematischen Schreibweise $x \in \mathbb{Z}$
|
||||
* In der Informatik nennt man das **Typisierung**
|
||||
* Es gibt verschiedene Arten der Typisierung
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Typisierung
|
||||
|
||||
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
|
||||
- also erst wenn das Programm *läuft*
|
||||
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
|
||||
- also während wir den Quellcode übersetzen
|
||||
|
||||
---
|
||||
|
||||
## Type annotations - Typisierung
|
||||
|
||||
- **dynamische Typisierung** überprüft die gegebenen Typen zur **Laufzeit**
|
||||
- also erst wenn das Programm *läuft*
|
||||
- **statische Typisierung** überprüft die gegebenen Typen zur **Übersetzungszeit**
|
||||
- also während wir den Quellcode übersetzen
|
||||
|
||||
### Was ist nun Python?
|
||||
|
||||
---
|
||||
|
||||
### Was ist nun Python?
|
||||
|
||||
- **dynamisch typisiert**
|
||||
- wir müssen unsere `.py` Datei ausführen bevor wir wissen ob alles korrekt ist
|
||||
- **Pylance** ist ein eigenes Programm
|
||||
- es soll beim Schreiben bereits **Typverletzungen** erkennen
|
||||
- **unvollständige** Typüberprüfung, es soll nur den Entwickler unterstützen
|
||||
|
||||
---
|
||||
|
||||
## Variabeln Typannotieren
|
||||
|
||||
* `variable_name: <Type> = ...`
|
||||
* Beispiele:
|
||||
```python
|
||||
x: int = 3
|
||||
y: int = 5
|
||||
string: str = "Hello World!"
|
||||
|
||||
# aber auch eigene Objekte (OOP)
|
||||
point: Point = Point(3, 1)
|
||||
```
|
||||
* diese Annotation ist für uns **optional**
|
||||
|
||||
---
|
||||
|
||||
## Funktionen Typannotieren
|
||||
|
||||
* `def func_name(param1: <Type>, param2: <Type>, ...) -> <Type>`
|
||||
* Beispiele:
|
||||
```python
|
||||
def add(x: int, y: int) -> int:
|
||||
return x + y
|
||||
|
||||
def div(x: float, y: float) -> Optional[float]:
|
||||
if y == 0.0:
|
||||
return None
|
||||
return x / y
|
||||
```
|
||||
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
|
||||
|
||||
---
|
||||
|
||||
## Klassen Typannotieren
|
||||
|
||||
*
|
||||
```
|
||||
class ClassName:
|
||||
attribute_name1: <Type>
|
||||
attribute_name2: <Type>
|
||||
...
|
||||
```
|
||||
* Beispiel:
|
||||
```python
|
||||
@dataclass
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
```
|
||||
* diese Annotation ist **verpflichtend** und muss so vollständig wie möglich sein
|
||||
|
||||
---
|
||||
|
||||
## Methoden Typannotieren
|
||||
|
||||
* `def method_name(self, param1: <Type>, ...) -> <Type>`
|
||||
* Beispiel:
|
||||
```python
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def distance_from(self, other: 'Point') -> float:
|
||||
return math.sqrt((other.x - self.x) ** 2 + (other.y - self.y) ** 2)
|
||||
```
|
||||
* `self` muss **nicht** Typannotiert werden, kann aber
|
||||
* `other` hingegen schon, wegen Python muss in der Klasse mit `'` annotiert werden
|
||||
* diese Annotation ist **verpflichtend**
|
||||
|
||||
---
|
||||
|
||||
## Datentypen von Datentypen
|
||||
|
||||
* Manche Datentypen bauen sich aus anderen Datentypen auf
|
||||
* z.B. `list` ist eine Liste von Elementen mit einem Typ
|
||||
* hierfür verwenden wir `[]` um den Datentyp in `list` zu annotieren
|
||||
```python
|
||||
def sum(xs: list[int]) -> int:
|
||||
total: int = 0
|
||||
for x in xs:
|
||||
total += x
|
||||
return total
|
||||
```
|
||||
* hierbei ist es wichtig so genau wie möglich zu annotieren!
|
||||
* diese Annotation ist **verpflichtend**
|
||||
|
||||
---
|
||||
|
||||
## Häufige Fehler mit verschachtelten Typen
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - `tuple[...]`
|
||||
|
||||
* Tuple haben eine feste größe
|
||||
* Tuple sind endlich
|
||||
* Tuple können Elemente mit unterschiedlichen Typen haben
|
||||
* Die Datentypen der Elemente werden mit einem `,` in `[]` getrennt
|
||||
* Beispiel:
|
||||
```python
|
||||
tup: tuple[int, int, float, str] = (1, 2, 3.0, "hello world")
|
||||
```
|
||||
* Diese Annotation ist **verpflichtend**
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - `dict[...]`
|
||||
|
||||
* Dictionary haben genau zwei zu definierende Typen
|
||||
* **Key**
|
||||
* **Value**
|
||||
* Beispiel:
|
||||
```python
|
||||
number_dictionary: dict[int, str] = {
|
||||
0: "zero",
|
||||
1: "one",
|
||||
2: "two",
|
||||
}
|
||||
```
|
||||
* Diese Annotation ist **verpflichtend**
|
||||
* Diese kann weiter geschachtelt werden durch z.B. `list` als `Value`:
|
||||
* `dict[int, list[str]]`
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - Typvariabeln (generische Typen)
|
||||
|
||||
* manchmal wollen wir nicht genau wissen welchen Datentypen wir haben
|
||||
* dieser wird dann implizit von Python erkannt
|
||||
* wir stellen damit sicher dass eine Typvariable **beliebig** aber **fest** ist
|
||||
* Beispiel:
|
||||
```python
|
||||
def add[T](x: T, y: T) -> T:
|
||||
return x + y
|
||||
```
|
||||
* `T` kann nur ein Datentyp sein, also muss `type(x) == type(y)` gelten
|
||||
* **außer** wir schrenken `T` mit `|` ein: `T: (int | str)` damit müssen x und y nicht den gleichen Datentypen haben
|
||||
* `T` lässt sich weiter einschränken durch `T: (int, str)`, hierbei ist `T` entweder ein `int` oder (exklusiv) `str`
|
||||
|
||||
---
|
||||
|
||||
## Fehlerquelle - Was ist TypeVar?
|
||||
|
||||
* `TypeVar` ist aus früheren Python-Versionen
|
||||
* Typvariablen wurden vor der Python 3.12 so definiert:
|
||||
```python
|
||||
T = TypeVar('T')
|
||||
```
|
||||
* sieht dumm aus, ist es auch, benutzt es nicht!
|
||||
|
||||
---
|
||||
|
||||
## Fragen zu Typannotationen?
|
||||
|
||||
---
|
||||
|
||||
# Funktionale Programmierung
|
||||
|
||||
---
|
||||
|
||||
## Funktionale Programmierung - was ist das?
|
||||
|
||||
- Funktionen sind äquivalent zu Datenobjekten
|
||||
- anonyme Funktionen aka Lambdas
|
||||
- Closures
|
||||
- Programmablauf mit Verkettung und Komposition von Funktionen
|
||||
|
||||
---
|
||||
|
||||
## Funktionen sind Datenobjekte
|
||||
|
||||
- Jede Funktion hat den Datentyp `Callable`
|
||||
- Wir können Funktionen wie alle anderen Objekte variabeln zuweisen
|
||||
```python
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
add_but_variable = add
|
||||
|
||||
print(add_but_variable(3, 2)) # 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anonyme Funktionen - `lambda`
|
||||
|
||||
- Mit dem `lambda` Keyword lassen sich anonyme Funktionen definieren ohne `def`
|
||||
- Bietet sich vor allem an für kleine Funktionen und Kompositionen von Funktionen
|
||||
```python
|
||||
print(reduce(lambda x, y: x + y, [1, 2, 3, 4])) # 10
|
||||
```
|
||||
- hat als Datentyp auch `Callable`
|
||||
```python
|
||||
add: Callable[[int, int], int] = lambda x, y: x + y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Closures
|
||||
|
||||
- Verkettete Funktionen, bei denen die Variabeln aus vorherigen benutzt werden können
|
||||
```python
|
||||
def poly(x: float) -> Callable[[float, float], Callable[[float], float]]:
|
||||
return lambda a, b: lambda c: a * x ** 2 + b * x + c
|
||||
|
||||
print(poly(3)(2, 3)(5)) # 2 * 3 ** 2 + 3 * 3 + 5 = 32
|
||||
```
|
||||
- kein wirklich schönes Beispiel, ein besseres ist `compose` für Kompositionen
|
||||
|
||||
---
|
||||
|
||||
## Komposition
|
||||
|
||||
- Verketten von Funktionen
|
||||
```python
|
||||
def compose[T](*funcs: Callable[[T], T]) -> Callable[[T], T]:
|
||||
return fold(lambda f, g: lambda n: f(g(n)), funcs)
|
||||
|
||||
f: Callable[[int], int] = lambda n: n + 42
|
||||
g: Callable[[int], int] = lambda n: n ** 2
|
||||
h: Callable[[int], int] = lambda n: n - 3
|
||||
|
||||
print(compose(f, g, h)(0))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Higher-Order Functions
|
||||
|
||||
- nehmen eine oder mehrere `Callable` als Argument
|
||||
- geben ein `Callable` zurück
|
||||
|
||||
### Higher-Order-Function - `map`
|
||||
|
||||
- Wendet ein `Callable` auf jedes Element in einem `Iterable` an
|
||||
|
||||
```python
|
||||
def map[T, R](func: Callable[[T], R], xs: Iterable[T]) -> Iterable[R]:
|
||||
return [func(x) for x in xs]
|
||||
|
||||
numeric_list = list(map(lambda e: int(e), ['1', '2', '3']))
|
||||
print(numeric_list) # [1, 2, 3]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Higher-Order-Function - `filter`
|
||||
|
||||
- `filter` verarbeitet Datenstrukturen anhand eines Prädikats (`Callable`)
|
||||
- behält nur Elemente die das Prädikat erfüllen
|
||||
```python
|
||||
def filter[T](predicate: Callable[[T], bool], xs: Iterable[T]) -> Iterable[T]:
|
||||
return [x for x in xs if predicate(x)]
|
||||
|
||||
predicate: Callable[[int | None] bool] = lambda e: e is not None
|
||||
none_free_list: list[int] = list(filter(predicate, [1, 2, 3, None, 5, 6]))
|
||||
print(none_free_list) # [1, 2, 3, 5, 6] - kein None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Higher-Order-Function - `fold`
|
||||
|
||||
- Kombiniert Elemente einer Datenstruktur
|
||||
```python
|
||||
def fold[T](func: Callable[[T, T], T], xs: Iterable[T]) -> T:
|
||||
it: Iterator[T] = iter(xs)
|
||||
value: T | None = None
|
||||
for x in it:
|
||||
match value:
|
||||
case None:
|
||||
value = x
|
||||
case _:
|
||||
value = func(value, x)
|
||||
if not value:
|
||||
raise TypeError("can't fold empty list")
|
||||
return value
|
||||
|
||||
sum: Callable[[Iterable[int]], int] = lambda xs: fold(lambda x, y: x + y, xs)
|
||||
print(sum([1, 2, 3, 4])) # 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### keine Higher-Order-Function - `flatten`
|
||||
|
||||
- Nimmt mehrdimensionale Listen und macht eine Liste draus
|
||||
```python
|
||||
def flatten(xs: Iterable[Any]) -> Iterable[Any]:
|
||||
new_list = []
|
||||
for s in xs:
|
||||
if isinstance(s, Iterable):
|
||||
new_list += flatten(s)
|
||||
else:
|
||||
new_list.append(s)
|
||||
return new_list
|
||||
|
||||
flattened = list(flatten([[1, 2, 3], 4, [[5, 6], 7, [8, 9]]]))
|
||||
print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
```
|
||||
- nimmt weder `Callable` als Argumente
|
||||
- gibt kein `Callable` zurück
|
||||
- ist keine Higher-Order-Function
|
||||
|
||||
---
|
||||
|
||||
# Fragen zur funktionalen Programmierung?
|
||||
|
||||
---
|
||||
|
||||
# Weitere allgemeine Fragen?
|
||||
|
BIN
Tutorium/tut13/slides.pdf
Normal file
BIN
Tutorium/tut13/slides.pdf
Normal file
Binary file not shown.
@@ -0,0 +1,153 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
# class: invert
|
||||
theme: rose-pine
|
||||
footer: Tutorium 14 - 02.02.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-14
|
||||
header:
|
||||
math: mathjax
|
||||
---
|
||||
|
||||
# Tutorium 14 - 02.02.2024
|
||||
|
||||
Decorator, Testing
|
||||
|
||||
---
|
||||
|
||||
# Decorator
|
||||
|
||||
- **Design-Pattern**, oft auch **Wrapper** genannt
|
||||
- Verpackt ein Objekt um **zusätzliche Funktionalität** zu bieten
|
||||
- Funktionen sind auch Objekte
|
||||
- eine Klasse ist ein Objekt
|
||||
- Oft einfach **syntax sugar**
|
||||
|
||||
---
|
||||
## Beispiel - execute_two_times
|
||||
|
||||
```python
|
||||
def execute_two_times(fn: Callable[..., Any]) -> Callable[..., Any]:
|
||||
def wrapper(*args, **kwargs)
|
||||
fn(*args, **kwargs)
|
||||
fn(*args, **kwargs)
|
||||
return wrapper
|
||||
return wrapper
|
||||
|
||||
@execute_two_times()
|
||||
def print_two_times(msg: str):
|
||||
print(msg)
|
||||
|
||||
print_two_times("hello") # hello
|
||||
# hello
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiel - execute_by
|
||||
|
||||
```python
|
||||
def execute_by(n: int):
|
||||
def wrapper(fn):
|
||||
def wrapped_fn(*args, **kwargs):
|
||||
for _ in range(0, n):
|
||||
fn(*args, **kwargs)
|
||||
return wrapped_fn
|
||||
return wrapped_fn
|
||||
return wrapper
|
||||
|
||||
@execute_by(10)
|
||||
def print_ten_times(msg: str):
|
||||
print(msg)
|
||||
|
||||
print_ten_times("hello") # hello
|
||||
# hello
|
||||
# ... (10 mal)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiel - CommandExecutor
|
||||
|
||||
```python
|
||||
class CommandExecutor[R]:
|
||||
|
||||
def __init__(self):
|
||||
self.__commands: dict[str, Callable[..., R]] = {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiel - run
|
||||
|
||||
```python
|
||||
def run(self, name: str, *args, **kwargs) -> list[R]:
|
||||
results : list[R] = []
|
||||
for command_name, command in self.__commands.items():
|
||||
if command_name == name:
|
||||
results += [command(*args, **kwargs)]
|
||||
return results
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiel - register
|
||||
|
||||
```python
|
||||
def register(self, cmd: Callable[..., R]) -> Callable[..., R]:
|
||||
self.__commands[cmd.__name__] = cmd
|
||||
return cmd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiel - CommandExecutor
|
||||
|
||||
```python
|
||||
class CommandExecutor[R]:
|
||||
def __init__(self):
|
||||
self.__commands: dict[str, Callable[..., R]] = {}
|
||||
|
||||
def run(self, name: str, *args, **kwargs) -> list[R]:
|
||||
results : list[R] = []
|
||||
for command_name, command in self.__commands.items():
|
||||
if command_name == name:
|
||||
results += [command(*args, **kwargs)]
|
||||
return results
|
||||
|
||||
def register(self, cmd: Callable[..., R]) -> Callable[..., R]:
|
||||
self.__commands[cmd.__name__] = cmd
|
||||
return cmd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiel - How to use
|
||||
|
||||
```python
|
||||
app = CommandExecutor[str]()
|
||||
|
||||
@app.register
|
||||
def hello_world() -> str:
|
||||
return 'hello_world'
|
||||
|
||||
@app.register
|
||||
def divide(a: int, b: int) -> str:
|
||||
if b == 0:
|
||||
return "tried to divide by zero"
|
||||
return str(a / b)
|
||||
|
||||
print(app.run('hello_world'))
|
||||
print(app.run('divide', 5, 0))
|
||||
print(app.run('divide', 10, 2))
|
||||
```
|
||||
---
|
||||
|
||||
## Decorator in der Klausur
|
||||
|
||||
- Waren noch nie Bestandteil der Klausur
|
||||
- Mut zur Lücke
|
||||
- Kann euch natürlich nichts versprechen
|
||||
|
||||
---
|
||||
|
||||
# Testen mit `pytest`
|
BIN
Tutorium/tut14/slides.pdf
Normal file
BIN
Tutorium/tut14/slides.pdf
Normal file
Binary file not shown.
36
Tutorium/tut14/src/commands.py
Normal file
36
Tutorium/tut14/src/commands.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from typing import Callable, Iterator
|
||||
|
||||
|
||||
class CommandExecutor[R]:
|
||||
def __init__(self):
|
||||
self.__commands: dict[str, Callable[..., R]] = {}
|
||||
|
||||
def run(self, name: str, *args, **kwargs) -> list[R]:
|
||||
results : list[R] = []
|
||||
for command_name, command in self.__commands.items():
|
||||
if command_name == name:
|
||||
results += [command(*args, **kwargs)]
|
||||
return results
|
||||
|
||||
def register(self, cmd: Callable[..., R]) -> Callable[..., R]:
|
||||
self.__commands[cmd.__name__] = cmd
|
||||
return cmd
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = CommandExecutor[str]()
|
||||
|
||||
@app.register
|
||||
def hello_world() -> str:
|
||||
return 'hello_world'
|
||||
|
||||
@app.register
|
||||
def divide(a: int, b: int) -> str:
|
||||
if b == 0:
|
||||
return "tried to divide by zero"
|
||||
return str(a / b)
|
||||
|
||||
print(app.run('hello_world'))
|
||||
print(app.run('divide', 5, 0))
|
||||
print(app.run('divide', 10, 2))
|
||||
|
67
Tutorium/tut14/src/decorator.py
Normal file
67
Tutorium/tut14/src/decorator.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
def count_calls[T](func: Callable[..., T]) -> Callable[..., T]:
|
||||
count_calls.calls = 0
|
||||
def wrapper(*args, **kwargs) -> T:
|
||||
init_calls = count_calls.calls
|
||||
count_calls.calls += 1
|
||||
result = func(*args, **kwargs)
|
||||
wrapper.calls = count_calls.calls - init_calls
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
def f(x: int, y: int) -> int:
|
||||
if x % 2 == 0:
|
||||
return x // 2
|
||||
else:
|
||||
return x + 2 * y - 1
|
||||
|
||||
def count_iterations(a: int, b: int) -> int:
|
||||
f_a(a, a, b)
|
||||
return f_a.calls - 1
|
||||
|
||||
@count_calls
|
||||
def f_a(init: int, a: int, b: int) -> None:
|
||||
if a < b:
|
||||
return
|
||||
return f_a(init, f(a, init), b)
|
||||
|
||||
def execute_by(n: int):
|
||||
def wrapper(fn):
|
||||
def wrapped_fn(*args, **kwargs):
|
||||
for _ in range(0, n):
|
||||
fn(*args, **kwargs)
|
||||
return wrapped_fn
|
||||
return wrapped_fn
|
||||
return wrapper
|
||||
|
||||
@execute_by(10)
|
||||
def hello_world():
|
||||
print('hello world!')
|
||||
|
||||
@execute_by(10)
|
||||
def print_ten_times(msg: str):
|
||||
print(msg)
|
||||
|
||||
|
||||
def execute_two_times(fn) -> Callable[..., Any]:
|
||||
def wrapper(*args, **kwargs):
|
||||
for _ in range(0, 2):
|
||||
fn(*args, **kwargs)
|
||||
return wrapper
|
||||
return wrapper
|
||||
|
||||
@execute_two_times
|
||||
def test(msg: str):
|
||||
print(msg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
assert (i := count_iterations(7, 6)) == 3, i
|
||||
assert (i := count_iterations(3, 2)) == 4, i
|
||||
assert (i := count_iterations(13, 9)) == 18, i
|
||||
assert (i := count_iterations(13, 10)) == 8, i
|
||||
assert (i := count_iterations(3, 4)) == 0, i
|
||||
print_ten_times("hello world")
|
||||
test("hello")
|
||||
|
185
Tutorium/tut15/README.md
Normal file
185
Tutorium/tut15/README.md
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
# class: invert
|
||||
theme: rose-pine
|
||||
footer: Tutorium 15 - 09.02.2024 - Nils Pukropp - https://s.narl.io/s/tutorium-15
|
||||
header:
|
||||
math: mathjax
|
||||
---
|
||||
|
||||
# Tutorium 15 - 19.02.2024
|
||||
|
||||
Orga, Test-Exam, Regex (Exkurs)
|
||||
|
||||
---
|
||||
|
||||
# Orga
|
||||
|
||||
---
|
||||
|
||||
## Orga - Punkte, Vorstellen und einscannen
|
||||
|
||||
- Ich habe bei **allen** auf Blatt 12 (oder dem letzten korrigierten) `+6p` für das **verpasste Tutorium** vergeben
|
||||
- Ich habe für **heute** bereits allen die **Anwesenheitspunkte + Vorstellen** eingetragen
|
||||
|
||||
Alle auf die das Zutrifft sind:
|
||||
|
||||
`as2037, at359, au56, aw616, bo35, cl393, dk446, eh224, eh295, fk439, fv100, ib180, jb1484, jx20, lf409, ln200, lp269, lp321, ls818, mk1518, mr824, mt367, mw793, mz144, mz242, nm320, no43, pk375, rh295, rl173, rw208, sn205, tr211, ua28, vb202, vb205, vr110, yp39, zj11`
|
||||
|
||||
Bei Problemen oder Rückfragen einfach per mail [nils@narl.io](mailto:nils@narl.io) oder nach dem Tutorium
|
||||
|
||||
---
|
||||
|
||||
## Orga - Klausur
|
||||
|
||||
- Klausur am 19.02.
|
||||
- Es gibt vorraussichtlich zwei Termine
|
||||
- 2 Stunden
|
||||
- keine unterschiedlichen Klausuren
|
||||
- Wo, Wann?
|
||||
- individuell
|
||||
- https://courses.laurel.informatik.uni-freiburg.de/courses/2023WS-EiP/exam
|
||||
- https://s.narl.io/s/termin
|
||||
- Klausurumgebung ausprobieren unter
|
||||
- https://bwlehrpool.ruf.uni-freiburg.de/guacamole
|
||||
- https://s.narl.io/s/examvm
|
||||
|
||||
---
|
||||
|
||||
## Orga - Vorbereitung auf Klausur
|
||||
|
||||
- Macht Altklausuren
|
||||
- Übungsaufgaben im Git
|
||||
- https://git.narl.io/nvrl/eidp-klausuraufgaben-2023
|
||||
- https://s.narl.io/s/eidp-ub
|
||||
- Wenn ihr die Probeklausur gut hinbekommen habt (**auch Zeitlich!!!**) seid ihr eig safe
|
||||
- Zusatztutorium mit Dani und mir
|
||||
|
||||
---
|
||||
|
||||
## Orga - Zusatztutorium von Dani und mir
|
||||
|
||||
- Wir machen Altklausuren/Übungsaufgaben
|
||||
- Zu zweit kann man sich etwas persönlicher kümmern
|
||||
- Gibt obv. keine Punkte, wir machen das auch nur freiwillig
|
||||
- Wann, Wo?
|
||||
- Mittwoch
|
||||
- x.xx Uhr open end
|
||||
- Hier in 101
|
||||
- Es folgt auch noch eine E-Mail an alle über dessen Uni-Mail mit allen Infos
|
||||
|
||||
---
|
||||
|
||||
# Test-Exam
|
||||
|
||||
---
|
||||
|
||||
## Test-Exam - Datenklassen
|
||||
|
||||
- Ihr könnt **private** Attribute nicht in einer Unterklasser verwenden!
|
||||
- Mit `super().post_init(...)` könnt ihr diese trotzdem setzen
|
||||
- `self.__privet_attribute` in einer Unterklasse führt zu einem Fehler
|
||||
- Es gibt `protected` welches von Außen nicht sichtbar ist, aber in Unterklassen
|
||||
- `_protected_attribute` welche mit einem `_` annotiert werden
|
||||
- Beißt sich leider etwas mit `InitVar[...]` von `dataclasses`
|
||||
- Vergesst am besten `private`, `public` für die Klausur :) versprechen kann ich aber nichts
|
||||
|
||||
---
|
||||
|
||||
## Test-Exam - Automata
|
||||
|
||||
- Bitte kein `T` oder Trash State in der Klausur, außer es ist explizit gefordert
|
||||
- Ein State bei dem invalide Eingaben hingeschoben werden
|
||||
- Auch wenn das die Musterlösung von Exercise-13 gemacht hat
|
||||
- Und auch wenn es eigentlich sinnvoller ist, weil wir wollen nicht bei einer falschen Eingabe dass unser Programm abstürzt
|
||||
```python
|
||||
class State(Enum):
|
||||
q0 = auto()
|
||||
q1 = auto()
|
||||
q2 = auto()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test-Exam - Automata
|
||||
|
||||
```python
|
||||
def delta(state: State, input: str) -> State:
|
||||
match state, input:
|
||||
case State.q0, "a":
|
||||
return State.q1
|
||||
case State.q0, "b":
|
||||
return State.q2
|
||||
case State.q1, "a":
|
||||
return State.q0
|
||||
case State.q1, "b":
|
||||
return State.q1
|
||||
case State.q2, "a":
|
||||
return State.q2
|
||||
case State.q2, "b":
|
||||
return State.q1
|
||||
case _:
|
||||
raise ValueError("invalid state or input")
|
||||
```
|
||||
---
|
||||
|
||||
# ReGex
|
||||
|
||||
---
|
||||
|
||||
## Was ist ein ReGex?
|
||||
|
||||
- Ein regulärer Ausdruck ist ein **match pattern** in einem **text**
|
||||
- Genau gesagt bildet es eine Menge von Zeichenketten (eine **Sprache**)
|
||||
- Ihr habt bereits ReGex benutzt
|
||||
- Wenn ihr z.B. im Browser Ctrl+F drückt und nach einem Wort sucht
|
||||
- das Wort ist dann ein ReGex
|
||||
- Es gibt aber auch deutlich komplexere ReGex
|
||||
|
||||
---
|
||||
|
||||
## Automaten schon wieder
|
||||
|
||||
- Was ist wenn wir einen Eingabe-String überprüfen wollen ob er
|
||||
- mit `a` beginnt
|
||||
- dann mindest ein, aber beliebig viele `b` folgen
|
||||
- und mit einem `a` endet
|
||||
- Wir können einen Akzeptor zeichnen! (nicht-deterministischen endlichen Automaten mit akzeptierenden Zustand)
|
||||
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## ReGex - Python
|
||||
|
||||
- In Python haben wir `re` also Modul
|
||||
- Ein ReGex ist eine Zeichenkette
|
||||
- `"ab"` akzeptiert `"ab"`
|
||||
- `re.fullmatch(r"ab", "ab")`
|
||||
- Es gibt Sonderzeichen wie `*, +, (, ), ...` mit denen man komplexere Eingaben überprüfen kann
|
||||
- Wir wollen `"ab...a"` von der vorherigen Slide matchen
|
||||
- `b*` möchte 0 bis unendlich `b`
|
||||
- `b+` möchte 1 bis unendlich `b`
|
||||
- also `re.fullmatch(r"ab+a", "abbbbbbba")` ist ein Match
|
||||
|
||||
---
|
||||
|
||||
## Weiter Sonderzeichen/Variabeln
|
||||
|
||||
- Mit `\d` kann man in Python eine beliebige Zahl meinen
|
||||
- Mit `\s` kann man ein beliebigen Whitespace meinen
|
||||
- So kann man z.B. eine beliebige Ip so darstellen
|
||||
- `r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'`
|
||||
- Nützlich zum Parsen oder auch Testen
|
||||
- Ich nutze z.b. ReGex um eure Aufgaben zu testen
|
||||
|
||||
---
|
||||
|
||||
# Viel Erfolg bei der Klausur!
|
||||
|
||||
|
||||
|
BIN
Tutorium/tut15/img/dfa.png
Normal file
BIN
Tutorium/tut15/img/dfa.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
BIN
Tutorium/tut15/slides.pdf
Normal file
BIN
Tutorium/tut15/slides.pdf
Normal file
Binary file not shown.
51
Tutorium/tut15/src/ex5_dataclasses.py
Normal file
51
Tutorium/tut15/src/ex5_dataclasses.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
# (a) Vehicle
|
||||
|
||||
|
||||
@dataclass
|
||||
class Vehicle:
|
||||
seats: int
|
||||
hp: int
|
||||
ccm: int
|
||||
weight: int
|
||||
|
||||
def __post_init__(self):
|
||||
assert 0 < self.seats < 10
|
||||
assert 0 < self.hp
|
||||
assert 0 < self.ccm
|
||||
assert 0 < self.weight
|
||||
|
||||
def fun_factor(self) -> float:
|
||||
return (10 * self.hp + self.ccm) / self.weight
|
||||
|
||||
def __gt__(self, other: 'Vehicle') -> bool:
|
||||
return self.fun_factor() > other.fun_factor()
|
||||
|
||||
# (b) Car
|
||||
|
||||
|
||||
@dataclass
|
||||
class Car(Vehicle):
|
||||
spoiler: bool
|
||||
|
||||
def fun_factor(self) -> float:
|
||||
return super().fun_factor() + (0.2 if self.spoiler else 0)
|
||||
|
||||
|
||||
# (c) Motorcycle
|
||||
|
||||
|
||||
@dataclass
|
||||
class Motorcycle(Vehicle):
|
||||
sidecar: bool
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
if self.sidecar:
|
||||
assert 2 <= self.seats <= 3
|
||||
else:
|
||||
assert 1 <= self.seats <= 2
|
||||
|
||||
def fun_factor(self) -> float:
|
||||
return super().fun_factor() * (2.4 if self.sidecar else 3)
|
84
Tutorium/tut15/src/ex5_dataclasses_private.py
Normal file
84
Tutorium/tut15/src/ex5_dataclasses_private.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from dataclasses import InitVar, dataclass
|
||||
|
||||
# (a) Vehicle
|
||||
|
||||
|
||||
@dataclass
|
||||
class Vehicle:
|
||||
_seats: InitVar[int]
|
||||
_hp: InitVar[int]
|
||||
_ccm: InitVar[int]
|
||||
_weight: InitVar[int]
|
||||
|
||||
def __post_init__(self, seats: int, hp: int, ccm: int, weight: int):
|
||||
assert 0 < seats < 10
|
||||
assert 0 < hp
|
||||
assert 0 < ccm
|
||||
assert 0 < weight
|
||||
self.__seats = seats
|
||||
self.__hp = hp
|
||||
self.__ccm = ccm
|
||||
self.__weight = weight
|
||||
|
||||
def fun_factor(self) -> float:
|
||||
return (10 * self.__hp + self.__ccm) / self.__weight
|
||||
|
||||
def __gt__(self, other: 'Vehicle') -> bool:
|
||||
return self.fun_factor() > other.fun_factor()
|
||||
|
||||
@property
|
||||
def seats(self) -> int:
|
||||
return self.__seats
|
||||
|
||||
@property
|
||||
def hp(self) -> int:
|
||||
return self.__hp
|
||||
|
||||
@property
|
||||
def ccm(self) -> int:
|
||||
return self.__ccm
|
||||
|
||||
@property
|
||||
def weight(self) -> int:
|
||||
return self.__weight
|
||||
|
||||
# (b) Car
|
||||
|
||||
|
||||
@dataclass
|
||||
class Car(Vehicle):
|
||||
_spoiler: InitVar[bool]
|
||||
|
||||
def __post_init__(self, seats: int, hp: int, ccm: int, weight: int, spoiler: bool):
|
||||
super().__post_init__(seats, hp, ccm, weight)
|
||||
self.__spoiler = spoiler
|
||||
|
||||
def fun_factor(self) -> float:
|
||||
return super().fun_factor() + (0.2 if self.__spoiler else 0)
|
||||
|
||||
@property
|
||||
def has_spoiler(self) -> bool:
|
||||
return self.__spoiler
|
||||
|
||||
|
||||
# (c) Motorcycle
|
||||
|
||||
|
||||
@dataclass
|
||||
class Motorcycle(Vehicle):
|
||||
_sidecar: InitVar[bool]
|
||||
|
||||
def __post_init__(self, seats: int, hp: int, ccm: int, weight: int, sidecar: bool):
|
||||
if sidecar:
|
||||
assert 2 <= seats <= 3
|
||||
else:
|
||||
assert 1 <= seats <= 2
|
||||
super().__post_init__(seats, hp, ccm, weight)
|
||||
self.__sidecar = sidecar
|
||||
|
||||
def fun_factor(self) -> float:
|
||||
return super().fun_factor() * (2.4 if self.__sidecar else 3)
|
||||
|
||||
@property
|
||||
def is_sidecar(self) -> bool:
|
||||
return self.__sidecar
|
Reference in New Issue
Block a user