- Aktuelle Version: Python 3.13
- Interpretierte Programmiersprache
- In der Standardversion `cpython`: kein Kompilieren zu Maschinencode, sondern zu Bytecode
- Programme werden mit dem `python`-Programm ausgeführt
- Eignet sich sehr gut zum Erlernen der Programmierung!
- Viele nützliche Pakete, die das wissenschaftliche Arbeiten extrem erleichtern!

Hier soll es bis Mittwoch hingehen; Auswertung von Praktikumsversuchen:

In [1]:

    from IPython.display import Image

    Image(filename="build/muon_plot.png")

Out[1]:

Versuch aus dem Fortgeschrittenen-Praktikum: Lebensdauer kosmischer Myonen

In [2]:

    %%javascript
    $.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')

# Inhalt¶

# Grundlagen¶

Eine Programmiersprache wie Python lässt sich nicht an einem Tag lernen. Wir geben hier einen kurzen Überblick über die wichtigsten Konzepte.

Keine Angst: Zum Erstellen von Plots und für einfache Auswertungen musst du nicht viel Python können!

Alle folgenden Beispiele kann man auf der Kommandozeile ausprobieren.

Dazu zunächst die virtuelle Python Umgebung mit

    mamba activate toolbox

aktiveren.

Anschließend die `IPython`-Kommandozeile mit

    ipython

starten, und dort Beispiele ausprobieren.

## Einfachstes Beispiel¶

In [3]:

    print("Hello, World!")

    Hello, World!

## Kommentare¶

- Sehr wichtig für die Leute, die mit euch arbeiten
- Auch für euch selbst zwei Wochen später

Alles was in Python hinter einem `#` steht, wird nicht als Code betrachtet und dient als Kommentar. Längere Kommentare, streng Docstrings, werden mit drei Anführungszeichen begonnen und mit drei weiteren beendet.

In [4]:

    """
    We start the workshop with the traditional first program.
    """
    print("Hello, World!")
    # This example is chosen to show the basic functionalities of python.

    Hello, World!

Kommentare sollten nicht erklären, was der Code tut, sondern warum. Wie jeder andere Code, sollten Kommentare auf Englisch sein.

# Auslagern in Python-Dateien¶

- Python-Dateien geben wir die Endung `.py`
- Ausführen mit `python programm.py`
- Die Datei muss mit dem Encoding UTF-8 abgespeichert werden. (Standard in modernen Editoren / Betriebssystemen)

Exportieren wir als Beispiel unseren "Hello World!" Code in eine Datei.

# Variablen¶

Man kann einem Objekt einen Namen geben, dazu kennen wir aus der Mathematik:

In [5]:

    a = 2
    b = 3

Variablennamen können auch überschrieben werden:

In [6]:

    a = 42
    b = a

    a = 0
    b

Out[6]:

    42

- Namen verweisen auf Objekte
- Mehrere Namen können auf das gleiche Objekt zeigen

# Datentypen¶

## Einfache Datentypen¶

### Booleans¶

`True` und `False`

Logische Operatoren: `and`, `or`, `not`

In [7]:

    True and False

Out[7]:

    False

In [8]:

    True or False

Out[8]:

    True

In [9]:

    not True

Out[9]:

    False

`None` oder leere Objekte verhalten sich wie `False`, andere wie `True`

In [10]:

    "" or True

Out[10]:

    True

In [11]:

    "Hallo" and True

Out[11]:

    True

### Zahlen¶

Ganze Zahlen: `42`
Kommazahlen: `3.14`
Komplexe Zahlen: `3 + 2j`

Rechenoperatoren:

- `+`, `-`, `*`
- `**` (Potenzieren: `2**3` → `8`)
- `/` (`3 / 2` → `1.5`)
- `//` (Ganzzahldivision: `3 // 2` → `1`)
- `%` (Modulo-Operation, Divisionsrest: `7 % 4` → `3`)

In [12]:

    x = 5
    y = 3
    x + y

Out[12]:

    8

Rechenoperationen können geklammert werden

In [13]:

    a = 2 + 4 / 5 + 1
    b = (2 + 4) / 5 + 1
    c = (2 + 4) / (5 + 1)

In [14]:

    print(a, b, c)

    3.8 2.2 1.0

In [15]:

    a = 3 + 2j
    b = 1 + 1j

    print(a + b, a * b, abs(b))

    (4+3j) (1+5j) 1.4142135623730951

Vergleichsoperatoren geben `True` oder `False` zurück

- `==`, `!=`
- `>`, `<`, `>=`, `<=`

In [16]:

    x = 5
    y = 3
    x < y

Out[16]:

    False

Mehrere Vergleichsoperatoren können in einem Ausdruck stehen:

In [17]:

    a = 3.8
    b = 2.2
    c = 1
    c < b < a

Out[17]:

    True

In [18]:

    c < a < b

Out[18]:

    False

### Kurzaufgaben 1, 2 und 3¶

1. Definiere die folgenden Variablen mit den angegebenen Werten

- Variable $t$, Wert: $0.5$s
- Variable $h_i$, Wert: $10$m
- Variable $v_i$, Wert: $0.75$m/s
- Variable $a_i$, Wert: $-9.81$m/s²

2. Berechne mit den obigen Werten die Höhe $h$, Geschwindigkeit $v$ und Beschleunigung $a$ zum Zeitpunkt $t=0.5$s eines fallenden Objekts. Speicher die Ergebnisse in entsprechenden Variablen $h$, $v$, $a$.

