#import "../../templates/template.typ": *

#show: project.with(
  title: "Zusammenfassung",
  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`

=== Datentypen

Um Daten zu beschreiben müssen wir diesen auch gewisse Typen zuordnen, welche deren Verhalten/Zustände beschreiben.

Nehmen wir hierfür die bekannten Mengendefinitionen der Zahlen in der Mathematik. Also z.B. die natürlichen Zahlen $NN$ und die rationalen Zahlen $RR$. Wir wissen dass die natürlichen Zahlen $NN$ in die rationalen Zahlen $RR$ _reinpassen_.

In Python stellen wir die natürlichen Zahlen $NN$ als `int` da. Die ratinoalen Zahlen $RR$ als `float`. Für uns ist `1 + 1 = 2` ziemlich eindeutig und uns ist egal ob jetzt mit `1` oder `2` eine rationale oder natürliche Zahl gemeint ist. Bei Programmiersprachen müssen wir aber genauer sein. Also ist `1` ein `int` und `1.0` eine `float`. 

Deswegen muss man jetzt aufpassen was passiert welche Datentypen man zusammenrechnet.

$
  underbrace(underbrace(underbrace(6, "int") * underbrace("round(2.1, 1)", "float"), "float") " // " underbrace(2, "int"), "float, also 6.0") 
$

Hier wandelt Python einen `int` direkt um zu einem `float`, da wir ja wissen dass $NN subset RR$ ist. Genauso hat aber alles einen Datentypen. Auch die Funktionen die wir schreiben.

=== Typannotationen

Um beim Schreiben von Python-Skripten die Datentypen zu veranschaulichen und besser lesbar zu machen, schreiben wir Typannotation. Hierbei ist nicht alles verpflichtend, sondern nur an den Stellen, an welchen der Datentyp nicht direkt offensichtlich ist.

==== Variabeln

Variabeln können eine Typannotation haben, ist aber eher offensichtlich und daher unnötig

```py
x: int = 1
```

==== Funktionen

Funktionen, oder genauer der Funktionskopf/Funktionssignatur benötigt eine Typannotation. Woher sollte man sonst wissen welche Parameter man verwendet oder was einem diese Funktion zurückgibt?

```py
def add(a: int, b: int) -> int:
    return a + b
```

==== Klassen

Datenklassen benötigen sowieso Typannotation da der Code sonst gar nicht funktioniert.

```py
from dataclasses import dataclass


@dataclass
class Vec2D:
    x: float
    y: float
```

Mit einer Klasse erstellen wir einen neuen Bauplan, sprich `Vec2D` ist jetzt auch ein Datentyp den wir verwenden können.

==== Methoden

Methoden sind die Funktionen der Objekte, es heißt Methode weil das Verhalten von einem Objekt modelliert wird. Genau wie Funktionen brauchen wir eine Typannotation. Mit dem kleinen Unterschied dass `self` keine braucht, da wir ja wissen was `self` ist.

```py
from dataclasses import dataclass
from math import sqrt


@dataclass
class Vec2D:
    x: float
    y: float

    def abs(self) -> float:
        return sqrt(self.x ** 2 + self.y ** 2)

    def __eq__(self, other: 'Vec2D') -> bool:
        if not isinstance(other, Vec2D):
            return False
        return self.x == other.x and self.y == other.y
```

==== Listen, Dictionary, Tuple

Listen sind, wer hätte es gedacht, Listen aus Objekten die auch wieder Datentypen haben. Das müssen wir bei der Typannotation berücksichtigen.

```py
my_list: list[str] = ["hello", "world"]
```

Dictionary bestehen immer aus Keys mit Datentypen und Values mit Datentypen. Deswegen müssen wir auch beides bei der Definition angeben.

```py
my_dict: dict[int, str] = { 1: "hello", 2: "world" }
```
Tupel beschreiben eine endliche Aufzählung von Dingen, also kann nicht größer oder kleiner werden. Deshalb müssen wir jedes einzelne Element einen Datentypen zuordnen.

```py
my_tuple: tuple[str, str, int, float] = ("hello", "world", 69, 420.0)
```

==== Lambda

Von Lambda würde ich eher abraten wenn man nicht genau weiß was man tut, aber auch Funktionen haben einen Datentyp und muss verwendet werden.

```py
add: Callable[[int, int], int] = lambda a, b: a + b
```

Genauso haben aber Funktionen die mit `def` geschrieben wurden denselben Datentyp.

```py

def add(a: int, b: int) -> int:
    return a + b

add_2: Callable[[int, int], int] = add

```