added tuts and zusammenfassung
This commit is contained in:
568
Tutorium/tut12/README.md
Normal file
568
Tutorium/tut12/README.md
Normal file
@ -0,0 +1,568 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
theme: rose-pine
|
||||
footer: EidP 2024 - Nils Pukropp - https://git.narl.io/nvrl/eidp-2024
|
||||
style: ".columns {\r display: grid;\r grid-template-columns: repeat(2, minmax(0, 1fr));\r gap: 1rem;\r }"
|
||||
|
||||
---
|
||||
|
||||
# Tutorium 11 - 2025-01-09
|
||||
|
||||
Vorrechnen 10, Generatoren, OOP, Type-Annotation, Blatt 11
|
||||
|
||||
---
|
||||
|
||||
# Vorrechnen [Blatt 10](https://proglang.github.io/teaching/24ws/eidp/exercises/sheet10.pdf)
|
||||
|
||||
---
|
||||
|
||||
# Generator
|
||||
|
||||
Was ist das?
|
||||
|
||||
---
|
||||
|
||||
# Generator
|
||||
|
||||
- Generatoren sind Funktionen, die sich wie Iteratoren verhalten
|
||||
- Bei Iteratoren könnt ihr euch entscheiden wann ihr das nächste Element anschauen wollt
|
||||
- Genauso könnt ihr bei Generatoren entscheiden wann ihr das nächste Element berechnen wollt
|
||||
|
||||
---
|
||||
|
||||
# Generator - Beispiel
|
||||
|
||||
```python
|
||||
def my_range(start: int, end: int) -> list[int]:
|
||||
num = start
|
||||
nums = [num]
|
||||
while (num := num + 1) < end:
|
||||
nums.append(num)
|
||||
return nums
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Generator - Beispiel
|
||||
|
||||
```python
|
||||
for i in my_range(0, 101):
|
||||
print(i) # 0 .. 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Generator - Beispiel
|
||||
|
||||
```python
|
||||
from typing import Iterator
|
||||
|
||||
def my_range(start: int, end: int) -> Iterator[int]:
|
||||
num = start
|
||||
yield num # start ist inklusive
|
||||
while (num := num + 1) < end:
|
||||
yield num
|
||||
raise StopIteration
|
||||
```
|
||||
|
||||
- `raise StopIteration` wird häufig genutzt um das Ende eines Iterators zu signalisieren
|
||||
- kann jetzt genauso wie unsere erste `my_range` benutzt werden
|
||||
|
||||
---
|
||||
|
||||
# *Unendlicher* Generator goes brrr
|
||||
|
||||
```python
|
||||
from typing import Iterator
|
||||
|
||||
def my_range(start: int) -> Iterator[int]:
|
||||
num = start
|
||||
yield num # start ist inklusive
|
||||
while True:
|
||||
yield (num := num + 1)
|
||||
raise StopIteration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# OOP
|
||||
|
||||
Überschreiben von Methoden und Polymorphie
|
||||
|
||||
---
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Warum muss man überhaupt Methoden überschreiben?
|
||||
|
||||
---
|
||||
|
||||
# Vererbung - Animal
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Animal:
|
||||
age: int
|
||||
weight: int
|
||||
name: str
|
||||
```
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Cat(Animal):
|
||||
pass
|
||||
|
||||
@dataclass
|
||||
class Dog(Animal):
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Bisher in der Vorlesung
|
||||
|
||||
```python
|
||||
def make_noise(animal: Animal):
|
||||
match animal:
|
||||
case Dog():
|
||||
print("Woof")
|
||||
case Cat():
|
||||
print("Meow")
|
||||
case _:
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Bisher in der Vorlesung
|
||||
|
||||
```python
|
||||
def make_noise(animal: Animal):
|
||||
match animal:
|
||||
case Dog():
|
||||
print("Woof")
|
||||
case Cat():
|
||||
print("Meow")
|
||||
case _:
|
||||
pass
|
||||
```
|
||||
|
||||
das ist kein gutes Design, warum?
|
||||
|
||||
---
|
||||
|
||||
was ist wenn wir jetzt eine neue Klasse `Mouse` erstellen wollen.
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Mouse(Animal):
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
jetzt müssen wir `Mouse` zu `make_noise` hinzufügen
|
||||
|
||||
```python
|
||||
def make_noise(animal: Animal):
|
||||
match animal:
|
||||
case Dog():
|
||||
print("Woof")
|
||||
case Cat():
|
||||
print("Meow")
|
||||
case Mouse():
|
||||
print("Peep")
|
||||
case _:
|
||||
pass
|
||||
```
|
||||
|
||||
das kann ziemlich nervig werden. Vor allem wenn wir größere Projekte haben
|
||||
|
||||
---
|
||||
|
||||
# Polymorphism
|
||||
|
||||
> Die Polymorphie der objektorientierten Programmierung ist eine Eigenschaft, die immer im Zusammenhang mit Vererbung und Schnittstellen (Interfaces) auftritt. Eine Methode ist polymorph, wenn sie in verschiedenen Klassen die gleiche Signatur hat, jedoch erneut implementiert ist.
|
||||
|
||||
---
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Animal:
|
||||
age: int
|
||||
weight: float
|
||||
name: str
|
||||
|
||||
def make_noise(self) -> None:
|
||||
pass
|
||||
|
||||
@dataclass
|
||||
class Cat(Animal):
|
||||
|
||||
def make_noise(self) -> None:
|
||||
print("Meow")
|
||||
|
||||
@dataclass
|
||||
class Dog(Animal):
|
||||
|
||||
def make_noise(self) -> None:
|
||||
print("Woof")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Geht das schöner in Python 3.12?
|
||||
|
||||
Ja, mit `override` sagt sogar pylance bescheid wenn wir eine polymorphe Methode falsch überschrieben haben!
|
||||
```python
|
||||
form dataclasses import dataclass
|
||||
from typing import override
|
||||
|
||||
@dataclass
|
||||
class Cat(Animal):
|
||||
|
||||
@override
|
||||
def make_noise(self) -> None:
|
||||
print("Meow")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Unsere neue polymorphe Methode benutzen
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Zoo:
|
||||
animals: list[Animal] = []
|
||||
|
||||
|
||||
zoo = Zoo(animals=[
|
||||
Cat(age=6, weight=4.5, name="Milow"),
|
||||
Cat(age=7, weight=5.0, name="Blacky"),
|
||||
Dog(age=12, weight=40.3, name="Bernd")
|
||||
])
|
||||
|
||||
for animal in zoo.animals:
|
||||
animal.make_noise() # Meow
|
||||
# Meow
|
||||
# Woof
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Oberklasse Animal
|
||||
|
||||
- Was passiert wenn man `Animal` euzeugt?
|
||||
- Wir haben ein Objekt was eigentlich nicht erzeugt werden sollte
|
||||
- Es ist eine Art Schablone für alle Klassen von Typ `Animal`
|
||||
|
||||
Können wir die Instanziierung verhindern? Ja!
|
||||
|
||||
---
|
||||
|
||||
# Abstrakte Klassen
|
||||
|
||||
- abstrakte Klassen sind kein konkreter Bauplan für ein Objekt
|
||||
- stattdessen eine Schablone für weiter Baupläne
|
||||
- `Animal` ist so einen Schablone für alle Tiere
|
||||
|
||||
---
|
||||
|
||||
# Abstrakte Klassen - in anderen Programmiersprachen
|
||||
Beispiel Java mit `abstract`
|
||||
```java
|
||||
abstract class Animal {
|
||||
private int age;
|
||||
private float weight;
|
||||
private String name;
|
||||
|
||||
abstract void make_noise();
|
||||
}
|
||||
|
||||
class Cat extends Animal {
|
||||
@override
|
||||
void make_noise() {
|
||||
System.out.println("Meow")
|
||||
}
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
# Abstrakte Klassen - Python
|
||||
|
||||
- `ABC` aus dem Modul `abc`.
|
||||
- Ja sie mussten `abstract` wirklich abkürzen mit `ABC` (Abstract Base Class)
|
||||
- für abstrakte Methoden gibt es die Annotation `@abstractmethod` im selben Modul
|
||||
|
||||
```python
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Animal(ABC):
|
||||
age: int
|
||||
weight: float
|
||||
name: str
|
||||
|
||||
@abstractmethod
|
||||
def make_noise():
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 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?
|
||||
|
||||
---
|
||||
|
||||
# [Blatt 11](https://proglang.github.io/teaching/24ws/eidp/exercises/sheet11.pdf)
|
BIN
Tutorium/tut12/slides.pdf
Normal file
BIN
Tutorium/tut12/slides.pdf
Normal file
Binary file not shown.
250
Tutorium/tut13/README.md
Normal file
250
Tutorium/tut13/README.md
Normal file
@ -0,0 +1,250 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
theme: rose-pine
|
||||
footer: EidP 2024 - Nils Pukropp - https://git.narl.io/nvrl/eidp-2024
|
||||
style: ".columns {\r display: grid;\r grid-template-columns: repeat(2, minmax(0, 1fr));\r gap: 1rem;\r }"
|
||||
|
||||
---
|
||||
|
||||
# Tutorium 12 - 2025-01-16
|
||||
|
||||
Datenklassen, Funktionale Programmierung, Blatt 12
|
||||
|
||||
---
|
||||
|
||||
## Datenklassen - `field(..)`
|
||||
|
||||
- Wird benutzt um zusätziche Funktionalität in Datenklassen für Attribute zu ermöglichen:
|
||||
- `init=..`: ob das Attribut als Parameter in der `__init__` (Konstruktor) auftauchen soll
|
||||
- `default=..`: ob das Attribut einen Standardwert bekommen soll (nur primitive Datentypen)
|
||||
- `default_factory=..`: Falls es ein Objekt ist brauchen wir eine Factory (z.B. Konstruktor) welche das Objekt erstellt
|
||||
- `repr=..`: Ob das Attribut in der `__repr__` auftauchen soll
|
||||
|
||||
---
|
||||
|
||||
## Datenklassen - Beispiel `field(..)`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Person:
|
||||
__name: str = field(init=False, default="")
|
||||
"""das Feld taucht nicht im Konstruktor auf und wird mit "" initialisiert
|
||||
"""
|
||||
__my_hobbies: list[str] = field(init=False, default_factory=list)
|
||||
"""das Feld lässt sich nur mit einer Factory erstellen
|
||||
"""
|
||||
__secrets: list[str] = field(init=False, default_factory=list, repr=False)
|
||||
"""das Feld taucht nicht in der `__repr__` Dunder-Methode auf
|
||||
"""
|
||||
|
||||
me = Person()
|
||||
print(me) # Person(_Person__name='', _Person__my_hobbies=[])
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Datenklassen - `InitVar`
|
||||
|
||||
- `InitVar` ermöglicht einen Platzhalter zu erstellen
|
||||
- speichert nichts ab
|
||||
- wird aber als Parameter im Konstruktor übergeben
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Time:
|
||||
hours: InitVar[int]
|
||||
minutes: InitVar[int]
|
||||
__time: int = field(init=False)
|
||||
|
||||
my_time = Time(hours=4, minutes=20)
|
||||
my_time = Time(4, 20)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Datenklassen - `InitVar` und `__post_init__`
|
||||
|
||||
- Um `InitVar` zu verwenden brauchen wir die `__post_init__`
|
||||
- die `__post_init__` bekommt die `InitVar` übergeben
|
||||
- dann können wir die eigentlichen Attribute initialisieren
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Time:
|
||||
hours: InitVar[int]
|
||||
minutes: InitVar[int]
|
||||
__time: int = field(init=False)
|
||||
|
||||
def __post_init__(self, hours: int, minutes: int):
|
||||
self__time = hours * 60 + minutes
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Was ist `property`?
|
||||
|
||||
- Syntax-Sugar,
|
||||
|
||||
---
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Higher-Order Functions
|
||||
|
||||
- Eine Funktion muss eine der Eigenschaften haben:
|
||||
- nimmt eine oder mehrere `Callable` als Argument
|
||||
- gibt 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: not e is 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
|
||||
|
||||
print(list(flatten([[1, 2, 3], 4, [[5, 6], 7, [8, 9]]]))) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
```
|
||||
- nimmt weder `Callable` als Argumente
|
||||
- gibt kein `Callable` zurück
|
||||
- ist keine Higher-Order-Function
|
||||
|
||||
---
|
||||
|
||||
## 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))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
# Fragen zur funktionalen Programmierung?
|
||||
|
||||
---
|
||||
|
||||
# [Blatt 12](https://proglang.github.io/teaching/24ws/eidp/exercises/sheet12.pdf)
|
BIN
Tutorium/tut13/slides.pdf
Normal file
BIN
Tutorium/tut13/slides.pdf
Normal file
Binary file not shown.
231
Tutorium/tut14/README.md
Normal file
231
Tutorium/tut14/README.md
Normal file
@ -0,0 +1,231 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
theme: rose-pine
|
||||
footer: EidP 2024 - Nils Pukropp - https://git.narl.io/nvrl/eidp-2024
|
||||
style: ".columns {\r display: grid;\r grid-template-columns: repeat(2, minmax(0, 1fr));\r gap: 1rem;\r }"
|
||||
|
||||
---
|
||||
|
||||
# Tutorium 14 - 2025-01-23
|
||||
|
||||
Closures, Automaten, Decorator, Blatt 13
|
||||
|
||||
---
|
||||
|
||||
## Closures - Wiederholung
|
||||
|
||||
Closures sind Funktionen, bei denen Variabeln den aktuellen `Scope`, also die Umgebung der Funktion verlassen
|
||||
|
||||
Beispiel:
|
||||
|
||||
```python
|
||||
def make_dividable_by(m: int) -> Callable[[int], bool]:
|
||||
def dividable(n: int) -> bool:
|
||||
return n % m == 0
|
||||
return dividable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automaten - Was ist das?
|
||||
|
||||