3. Prüfe, ob das Objekt bereits über einen Meter gefallen ist.

Die Lösungen für die Kurzaufgaben sind am Ende dieser Datei oder in der `loesung.py` in `exercises-toolbox/1-python/Kurzaufgaben`.

### Strings¶

Stehen in `'` oder `"`. (Für eins entscheiden)

In [19]:

    foo = "foo"
    bar = "bar"

Strings können mit `+` konkateniert werden:

In [20]:

    foo + " " + bar

Out[20]:

    'foo bar'

Strings können mit `*` vervielfacht werden

In [21]:

    foo * 4

Out[21]:

    'foofoofoofoo'

Strings können mit `[]` indiziert werden

In [22]:

    foo[0]

Out[22]:

    'f'

Können beliebige Unicode Symbole enthalten

In [23]:

    print("π ist genau 3, Erwin Schrödinger")

    π ist genau 3, Erwin Schrödinger

### None¶

Stellt das Fehlen eines Wertes dar.

Wird häufig für Standardwerte von Funktionen verwendet.

In [24]:

    print(None)

    None

Funktionen, die 'nichts' zurückgeben, geben `None` zurück:

In [25]:

    print("test")

    test

In [26]:

    a = print("test")

    test

In [27]:

    print(a)

    None

### Kurzaufgaben 4¶

4. Nutze die `print`-Funktion, um die gegebenen Startwerte und die Ergebnisse der letzten Kurzaufgabe mit Überschriften zu versehen und auszugeben.

## Sammlungen (Collections)¶

Speichern mehrerer Objekte

### Listen¶

- Veränderbar
- Gedacht für mehrere Werte vom selben Typ

In [28]:

    names = ["foo", "bar"]

Listen können mit `[]` indiziert werden

In [29]:

    names[1]

Out[29]:

    'bar'

Man kann Werte an Listen anhängen:

In [30]:

    names.append("baz")
    names

Out[30]:

    ['foo', 'bar', 'baz']

Negative Indizes (beginnend mit `-1`), indizieren die Liste rückwärts

In [31]:

    names[-1]

Out[31]:

    'baz'

Man kann auch Teillisten indizieren.
`names[Anfang:Ende]` liefert eine Teilliste mit den Werten von `names[Anfang]` bis `names[Ende-1]`:

In [32]:

    names[1:3]

Out[32]:

    ['bar', 'baz']

Listen werden mit `+` zusammengeführt:

In [33]:

    names + ["thing"]

Out[33]:

    ['foo', 'bar', 'baz', 'thing']

Listen können mit `*` vervielfacht werden:

In [34]:

    names * 3

Out[34]:

    ['foo', 'bar', 'baz', 'foo', 'bar', 'baz', 'foo', 'bar', 'baz']

Die Liste kann auch erweitert werden:

In [35]:

    names.extend(["quux", "quem"])
    names

Out[35]:

    ['foo', 'bar', 'baz', 'quux', 'quem']

Listen sind veränderbar, man kann ihnen Werte zuweisen:

In [36]:

    names[1] = "new"
    names

Out[36]:

    ['foo', 'new', 'baz', 'quux', 'quem']

Mit `in` wird überprüft, ob ein Wert in der Liste ist:

In [37]:

    weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
    "Mo" in weekdays

Out[37]:

    True

### Tupel¶

- Gedacht für Werte mit unterschiedlichen Typen
- Werden an allen möglichen Stellen von python erzeugt

In [38]:

    tup = (5, 3)
    tup

Out[38]:

    (5, 3)

Können wahlweise auch ohne Klammern geschrieben werden (wenn es eindeutig ist).

In [39]:

    tup = 5, 3
    tup

Out[39]:

    (5, 3)

In [40]:

    a = 5
    b = 3
    a, b = b, a

In [41]:

    print("a =", a)
    print("b =", b)

    a = 3
    b = 5

In [42]:

    tup[0]

Out[42]:

    5

Einträge nach Erzeugung unveränderbar:

In [43]:

    tup[1] = 7

    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    Cell In[43], line 1
    ----> 1 tup[1] = 7

    TypeError: 'tuple' object does not support item assignment

### Kurzaufgaben 5¶

5. Definiere eine Liste mit Namen T, die die 3 Werte 0.5, 1 und 1.5 enthält. Definiere drei leere Listen mit den Namen H, V und A. Berechne dann für jede der drei Zeiten wie zuvor die Höhe, Geschwindigkeit und Strecke, jedoch indem du

       a) die Werte aus der Liste T verwendest (T[i] für das entsprechende i),
       b) die Ergebnisse an die zugehörige Liste anhängst.

   Gib die Listen H, V, A mit der `print`-Funktion aus.

### Dictionaries¶

In [44]:

    numbers = {"one": 1, "two": 2, "three": 3}

In [45]:

    numbers["two"]

Out[45]:

    2

Dictionaries sind extrem hilfreich, zum Beispiel um die verschiedene Datensätze der gleichen Messung zusammenzufassen. So wird es einfacher dieselbe Analyse auf die einzelnen Datensätze anzuwenden.

In [46]:

    data = {"Cu": [1.1, 1.2, 1.3, 1.4], "Fe": [0.7, 0.8, 0.9, 1.0]}
    data["Cu"]

Out[46]:

    [1.1, 1.2, 1.3, 1.4]

Leere Dictionaries werden mit der Funktion `dict()`erstellt.

In [47]:

    data = dict()
    data

Out[47]:

    {}

In [48]:

    data["Fe"] = [1.1, 1.2, 1.3, 1.4]
    data

Out[48]:

    {'Fe': [1.1, 1.2, 1.3, 1.4]}

In [49]:

    data["Cu"] = [0.7, 0.8, 0.9, 1.0]
    data

Out[49]:

    {'Fe': [1.1, 1.2, 1.3, 1.4], 'Cu': [0.7, 0.8, 0.9, 1.0]}

In [50]:

    data["Au"] = [0.2, 0.3, 0.1, 0.5]
    data

Out[50]:

    {'Fe': [1.1, 1.2, 1.3, 1.4],
     'Cu': [0.7, 0.8, 0.9, 1.0],
     'Au': [0.2, 0.3, 0.1, 0.5]}

### Kurzaufgaben 6 und 7¶

6. Speichere die zuvor erstellten Listen in einem Dictionary mit dem Namen data und den keys t, h, v und a. Gib das Dictionary mit der `print`-Funktion aus.

7. Greife auf die folgenden Werte im Dictionary zu:

- der zweite Wert von t
- der erste Wert von v
- der letzte Wert von h

# Kontrollstrukturen¶

Die Zeilen eines Python-Programms werden nacheinander ausgeführt.

Sogennante Kontrollstrukturen erlauben es, den Ablauf zu steuern.

## `if` / `elif` / `else`¶

In [51]:

    a = 3

    if a == 1:
        # muss eingerückt werden, 4 spaces:
        print("foo")
    elif a == 2:
        print("bar")
    else:
        print("baz")

    baz

## `while`¶

Wiederholen, solange eine Bedingung erfüllt ist.

In [52]:

    i = 0
    while i < 5:
        print(i)
        i += 1

    # there has to be a better way!

    0
    1
    2
    3
    4

## `for`¶

Wiederholen für jedes Elements (z.B. einer Liste)

In [53]:

    data = [10, 42, -1]

    for x in data:
        # Wir können auf das jetzige Element als "x" zugreifen
        print(2 * x)

    20
    84
    -2

klassische for-Schleife mit `range`:

In [54]:

    for i in range(5):
        print(i)

    0
    1
    2
    3
    4

In [55]:

    for i in range(2, 5):
        print(i)

    2
    3
    4

In [56]:

    for i in range(2, 10, 2):
        print(i)

    2
    4
    6
    8

In [57]:

    for i in range(10, 3, -1):
        print(i)

    10
    9
    8
    7
    6
    5
    4

In [58]:

    weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
    for day in weekdays:
        print("Heute ist", day)

    Heute ist Mo
    Heute ist Di
    Heute ist Mi
    Heute ist Do
    Heute ist Fr
    Heute ist Sa
    Heute ist So

Zwei sehr nützliche Funktionen sind `enumerate` und `zip`

In [59]:

    for i, day in enumerate(weekdays):
        print(day, " ist der ", i + 1, ". Tag der Woche", sep="")

    Mo ist der 1. Tag der Woche
    Di ist der 2. Tag der Woche
    Mi ist der 3. Tag der Woche
    Do ist der 4. Tag der Woche
    Fr ist der 5. Tag der Woche
    Sa ist der 6. Tag der Woche
    So ist der 7. Tag der Woche

Was `enumerate()` macht:

In [60]:

    list(enumerate(weekdays))

Out[60]:

    [(0, 'Mo'), (1, 'Di'), (2, 'Mi'), (3, 'Do'), (4, 'Fr'), (5, 'Sa'), (6, 'So')]

In [61]:

    voltages = [1, 3, 5]
    currents = [1, 5, 1]

    for voltage, current in zip(voltages, currents):
        print("R =", voltage / current, "Ω")

    R = 1.0 Ω
    R = 0.6 Ω
    R = 5.0 Ω

Was `zip()` macht:

In [62]:

    list(zip(voltages, currents))

Out[62]:

    [(1, 1), (3, 5), (5, 1)]

Iterieren über die Einträge von `dict`

In [63]:

    translations = {
        "foot": "Fuß",
        "ball": "Ball",
        "goal": "Tor",
    }

    for e, g in translations.items():
        print(e, g)

    foot Fuß
    ball Ball
    goal Tor

Übungen 1, 2, und 3 können jetzt bearbeitet werden.

# Funktionen¶

Mit Funktionen lässt sich Code leicht wiederverwenden. Eine Funktion nimmt Parameter, verarbeitet sie und gibt Ergebnisse zurück.

In [64]:

    print("Hello!")

    Hello!

In [65]:

    len([1, 2, 3, 4, 5])

Out[65]:

    5

Viele Funktionen haben mehrere Parameter, einige können sogar beliebig viele haben:

In [66]:

    print(1, 2, 3, 4, 5)

    1 2 3 4 5

Viele Funktionen haben optionale Parameter mit eigenem Namen, sogenannte "keyword arguments":

In [67]:

    print(1, 2, 3, 4, 5, sep=", ")

    1, 2, 3, 4, 5

Objekte haben auch Funktionen (diese nennt man Methoden):

In [68]:

    s = "test"
    s.upper()