|
||||
|
||||
- Mit Automaten werden endlich deterministische Automaten gemeint
|
||||
- Müssen **endlich** sein
|
||||
- Müssen **deterministisch** sein
|
||||
- Nennt man **DEA**
|
||||
|
||||
---
|
||||
|
||||
## DEA - *Etwas* formeller definiert
|
||||
|
||||
DEA sind 5-Tupel, also $\mathfrak{A} = (Q, \Sigma, \delta, q_0, F)$, wobei
|
||||
|
||||
- $Q$ die endliche Zustandsmenge ist
|
||||
- $\Sigma$ die Eingabe-Menge ist
|
||||
- $\delta : Q \times \Sigma \rightarrow Q$ die Übergangsfunktion
|
||||
- $q_0 \in Q$ der Startzustand
|
||||
- $F \subseteq Q$ die Menge der Akzeptierenden Zustände
|
||||
|
||||
---
|
||||
|
||||
## formelles Beispiel
|
||||
|
||||
- $Q := \{q_0, q_1, q_2\}$
|
||||
- $\Sigma := \{\text{Münze}, \text{Abbruch}, \text{Getränk}, \text{Entnahme}\}$
|
||||
- $\delta : Q \times \Sigma \rightarrow Q$
|
||||
- $(q_0, \text{Münze}) \mapsto q_1$
|
||||
- $(q_1, \text{Abbruch}) \mapsto q_0$
|
||||
- $(q_1, \text{Getränk}) \mapsto q_2$
|
||||
- $(q_2, \text{Entnahme}) \mapsto q_0$
|
||||
- $q_0$ Startzustand
|
||||
- $F := \{q_0\}$
|
||||
|
||||
---
|
||||
|
||||
Genau genommen ist der Beispiel-Automat nicht deterministisch, warum?
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## DEA in Python
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class Automaton[Q, S]:
|
||||
delta: Callable[[Q, S], Q]
|
||||
start: Q
|
||||
finals: frozenset[Q]
|
||||
|
||||
def accept(self, input: Iterable[S]) -> bool:
|
||||
state = self.start
|
||||
for c in input:
|
||||
state = self.delta(state, c)
|
||||
return state in self.finals
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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 - CommandDispatcher
|
||||
|
||||
```python
|
||||
class CommandDispatcher[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 - CommandDispatcher
|
||||
|
||||
```python
|
||||
class CommandDispatcher[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 = CommandDispatcher()
|
||||
|
||||
@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
|
||||
|
||||
---
|
||||
|
||||
# [Blatt 13](https://proglang.github.io/teaching/24ws/eidp/exercises/sheet13.pdf)
|
BIN
Tutorium/tut14/image-1.png
Normal file
BIN
Tutorium/tut14/image-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
Tutorium/tut14/image.png
Normal file
BIN
Tutorium/tut14/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
BIN
Tutorium/tut14/slides.pdf
Normal file
BIN
Tutorium/tut14/slides.pdf
Normal file
Binary file not shown.
15
Tutorium/tut14/src/closures.py
Normal file
15
Tutorium/tut14/src/closures.py
Normal file
@ -0,0 +1,15 @@
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def make_dividable_by(m: int) -> Callable[[int], bool]:
|
||||
def dividable(n: int) -> bool:
|
||||
return n % m == 0
|
||||
return dividable
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
dividable_by_5 = make_dividable_by(5)
|
||||
dividable_by_10 = make_dividable_by(10)
|
||||
assert dividable_by_5(15)
|
||||
assert not dividable_by_5(3)
|
||||
assert dividable_by_10(100)
|
35
Tutorium/tut14/src/decorator.py
Normal file
35
Tutorium/tut14/src/decorator.py
Normal file
@ -0,0 +1,35 @@
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class CommandDispatcher[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 = CommandDispatcher()
|
||||
|
||||
@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))
|
52
Tutorium/tut14/src/drinking_machine.py
Normal file
52
Tutorium/tut14/src/drinking_machine.py
Normal file
@ -0,0 +1,52 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Iterable
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Automaton[Q, S]:
|
||||
delta: Callable[[Q, S], Q]
|
||||
start: Q
|
||||
finals: frozenset[Q]
|
||||
|
||||
def accept(self, input: Iterable[S]) -> bool:
|
||||
state = self.start
|
||||
for c in input:
|
||||
state = self.delta(state, c)
|
||||
return state in self.finals
|
||||
|
||||
|
||||
class State(Enum):
|
||||
Q0 = auto(),
|
||||
Q1 = auto(),
|
||||
Q2 = auto(),
|
||||
TRASH = auto(),
|
||||
|
||||
|
||||
class Input(Enum):
|
||||
COIN = auto(),
|
||||
STOP = auto(),
|
||||
DRINK = auto(),
|
||||
TAKE = auto(),
|
||||
|
||||
|
||||
def delta(state: State, input: Input) -> State:
|
||||
match (state, input):
|
||||
case (State.Q0, Input.COIN):
|
||||
return State.Q1
|
||||
case (State.Q1, Input.STOP):
|
||||
return State.Q0
|
||||
case (State.Q1, Input.DRINK):
|
||||
return State.Q2
|
||||
case (State.Q2, Input.TAKE):
|
||||
return State.Q0
|
||||
case _:
|
||||
return State.TRASH
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
automata = Automaton(delta, State.Q0, frozenset([State.Q0]))
|
||||
assert automata.accept(
|
||||
[Input.COIN, Input.STOP, Input.COIN, Input.DRINK, Input.TAKE])
|
||||
assert not automata.accept(
|
||||
[Input.COIN, Input.TAKE, Input.DRINK, Input.TAKE])
|
70
Zusammenfassungen/type-annotations/type-annotations.typ
Normal file
70
Zusammenfassungen/type-annotations/type-annotations.typ
Normal file
@ -0,0 +1,70 @@
|
||||
#import "../../templates/template.typ": *
|
||||
|
||||
#show: project.with(
|
||||
title: "Type Annotations",
|
||||
subtitle: "Einführung in die Programmierung",
|
||||
logo: "unifreiburglogo.png",
|
||||
authors: (
|
||||
(name: "Nils Pukropp", email: "nils@narl.io", affiliation: "git.narl.io"),
|
||||
),
|
||||
)
|
||||
|
||||
= Vorwort
|
||||
|
||||
In dieser PDF werden alle wichtigen Grundlagen der Vorlesung #link("https://proglang.github.io/teaching.html", "Einführung in die Programmierung") zusammengefasst. Diese wurde für das WS2024/25 verfasst, andere Semester können Inhaltlich abweichen. Vor allem können neuere Python-Versionen zu anderen schreibweisen führen.
|
||||
|
||||
= Wie wird in Python programmiert?
|
||||
|
||||
Um in Python zu Programmieren wird das Programm Python benötigt um den geschriebenen Quelltext zu interpretieren. Wie Python installiert wird ist von Semester zu Semester unterschiedlich (siehe #link("https://proglang.github.io/teaching/24ws/eidp/setup.html", "WS24/25")). Machen Sie sich mit Python vertraut, dazu gehört:
|
||||
|
||||
- Python-Dateien erstellen und ausführen
|
||||
- Python-Shell im Terminal verwenden
|
||||
|
||||
Es gibt genügend Material online. Ich gehe hier trotzdem auf die beiden Modi ein, am Ende muss man aber einfach beim Üben sich mit den Tools vertraut machen. Dies kann man kaum textuell ohne Ausprobieren verstehen, gerade weil für viele wahrscheinlich das Terminal völlig neu ist.
|
||||
|
||||
== Python-Dateien erstellen
|
||||
|
||||
- Python wird in `*.py` Dateien geschrieben.
|
||||
- Zum ausführen haben wir entweder das Programm `python3` im Terminal, oder einen "Run"-Knopf in unserer bevorzugten IDE (vscode, pycharm, ...)
|
||||
- In die Python-Datei wird der Quelltext geschrieben, auf die Strukturen gehen wir später ein.
|
||||
- Zum ausführen im Terminal wird der Kommand `python3 <name der python Datei>.py` benutzt
|
||||
|
||||
== Python-Shell
|
||||
|
||||
Eine Ausnahme dazu bietet die *Python-Shell*, diese ermöglicht das Zeile für Zeile schreiben von Python.
|
||||
|
||||
- Wenn wir `python3` ohne weitere Argumente im Terminal eingeben startet Python im interaktiven Modus.
|
||||
- Wird signalisiert durch `>>>` im Terminal, hier können wir dann unseren Quelltext direkt schreiben und Zeile für Zeile direkt ausführen. Hier z.B. starten wir die Python-Shell. Das `$` signalisiert was in das Terminal auf Linux geschrieben wurde, alles danach ist die Python-Shell. Wir geben `print("Hello Python-Shell")` ein und bekommen auch direkt die Antwort `Hello Python-Shell`.
|
||||
|
||||
```python
|
||||
$ python3
|
||||
Python 3.13.1 (main, Dec 4 2024, 18:05:56) [GCC 14.2.1 20240910] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> print("Hello Python-Shell")
|
||||
Hello Python-Shell
|
||||
>>>
|
||||
```
|
||||
|
||||
= Woraus besteht Python?
|
||||
|
||||
Python besteht aus *Keywords* und den bekannten arithmetischen Operatoren die wir aus der Mathematik kennen. Diese Keywords sind nicht einmal besonders viele, aber dadurch lassen sich fast alle Programme schreiben.
|
||||
|
||||
== Keywords in Python
|
||||
|
||||
=== Arithmetische Operatoren
|
||||
|
||||
- Addition: `a + b`
|
||||
- Subtraktion: `a - b`
|
||||
- Multiplikation: `a * b`
|
||||
- Division: `a / b`
|
||||
- Modulo: `a % b`
|
||||
- Ganzzahl Divison: `a // b`
|
||||
- Potenzen: `a ** b`
|
||||
|
||||
=== Vergleich Operatoren
|
||||
|
||||
Neben den typischen arithmetischen Operatoren aus der Mathematik brauchen wir auch noch aussagenlogische Operatoren, um Werte und Zustände zu vergleichen und so den Programmfluss zu beschreiben.
|
||||
|
||||
- Äquivalenz ist etwas schwieriger, denn diese unterscheidet sich manchmal von Paradigmen zu Paradigmen. Aber für den Anfang sagen wir einfach wenn etwas gleich ist, dann ist es äquivalent: `a == b`
|
||||
- Kleiner/Größer als: `a > b`, `a < b`
|
||||
- Kleiner/Größer-Gleich als: `a >= b`, `a <= b`
|
60
templates/template.typ
Normal file
60
templates/template.typ
Normal file
@ -0,0 +1,60 @@
|
||||
#import "./typst-catppuccin/src/lib.typ": catppuccin, flavors, mocha, get-flavor
|
||||
|
||||
|
||||
// The project function defines how your document looks.
|
||||
// It takes your content and some metadata and formats it.
|
||||
// Go ahead and customize it to your liking!
|
||||
#let flavor = sys.inputs.at("flavor", default: flavors.mocha.identifier)
|
||||
#let palette = get-flavor(flavor)
|
||||
#let project(title: "", subtitle: "", authors: (), logo: none, body) = {
|
||||
show: catppuccin.with(flavors.mocha, code-block: true, code-syntax: true)
|
||||
// Set the document's basic properties.
|
||||
set document(author: authors.map(a => a.name), title: title)
|
||||
set page(numbering: "1", number-align: center)
|
||||
set text(font: "Nunito", lang: "en")
|
||||
set heading(numbering: none)
|
||||
|
||||
show heading: it => [
|
||||
#set align(left)
|
||||
#set text(18pt, fill: palette.colors.mauve.rgb)
|
||||
#block(it.body)
|
||||
#v(0.5em)
|
||||
]
|
||||
// Title page.
|
||||
// The page can contain a logo if you pass one with `logo: "logo.png"`.
|
||||
v(0.6fr)
|
||||
if logo != none {
|
||||
align(right, image(logo, width: 50%))
|
||||
}
|
||||
v(9.6fr)
|
||||
|
||||
|
||||
text(2em, weight: 700, title, fill: palette.colors.red.rgb)
|
||||
v(0.5fr)
|
||||
text(1.2em, weight: 700, subtitle, fill: palette.colors.mauve.rgb)
|
||||
|
||||
// Author information.
|
||||
pad(
|
||||
top: 0.7em,
|
||||
right: 20%,
|
||||
grid(
|
||||
columns: (1fr,) * calc.min(3, authors.len()),
|
||||
gutter: 1em,
|
||||
..authors.map(author => align(start)[
|
||||
*#author.name* \
|
||||
#author.affiliation \
|
||||
#author.email
|
||||
]),
|
||||
),
|
||||
)
|
||||
|
||||
v(2.4fr)
|
||||
pagebreak()
|
||||
|
||||
|
||||
// Main body.
|
||||
set par(justify: true)
|
||||
|
||||
body
|
||||
}
|
||||
|
1
templates/typst-catppuccin
Submodule
1
templates/typst-catppuccin
Submodule
Submodule templates/typst-catppuccin added at 31e6c45147
BIN
templates/unifreiburglogo.png
Normal file
BIN
templates/unifreiburglogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 477 KiB |
Reference in New Issue
Block a user