Out[68]:

    'TEST'

In [69]:

    "foo bar baz".split()

Out[69]:

    ['foo', 'bar', 'baz']

In IPython kann man wie folgt auf die Dokumentation einer Funktion zugreifen:

In [70]:

    print?

Ansonsten findet man ausführliche Erklärungen in der offiziellen Dokumentation.

## Eigene Funktionen definieren mit `def`¶

Um mehrfach benötigten Code einfacher zu nutzen, können Funktionen definiert werden.

Vorteile:

- Weniger Code
- Änderungen nur an einer Stelle notwendig

In [71]:

    def add(x, y):
        z = x + y
        return z

    add(2, 2)

Out[71]:

    4

Mit dem `return` Statement wird die Funktion beendet und Ergebnisse zurückgeben.

Es können auch mehrere Werte zurückgeben werden:

In [72]:

    def divide(x, y):
        return x // y, x % y

    n, rest = divide(5, 3)
    n, rest

Out[72]:

    (1, 2)

Funktionen können sich selbst aufrufen (Rekursion)

In [73]:

    def factorial(n):
        if n == 0:
            return 1
        else:
            return n * factorial(n - 1)

    factorial(4)

Out[73]:

    24

### Kurzaufgaben 8 und 9¶

8. Definiere jeweils eine Funktion für die Berechnung von h, v und a zu einem beliebigen Zeitpunkt t.

9. Erstelle eine neue Liste T mit den Werten 0.25, 0.5, 0.75, 1.0, 1.25 und 1.5. Und erstelle das Dictionary data erneut für alle Zeitpunkte in T. Verwende dafür die in 8. erstellten Funktionen.

Zusatz: Du kannst auch die Listen H, V und A neu erstellen und dafür eine Schleife verwenden.

Gib data mit der `print`-Funktion aus.

## Module und `import`¶

In Python kann man Code in einzelne Module portionieren. Es stehen bereits unglaublich viele Module für Python zur Verfügung. Und gerade für das wissenschaftliche Arbeiten ist eigentlich alles da, was gebraucht wird und noch viel mehr.

Wird etwas aus einem anderen Modul gebraucht, importiert man es.

Zahlreiche Module sind Teil jeder Python Installation, die sogenannte "standard library": https://docs.python.org/3/library/

Weitere Module hast du in deiner `toolbox` Umgebung mit Mamba installiert.

In [74]:

    import os

    os.listdir(".")

Out[74]:

    ['.gitignore',
     'Makefile',
     'README.md',
     'data',
     'header-matplotlib.tex',
     'images',
     'matplotlib.ipynb',
     'matplotlibrc_muon',
     'python.ipynb',
     'uncertainties.ipynb',
     'exp.txt',
     'exp_polyfit.ipynb',
     'muon_plot.py',
     'numeric-python.ipynb',
     'scientific-python.ipynb',
     'build',
     'plot.pdf',
     'plot_1.pdf',
     'plot_2.pdf',
     'test.txt']

Für das Importieren einzelner Funktionen gibt es den Befehl:

In [75]:

    from time import sleep

    print("hello")
    sleep(1)
    print("world")

    hello

    world

Den Modulen können mit `as` neue Namen gegeben werden. Viele Module haben jedoch Konventionen für den Kurznamen, die du verwenden solltest.

In [76]:

    import numpy as np

    np.sum([1, 2])  # siehe numpy Kurs

Out[76]:

    np.int64(3)

# Auslagern in Python-Dateien Part II¶

Erinnerung an Part I

- Python-Dateien geben wir die Endung `.py`
- Ausführen mit `python programm.py`
- Die Datei muss mit dem Encoding UTF-8 abgespeichert werden. (Standard in modernen Editoren / Betriebssystemen)

Eigene Module

- Einzelne Python-Dateien (Endung `.py`) kann man als Module benutzen.
  Liegt eine Datei `test.py` im selben Ordner, so kann der Inhalt mit `import test` in einer weiteren Datei verwendet werden.

Es folgt ein fertiges Python-Programm.

Speicher es als `primes.py` ab und starte es aus dem Terminal mit `python primes.py`.

In [77]:

    def primes(max_num):
        # Only primes smaller than max_num are calculated
        is_prime = max_num * [True]
        is_prime[0] = False
        is_prime[1] = False

        primes = []

        # Sieve of Erathosthenes:
        for i in range(2, max_num):
            if is_prime[i]:
                for j in range(2 * i, max_num, i):
                    # Multiples are not primes
                    is_prime[j] = False
                primes.append(i)

        return primes

    print(primes(100))

    [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

Übung 4, 5 und 6 können bearbeitet werden.

# Formatieren von Strings¶

## f-strings¶

- https://docs.python.org/3/reference/lexical_analysis.html#f-strings
- https://docs.python.org/3/library/string.html#formatspec

Oft will man Werte oder andere Strings in einen String einsetzen.

Seit der Python Version 3.6 gibt es sogenannte f-strings (formatted strings) für String-Formatierung:

In [78]:

    U = 42
    R = 7

    print(f"Spannung U = {U} V, R = {R} Ω, I = {U / R} A")

    Spannung U = 42 V, R = 7 Ω, I = 6.0 A

Der Ausdruck in den `{}`-Markierungen wird ausgewertet und das Ergebnis anstelle der geschweiften Klammern gesetzt.
Variablen ersetzt.

f-strings haben viele fortgeschrittene Funktionen. Beispielsweise kann die gewünschte Genauigkeit für `floats` angeben werden:

In [79]:

    result = 3.2291421

    print(f"Das Ergebnis ist {result:.2f}")

    Das Ergebnis ist 3.23

Alle Formateinstellungen werden nach einem Doppelpunkt angegeben.
Für die Genauigkeit sieht die Angabe allgemein so aus: `:.Nf`
`N` ist dabei die gewünschte Anzahl an Nachkommastellen.

## Sonderzeichen¶

Mit `\` kann man in Strings besondere Anweisungen setzen:

- `'\n'` -> Zeilenumbruch
- `'\t'` -> Tab
- `'\\'` -> normales `'\'`

Wenn viele `'\'` geschrieben werden müssen (z.B. in LaTeX-Code), lohnt es sich diese Funktion mit dem Präfix `r` auszuschalten:

In [80]:

    print(r"\Huge\texttt{Python}")

    \Huge\texttt{Python}

Will man `{` oder `}` im Text stehen haben (für LaTeX benötigt) geht das mit doppelten `{{` oder `}}`.

LaTeX sieht dann leider so aus:

In [81]:

    energy = 1.23456
    print(rf"\qty{{{energy:.4f}}}{{\kilo\joule}}")

    \qty{1.2346}{\kilo\joule}

Nur zur Demonstration einmal mit mehr Lücken (funktioniert aufgrund der Leerzeichen so nicht in LaTeX!):

In [82]:

    print(rf"\qty {{ {energy:.4f} }} {{ \kilo\joule }}")

    \qty { 1.2346 } { \kilo\joule }

### Kurzaufgaben 10¶

Nutze eine Schleife über das Dictionary data, um die Daten in Tabellenform auszugeben.

    t  0.25  0.50  0.75  1.00  1.25  1.50
    h  9.88  9.15  7.80  5.84  3.27  0.09
    v  -2.27  -4.53  -6.79  -9.06  -11.33  -13.59
    a  -9.81  -9.81  -9.81  -9.81  -9.81  -9.81

Jede Zeile der Tabelle kann dabei durch Verwendung eines f-Strings formatiert werden.

# Comprehensions¶

Sind nützlich, um Listen oder Dictionaries umzuwandeln oder zu erzeugen:

In [83]:

    # Ursprüngliche Liste
    list(range(5))

Out[83]:

    [0, 1, 2, 3, 4]

In [84]:

    # List-Comprehension
    [2 * x for x in range(5)]

Out[84]:

    [0, 2, 4, 6, 8]

In [85]:

    # List-Comprehension mit Bedingung
    [2 * x for x in range(5) if x % 2 == 0]

Out[85]:

    [0, 4, 8]

In [86]:

    # Ursprüngliche Liste
    weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]

In [87]:

    # Dict-Comprehension
    {num + 1: name for num, name in enumerate(weekdays)}

Out[87]:

    {1: 'Mo', 2: 'Di', 3: 'Mi', 4: 'Do', 5: 'Fr', 6: 'Sa', 7: 'So'}

In [88]:

    # Ursprüngliche Listen
    print(list(range(3)))
    print(list(range(4)))

    [0, 1, 2]
    [0, 1, 2, 3]

In [89]:

    # List-Comprehension mit verschachtelten Schleifen
    [x + y for y in range(3) for x in range(4)]

Out[89]:

    [0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5]

In [90]:

    # List-Comprehension mit verschachtelten Schleifen und Bedingung
    [x + y for y in range(3) for x in range(4) if x % 2 == 0]

Out[90]:

    [0, 2, 1, 3, 2, 4]

Übung 7 kann jetzt bearbeitet werden.

# Häufig auftretende Fehler¶

Einige Fehler werden zu Anfang sehr häufig gemacht (und immer mal wieder).
Wenn man die Fehlermeldungen richtig zu lesen weiß, kann einem das viel Arbeit/Frust ersparen.
Das Wichtigste ist: Lies die Fehlermeldung und versuche sie zu verstehen!

## Gerade am Anfang beliebt: `IndentationError`:¶

Die Fehlermeldung gibt die Zeile an, in der der Fehler auftritt (hier: `line 8`)
und den Grund für den Fehler, häufig mit Erklärung (hier: `IndentationError: expected an indented block after 'if' statement on line 6`).

In [91]:

    a = 5
    b = 7
    c = 9

    if a <= b:
        if c >= b:
        # Zeile 8 müsste in dem if-Block eingerückt werden
        print("a <= b und c >= b")
        else:
            print("a <= b und b > c")
    else:
        print("a > b")

      Cell In[91], line 8
        print("a <= b und c >= b")
        ^
    IndentationError: expected an indented block after 'if' statement on line 6

## Allgemeiner Syntax-Fehler: `SyntaxError`¶

Zeile, in der der Fehler auftritt: `line 6`
Grund für den Fehler: `SyntaxError: expected ':'`
Hier sogar mit Hinweis auf das fehlerhafte/fehlende Zeichen.

In [92]:

    a = 5
    b = 7
    c = 9

    # In Zeile 6 fehlte der Doppelpunkt ':' nach der Bedingung, um den If-Block einzuleiten
    if a <= b
        if c >= b:
            print("a <= b und c >= b")
        else:
            print("a <= b und b > c")
    else:
        print("a > b")

      Cell In[92], line 6
        if a <= b
                 ^
    SyntaxError: expected ':'

## Klammerfehler¶

Zeile, in der der Fehler auftritt: `line 10`
Grund für den Fehler: `SyntaxError: '(' was never closed`
Hier sogar mit Hinweis auf die Klammer, die keinen Partner hat.

In [93]:

    a = 5
    b = 7
    c = 9

    if a <= b:
        if c >= b:
            # In Zeile 10 fehlt eine Klammer, zum Vergleich:
    #       d = ((a + b) / (a + c))/((a + b) / (a + c))
            d = ((a + b) / (a + c)/((a + b) / (a + c))
            print("a <= b und c >= b")
        else:
            print("a <= b und b > c")
    else:
        print("a > b")

      Cell In[93], line 10
        d = ((a + b) / (a + c)/((a + b) / (a + c))
            ^
    SyntaxError: '(' was never closed

## Auch sehr einfach auszulösen: `NameError`:¶

Zeile in der der Fehler auftritt: `----> 8 if a <= n:`
Grund für den Fehler: `NameError: name 'n' is not defined` Wir können keine Variable verwenden, die nicht existiert.

In [94]:

    del n  # Please ignore me, I'm just setting the stage for the example
    a = 5
    b = 7
    c = 9

    # Beim Schreiben des Codes vertippt (n anstelle von b),
    # sodass a <= n statt a <= b ausgewertet wird, n ist jedoch nicht definiert
    if a <= n:
        if c >= b:
            print("a <= b und c >= b")
        else:
            print("a <= b und b > c")
    else:
        print("a > b")

    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    Cell In[94], line 8
          4 c = 9
          6 # Beim Schreiben des Codes vertippt (n anstelle von b),
          7 # sodass a <= n statt a <= b ausgewertet wird, n ist jedoch nicht definiert
    ----> 8 if a <= n:
          9     if c >= b:
         10         print("a <= b und c >= b")

    NameError: name 'n' is not defined

## Fehler bei Zugriffen auf Listen: `IndexError`¶

Zeile in der der Fehler auftritt: `----> 4 names[4]`
Grund für den Fehler: `IndexError: list index out of range`

In [95]:

    names = ["Anna", "Jan", "Marie", "Tim"]
    # Die Liste hat vier Einträge, da der erste Index aber 0 ist ist der letzte Index 3
    # der Index 4 der in Zeile 4 abgerufen wird überschreitet diesen Bereich
    names[4]

    ---------------------------------------------------------------------------
    IndexError                                Traceback (most recent call last)
    Cell In[95], line 4
          1 names = ["Anna", "Jan", "Marie", "Tim"]
          2 # Die Liste hat vier Einträge, da der erste Index aber 0 ist ist der letzte Index 3
          3 # der Index 4 der in Zeile 4 abgerufen wird überschreitet diesen Bereich
    ----> 4 names[4]

    IndexError: list index out of range

## Fehler bei Zugriffen auf Dictionaries: `KeyError`¶

Zeile in der der Fehler auftritt: `----> 4 numbers["five"]`
Grund für den Fehler: `KeyError: 'five'`

In [96]:

    numbers = {"one": 1, "two": 2, "three": 3, "four": 4}
    # Analog zum Beispiel mit der Listen Index
    # Der key "five" ist nicht in dem Dictionary vorhanden
    numbers["five"]

    ---------------------------------------------------------------------------
    KeyError                                  Traceback (most recent call last)
    Cell In[96], line 4
          1 numbers = {"one": 1, "two": 2, "three": 3, "four": 4}
          2 # Analog zum Beispiel mit der Listen Index
          3 # Der key "five" ist nicht in dem Dictionary vorhanden
    ----> 4 numbers["five"]

    KeyError: 'five'

## Komplexer Fehler mit Traceback:¶

Wenn an einem Fehler mehrere Funktionen beteiligt sind, gibt die Fehlermeldung einen Traceback aus. Dieser Fall ist eher die Regel, vor allem, wenn Module verwendet werden. Der Traceback zeigt die Reihenfolge aller Funktionsaufrufe, die am Ende zu dem Fehler geführt haben, mit dem letzten Aufruf ganz unten (most recent call last).

Zeilen, die zum Auftreten des Fehlers führen:
Funktionsaufruf in Zeile 13:
`---> 16 add_one_to_inverse_difference(x,y)`
Ergebnis des Funktionsaufrufs ist das `return` in Zeile 6 mit neuem Funktionsaufruf:
`----> 6 return 1 + inverse_difference(a, b)`
Ergebnis des 2. Funktionsaufrufs ist das `return` in Zeile 2:
`----> 2 return 1/(a - b)`
Hier tritt der Fehler auf, da durch Null geteilt wird, wenn a und b gleich sind!

Grund für den Fehler: `ZeroDivisionError: division by zero`

In [97]:

    def inverse_difference(a, b):
        return 1 / (a - b)

    def add_one_to_inverse_difference(a, b):
        return 1 + inverse_difference(a, b)

    def add_2_to_inverse_difference(a, b):
        return add_one_to_inverse_difference(a, b) + 2

    x = 2
    y = 2

    add_one_to_inverse_difference(x, y)

    ---------------------------------------------------------------------------
    ZeroDivisionError                         Traceback (most recent call last)
    Cell In[97], line 16
         13 x = 2
         14 y = 2
    ---> 16 add_one_to_inverse_difference(x, y)

    Cell In[97], line 6, in add_one_to_inverse_difference(a, b)
          5 def add_one_to_inverse_difference(a, b):
    ----> 6     return 1 + inverse_difference(a, b)

    Cell In[97], line 2, in inverse_difference(a, b)
          1 def inverse_difference(a, b):
    ----> 2     return 1 / (a - b)

    ZeroDivisionError: division by zero

## Debugging leicht gemacht¶

Es kann vorkommen, dass man mal überhaupt nicht weiß, warum etwas nicht funktioniert.

In diesem Fall sollte man zunächst mal Ruhe bewahren und sich dann auf systematische Fehlersuche begeben.
Ein einfacher Ansatz ist das `print()`-debugging.

Kleiner Trick, um Schuhgröße und Alter einer weiteren Person zu berechnen. Die zweite Person muss folgende Anweisungen befolgen:

    1. Multipliziere deine Schuhgröße mit 5
    2. Addiere 50 zum Ergebnis
    3. Multipliziere das Ergebnis mit 20
    4. Addiere 1020 zum Ergebnis
    5. Ziehe dein Geburtsjahr vom Ergebnis ab
    6. Nenne das Ergebnis

In [98]:

    def shoesize_and_age(shoesize, birth_year):
        step_1 = shoesize * 5
        step_2 = step_1 + 50
        step_3 = step_2 * 20
        step_4 = step_3 + 1020
        step_5 = step_4 - birth_year

        return step_5

#### Beispiel:¶

Mit der Schuhgröße 45 und dem Geburtsjahr 1993, kriege ich die Zahl `4527` raus.
Ich habe (wie erwähnt die Schuhgröße 45) und bin 27 Jahre alt.
Wie funktioniert dieser Trick?!

In [99]:

    shoesize_and_age(45, 1993)

Out[99]:

    4527

Die `print()-Debugguing` Methode:

In [100]:

    def shoesize_and_age(shoesize, birth_year):
        print("shoesize:", shoesize)
        step_1 = shoesize * 5
        print("step_1:", step_1)
        step_2 = step_1 + 50
        print("step_2:", step_2)
        step_3 = step_2 * 20
        print("step_3:", step_3)
        step_4 = step_3 + 1020
        print("step_4:", step_4)
        step_5 = step_4 - birth_year
        print("step_5:", step_5)

        return step_5

In [101]:

    shoesize_and_age(45, 1993)

    shoesize: 45
    step_1: 225
    step_2: 275
    step_3: 5500
    step_4: 6520
    step_5: 4527

Out[101]:

    4527

Die coolere `IPython.embed()`-Methode:

In [102]:

    import IPython

    def shoesize_and_age(shoesize, birth_year):
        step_1 = shoesize * 5
        step_2 = step_1 + 50
        step_3 = step_2 * 20
        step_4 = step_3 + 1020
        step_5 = step_4 - birth_year

        IPython.embed()

        return step_5

In [103]:

    shoesize_and_age(45, 1993)

    Python 3.13.7 | packaged by conda-forge | (main, Sep  3 2025, 14:30:35) [GCC 14.3.0]
    Type 'copyright', 'credits' or 'license' for more information
    IPython 9.5.0 -- An enhanced Interactive Python. Type '?' for help.
    Tip: You can use Ctrl-O to force a new line in terminal IPython

    ---------------------------------------------------------------------------
    StdinNotImplementedError                  Traceback (most recent call last)
    File /tmp/ipykernel_14071/2333610718.py:1
    ----> 1 shoesize_and_age(45, 1993)

    File /tmp/ipykernel_14071/2372780137.py:11, in shoesize_and_age(shoesize, birth_year)
          8 step_4 = step_3 + 1020
          9 step_5 = step_4 - birth_year
    ---> 11 IPython.embed()
         13 return step_5

    File ~/.local/conda/envs/toolbox/lib/python3.13/site-packages/IPython/terminal/embed.py:425, in embed(header, compile_flags, **kwargs)
        422 frame = sys._getframe(1)
        423 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
        424     frame.f_code.co_filename, frame.f_lineno), **kwargs)
    --> 425 shell(header=header, stack_depth=2, compile_flags=compile_flags,
        426     _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
        427 InteractiveShellEmbed.clear_instance()
        428 #restore previous instance

    File ~/.local/conda/envs/toolbox/lib/python3.13/site-packages/IPython/terminal/embed.py:253, in InteractiveShellEmbed.__call__(self, header, local_ns, module, dummy, stack_depth, compile_flags, **kw)
        249     self.show_banner()
        251 # Call the embedding code with a stack depth of 1 so it can skip over
        252 # our call and get the original caller's namespaces.
    --> 253 self.mainloop(
        254     local_ns, module, stack_depth=stack_depth, compile_flags=compile_flags
        255 )
        257 self.banner2 = self.old_banner2
        259 if self.exit_msg is not None:

    File ~/.local/conda/envs/toolbox/lib/python3.13/site-packages/IPython/terminal/embed.py:344, in InteractiveShellEmbed.mainloop(self, local_ns, module, stack_depth, compile_flags)
        341 self.set_completer_frame()
        343 with self.builtin_trap, self.display_trap:
    --> 344     self.interact()
        346 # now, purge out the local namespace of IPython's hidden variables.
        347 if local_ns is not None:

    File ~/.local/conda/envs/toolbox/lib/python3.13/site-packages/IPython/terminal/interactiveshell.py:997, in TerminalInteractiveShell.interact(self)
        994 print(self.separate_in, end='')
        996 try:
    --> 997     code = self.prompt_for_code()
        998 except EOFError:
        999     if (not self.confirm_exit) \
       1000             or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):

    File ~/.local/conda/envs/toolbox/lib/python3.13/site-packages/IPython/terminal/interactiveshell.py:777, in TerminalInteractiveShell.init_prompt_toolkit_cli.<locals>.prompt()
        775 def prompt():
        776     prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
    --> 777     lines = [input(prompt_text)]
        778     prompt_continuation = "".join(
        779         x[1] for x in self.prompts.continuation_prompt_tokens()
        780     )
        781     while self.check_complete("\n".join(lines))[0] == "incomplete":

    File ~/.local/conda/envs/toolbox/lib/python3.13/site-packages/IPython/utils/py3compat.py:49, in input(prompt)
         48 def input(prompt=""):
    ---> 49     return builtin_mod.input(prompt)

    File ~/.local/conda/envs/toolbox/lib/python3.13/site-packages/ipykernel/kernelbase.py:1274, in Kernel.raw_input(self, prompt)
       1272 if not self._allow_stdin:
       1273     msg = "raw_input was called, but this frontend does not support input requests."
    -> 1274     raise StdinNotImplementedError(msg)
       1275 return self._input_request(
       1276     str(prompt),
       1277     self._parent_ident["shell"],
       1278     self.get_parent("shell"),
       1279     password=False,
       1280 )

    StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

Das funktioniert auch in normalen Python `.py` Dateien.
`IPython.embed()` öffnet ein `IPython`-Terminalfenster mit allen Variablennamen, die vorher definiert wurden. Man kann dann interaktiv nach Problemen im Code suchen oder wie hier herausfinden wie Code funktioniert.

### Lösungen für die Kurzaufgaben¶

Lösung zu 1., 2. und 3.

    # 1.
    t   = 0.5
    h_i = 10
    v_i = 0.75
    a_i = -9.81

    # 2.
    h = h_i + v_i * t + 1/2 * a_i * t**2
    v = v_i * t + a_i * t
    a = a_i

    # 3.
    h < 9

Lösung zu 4.

    # 4.
    print("Start...")
    print("...höhe")
    print(h_i)
    print("...geschwindigkeit")
    print(v_i)
    print("...beschleunigung")
    print(a_i)

    print("Nach der Zeit t")
    print(t)
    print("Höhe")
    print(h)
    print("Geschwindigkeit")
    print(v)
    print("Beschleunigung")
    print(a)

Lösung zu 5.

    #  5.
    T = [0.5, 1, 1.5]
    H = []
    V = []
    A = []

    H.append(h_i + v_i * T[0] + 1/2 * a_i * T[0]**2)
    H.append(h_i + v_i * T[1] + 1/2 * a_i * T[1]**2)
    H.append(h_i + v_i * T[2] + 1/2 * a_i * T[2]**2)

    V.append(v_i * T[0] +  a_i * T[0])
    V.append(v_i * T[1] +  a_i * T[1])
    V.append(v_i * T[2] +  a_i * T[2])

    A.append(a_i)
    A.append(a_i)
    A.append(a_i)

    print(H)
    print(V)
    print(A)

Lösung zu 6. und 7.

    # 6.
    data = {"t":T, "h": H, "v": V, "a":A}
    print(data)

    # 7.
    print(data["t"][1])
    print(data["v"][0])
    print(data["h"][-1])

Lösung zu 8. und 9.

    # 8.
    def h(t):
      return h_i + v_i * t + 1/2 * a_i * t**2

    def v(t):
      return v_i * t + a_i * t

    def a(t):
      return a_i

    # 9.

    T = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5]

    # ohne Schleife
    data = {"t":T,
     "h": [h(T[0]), h(T[1]), h(T[2]), h(T[3]), h(T[4]), h(T[5])],
     "v": [v(T[0]), v(T[1]), v(T[2]), v(T[3]), v(T[4]), v(T[5])],
     "a": [a(T[0]), a(T[1]), a(T[2]), a(T[3]), a(T[4]), a(T[5])]}

    print(data)

    # mit Schleife
    H = []
    V = []
    A = []

    for t in T:
      H.append(h(t))
      V.append(v(t))
      A.append(a(t))

    data = {"t":T, "h": H, "v": V, "a": A}
    print(data)

Lösung zu 10.

    for k,v in data.items():
      print(f"{k}  {v[0]:.2f}  {v[1]:.2f}  {v[2]:.2f}  {v[3]:.2f}  {v[4]:.2f}  {v[5]:.2f}")

    # Am Dezimaltrennzeichen ausgerichtet

    for k,v in data.items():
      print(f"{k}  {v[0]:6.2f}  {v[1]:6.2f}  {v[2]:6.2f}  {v[3]:6.2f}  {v[4]:6.2f}  {v[5]:6.2f}")

    # Noch eine Schleife (List-Comprehension) mehr, erübrigt die Wiederholungen

    for key, values in data.items():
      formatted_values = " ".join([f"{v:6.2f}" for v in values])
      print(f"{key}  {formatted_values}")