- Aktuelle Version: Python 3.6
- Interpretierte Programmiersprache
  - Kein Kompilieren
  - Programme werden mit dem `python`-Programm ausgeführt
- Eignet sich sehr gut zum Erlernen der Programmierung!
- Hat viele Pakete, die man für wissenschaftliche Arbeit gebrauchen kann

Hier soll es hingehen: Auswertung von Praktikumsversuchen:

Versuch aus dem Fortgeschrittenen-Praktikum: Lebensdauer kosmischer Myonen

# Python¶

Eine Programmiersprache wie Python lässt sich nicht an einem Tag lernen.

Hier geben wir einen kurzen Überblick über die wichtigsten Konzepte.

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

Die folgenden Beispiele kann man auf der Kommandozeile durch ausführen von

    ipython

ausprobieren.

### Einfachstes Beispiel¶

In [1]:

    print('Hello, World!')

    Hello, World!

## Kommentare¶

Sehr wichtig für die Leute, die mit euch Arbeiten und für euch zwei Wochen später, sind Kommentare, die erklären, was euer Code tut.

Alles was in Python hinter einem `#` steht, wird nicht als Code betrachtet und dient als Kommentar.

In [2]:

    # This just prints the traditional greeting to the console
    print("Hello, World!")

    Hello, World!

Kommentare sollten nicht erklären, was der Code tut, sondern warum.

Kommentare, wie jeder andere Code, sollten auf englisch sein.

## Variablen¶

Man kann einem Objekt einen Namen geben:

In [3]:

    a = 2
    b = 3

Variablennamen können auch überschrieben werden:

In [4]:

    a = 42
    b = a

    a = 0
    b

Out[4]:

    42

## Einfache Datentypen¶

### None¶

Stellt das Fehlen eines Wertes dar.

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

In [5]:

    print(None)

    None

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

In [6]:

    print('test')

    test

In [7]:

    a = print('test')

    test

In [8]:

    print(a)

    None

### Booleans¶

`True` und `False`

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

In [9]:

    True and False

Out[9]:

    False

In [10]:

    True or False

Out[10]:

    True

In [11]:

    not True

Out[11]:

    False

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

In [12]:

    None or True

Out[12]:

    True

In [13]:

    'Hallo' and True

Out[13]:

    True

### Zahlen¶

Ganze Zahlen: `42` Kommazahlen: `3.14`

Rechenoperatoren:

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

In [14]:

    x = 5
    y = 3
    x + y

Out[14]:

    8

Rechenoperationen können geklammert werden

In [15]:

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

In [16]:

    print(a)

    3.8

In [17]:

    print(b)

    2.2

In [18]:

    print(c)

    1.0

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

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

In [19]:

    x < y

Out[19]:

    False

Mehrere Vergleichsoperatoren können in einem Ausdruck stehen:

In [20]:

    c < b < a

Out[20]:

    True

In [21]:

    c < a < b

Out[21]:

    False

### Strings¶

Stehen in `'` oder `"`.

In [22]:

    foo = 'foo'
    bar = "bar"

Strings können mit `+` konkateniert werden:

In [23]:

    foo + " " + bar

Out[23]:

    'foo bar'

Strings können mit `*` vervielfacht werden

In [24]:

    foo * 4

Out[24]:

    'foofoofoofoo'

Strings können mit `[]` indiziert werden

In [25]:

    foo[0]

Out[25]:

    'f'

### Listen¶

Gedacht für mehrere Werte vom selben Typ.

In [26]:

    names = ['foo', 'bar']

Listen können mit `[]` indiziert werdern

In [27]:

    names[1]

Out[27]:

    'bar'

Man kann Werte an Listen anhängen:

In [28]:

    names.append('baz')
    names

Out[28]:

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

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

In [29]:

    names[-1]

Out[29]:

    'baz'

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

In [30]:

    names[1:3]

Out[30]:

    ['bar', 'baz']

Listen werden mit `+` zusammengeführt:

In [31]:

    names + ['thing']

Out[31]:

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

Listen können mit `*` vervielfacht werden:

In [32]:

    names * 3

Out[32]:

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

Man kann die Liste auch erweitern:

In [33]:

    names.extend(['quux'])
    names

Out[33]:

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

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

In [34]:

    names[1] = 'new'
    names

Out[34]:

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

Mit `in` prüft man, ob ein Wert in der Liste ist:

In [35]:

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

Out[35]:

    True

### Tupel¶

Gedacht für Werte mit unterschiedlichen Typen. Unveränderbar nach Erzeugung.

In [36]:

    tup = (5, 3)
    tup

Out[36]:

    (5, 3)

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

In [37]:

    tup = 5, 3
    tup

Out[37]:

    (5, 3)

In [38]:

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

In [39]:

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

    a = 3
    b = 5

In [40]:

    tup[0]

Out[40]:

    5

In [41]:

    tup[1] = 7

    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-41-0dc366c7c319> in <module>()
    ----> 1 tup[1] = 7

    TypeError: 'tuple' object does not support item assignment

### Dictionaries¶

In [42]:

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

In [43]:

    numbers['two']

Out[43]:

    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 [44]:

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

Out[44]:

    [1.1, 1.2, 1.3, 1.4]

Leere Dictionaries erstellt man z.B. mit der Funktion `dict()`

In [45]:

    data = dict()
    data

Out[45]:

    {}

In [46]:

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

Out[46]:

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

In [47]:

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

Out[47]:

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

In [48]:

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

Out[48]:

    {'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]}

In der aktuellen Python-Version 3.6 entspricht die Reihenfolge der Einträge in Dictionaries der
Zuweisungsreihenfolge! In allen älteren Versionen war dies nicht der Fall und es kann sich auch
wieder ändern. $\Rightarrow$ Nicht auf die Reihenfolge der Dictionary-Einträge verlassen.

## Funktionen aufrufen¶

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

In [49]:

    print('Hello!')

    Hello!

In [50]:

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

Out[50]:

    5

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

In [51]:

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

    1 2 3 4 5

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

In [52]:

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

    1, 2, 3, 4, 5

Objekte haben auch Funktionen (diese nennt man Methoden):

In [53]:

    s = 'test'
    s.upper()

Out[53]:

    'TEST'

In [54]:

    'foo bar baz'.split()

Out[54]:

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

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

In [55]:

    print?

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

## Kontrollstrukturen¶

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

Sogennante Kontrollstrukturen erlauben es, den Ablauf zu steuern.

- `if`/`elif`/`else`: Je nach Bedingung unterschiedliche Befehle ausführen

In [56]:

    a = 3

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

    baz

- `while`-Schleifen: Wiederholen, solange ein bestimmter Ausdruck erfüllt ist.

In [57]:

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

    # there has to be a better way!

    0
    1
    2
    3
    4

- `for`-Schleifen: Wiederholen für jedes Element (z.B. einer Liste)

In [58]:

    data = [10, 42, -1]

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

    20
    84
    -2

In [59]:

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

    0
    1
    2
    3
    4

In [60]:

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

    2
    3
    4

In [61]:

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

    2
    4
    6
    8

In [62]:

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

    10
    9
    8
    7
    6
    5
    4

In [63]:

    # Hatten wir weiter oben bei den Listen erstellt
    weekdays

Out[63]:

    ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']

In [64]:

    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 [65]:

    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 [66]:

    list(enumerate(weekdays))

Out[66]:

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

In [67]:

    english = ["foot", "ball", "goal"]
    german = ["Fuß", "Ball", "Tor"]

    for a, b in zip(english, german):
        print(a, b)

    foot Fuß
    ball Ball
    goal Tor

Was `zip()` macht:

In [68]:

    list(zip(english,german))

Out[68]:

    [('foot', 'Fuß'), ('ball', 'Ball'), ('goal', 'Tor')]

Das obere würde man eher so machen:

In [69]:

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

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

    foot Fuß
    ball Ball
    goal Tor

## Eigene Funktionen definieren mit `def`¶

Bevor man eine Funktion verwenden kann, muss sie erst definiert werden. Das wird wie folgt gemacht. Mit dem Keyword `return` kann man die Funktion beenden und Ergebnisse zurückgeben.

In [70]:

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

    add(2, 2)

Out[70]:

    4

Man kann auch mehrere Werte zurückgeben:

In [71]:

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

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

Out[71]:

    (1, 2)

In [72]:

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

    factorial(4)

Out[72]:

    24

## Module und `import`¶

In Python kann man Programme in einzelne Module portionieren.

Es stehen bereits unglaublich viele Module für Python zur Verfügung. Gerade fürs wissenschaftliche Arbeiten ist eigentlich alles da was man braucht und noch viel mehr. Braucht man etwas aus einem anderen Modul, importiert man es:

In [73]:

    import os

    os.listdir()

Out[73]:

    ['matplotlib.ipynb',
     'fit_data_with_init_values.txt',
     'scientific-python.html',
     'python.ipynb',
     'uncertainties.html',
     'matplotlibrc',
     'Indexing2D.svg',
     'numeric-python.html',
     'plot_2.pdf',
     'Makefile',
     'numeric-python.ipynb',
     'scientific-python.ipynb',
     'Indexing1D_code.svg',
     'plot_1.pdf',
     'Indexing1D.svg',
     '.gitignore',
     'plot.pdf',
     'Indexing2D_code.svg',
     'muon_plot.png',
     'example_data_linear.txt',
     'test.txt',
     'muon_data.txt',
     'example2.txt',
     'muon_plot.py',
     'README.md',
     'uncertainties.ipynb',
     'matplotlib.html']

Man kann auch nur einzelne Funktionen importieren:

In [74]:

    from os.path import join
    outputpath = "Plots"
    join(outputpath, "fig1.pdf")

Out[74]:

    'Plots/fig1.pdf'

`from module import *` importiert jede Funktion aus dem angegebenen Modul `module`. Sollte auf Grund von möglichen Mehrfachbelegungen nicht in größeren Programmen verwendet werden

In [75]:

    from os.path import *
    outputpath = "Plots"
    if exists(outputpath):
        print("Plots exists!")

Den Modulen können mit `as` neue Namen gegeben werden

In [76]:

    import os.path as pa

    pa.join(outputpath, "fig1.pdf")

Out[76]:

    'Plots/fig1.pdf'

## Auslagern in Python-Dateien¶

- Python-Dateien haben die Endung `.py`
- Startet man mit `python programm.py`
- Einzelne Python-Dateien (Endung `.py`) kann man als Module benutzen. Liegt eine Datei `test.py` im selben Ordner, so kann man `import test` ausführen.
- Die Datei sollte immer in der Codierung UTF-8 abgespeichert werden.

Es folgt ein fertiges Python-Programm.

Speichert es als `primes.py` ab und startet 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]

## Besonderheiten bei Strings¶

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

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

Wenn man viele `'\'` schreiben muss (z.B. in LaTeX-Code), lohnt es sich diese Funktion mit dem Prefix `r` auszuschalten:

In [78]:

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

    \Huge\texttt{Python}

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 [79]:

    first_value = 42
    second_value = 0

    print(f"Erster Wert: {first_value}, Zweiter Wert: {second_value}")

    Erster Wert: 42, Zweiter Wert: 0

Die Variablen in den `{}`-Markierungen werden durch die Werte der entsprechenden
Variablen ersetzt.

f-strings haben viele fortgeschrittene Funktionen.

Man kann beispielsweise die gewünschte Genauigkeit für `floats` angeben:

In [80]:

    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.

Man kann die Variablen in f-strings auch verschachteln.
Zum Beispiel kann man die Genauigkeit für `floats` auch als Variable angeben:

In [81]:

    result = 3.2291421
    precisions = [1,2,3,4,5]

    for precision in precisions:
        print(f'Das Ergebnis ist {result:.{precision}f}')

    Das Ergebnis ist 3.2
    Das Ergebnis ist 3.23
    Das Ergebnis ist 3.229
    Das Ergebnis ist 3.2291
    Das Ergebnis ist 3.22914

Die f-strings sind eine lesbarere Variante einer allgemeineren Funktionalität, der `format()`-Methode:

In [82]:

    first_value = 42
    second_value = 0

    'Erster Wert: {}, Zweiter Wert: {}'.format(first_value, second_value)

Out[82]:

    'Erster Wert: 42, Zweiter Wert: 0'

Die `{}`-Markierungen werden durch die Parameter von `format()` ersetzt.

`format()` bietet noch einige weitere Möglichkeiten, wenn f-strings mal nicht reichen.

Man kann Parameter benennen und diese genauso formattieren wie in f-stings:

In [83]:

    'Das Ergebnis ist {result:.2f} und damit kleiner als {four}'.format(result=3.2291421, four=4)

Out[83]:

    'Das Ergebnis ist 3.23 und damit kleiner als 4'

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

LaTeX sieht dann leider so aus:

In [84]:

    print(r'\SI{{{:.4f}}}{{{:s}}}'.format(1.23456, r'\kilo\joule'))

    \SI{1.2346}{\kilo\joule}

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

In [85]:

    print(r'\SI {{ {:.4f} }} {{ {:s} }}'.format(1.23456, r'\kilo\joule'))

    \SI { 1.2346 } { \kilo\joule }

Ausführliche Beispiele zur Benutzung von Format findet man z.B. in der offiziellen Dokumentation:

https://docs.python.org/3.1/library/string.html#format-examples

## Comprehensions¶

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

In [86]:

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

Out[86]:

    [0, 1, 2, 3, 4]

In [87]:

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

Out[87]:

    [0, 2, 4, 6, 8]

In [88]:

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

Out[88]:

    [0, 4, 8]

In [89]:

    # Ursprüngliche Liste
    list(enumerate(weekdays))

Out[89]:

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

In [90]:

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

Out[90]:

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

In [91]:

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

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

In [92]:

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

Out[92]:

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

In [93]:

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

Out[93]:

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

## Häufig auftretende Fehler¶

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

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

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

In [94]:

    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 b >= c")
        else:
            print("a <= b und b >= c")
    else:
        print("a > b")

      File "<ipython-input-94-0a5350255358>", line 8
        print("a <= b und b >= c")
            ^
    IndentationError: expected an indented block

### Allgemeiner Syntax-Fehler: `SyntaxError`¶

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

In [95]:

    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 b >= c")
        else:
            print("a <= b und b >= c")
    else:
        print("a > b")

      File "<ipython-input-95-326c449f263e>", line 6
        if a <= b
                 ^
    SyntaxError: invalid syntax

### Ein zu Anfang verwirrender Fehler¶

Die Fehlermeldung ist hier im ersten Moment nicht besonders hilfreich:
Zeile in der der Fehler auftritt: `line 11`
Grund für den Fehler: `SyntaxError: invalid syntax`

Stimmt nur zum Teil, denn in Zeile 11 ist alles in Ordnung und der eigentliche Fehler ist in Zeile 10.
In komplexeren Programmen ist das nicht mehr so einfach zu sehen wie hier,
deswegen ist es gut, wenn man dieses Verhalten schon mal gesehen hat.

In [96]:

    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 b >= c")
        else:
            print("a <= b und b >= c")
    else:
        print("a > b")

      File "<ipython-input-96-04b0166b2e09>", line 11
        print("a <= b und b >= c")
            ^
    SyntaxError: invalid syntax

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

Zeile in der der Fehler auftritt: `----> 7 if a <= n:`
Grund für den Fehler: `NameError: name 'n' is not defined`
Fehlermeldung sieht etwas anders aus, liefert aber immer noch die gleichen Informationen.

In [97]:

    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 b >= c")
        else:
            print("a <= b und b >= c")
    else:
        print("a > b")

    a > b

### 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`
Fehlermeldung sieht etwas anders aus, liefert aber immer noch die gleichen Informationen.

In [98]:

    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)
    <ipython-input-98-da525c7959b7> in <module>()
          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'`
Fehlermeldung sieht etwas anders aus, liefert aber immer noch die gleichen Informationen.

In [99]:

    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)
    <ipython-input-99-2308ed8a16fa> in <module>()
          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, vorallem, wenn man Module verwendet.
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 10:
`---> 10 add_one_to_inverse_difference(x,y)`
Ergebnis des Funktionsaufrufs ist das `return` in Zeile 5 mit neuem Funktionsaufruf:
`----> 5 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 [100]:

    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)
    <ipython-input-100-c6e4b41c5f62> in <module>()
         11 y = 2
         12
    ---> 13 add_one_to_inverse_difference(x,y)

    <ipython-input-100-c6e4b41c5f62> in add_one_to_inverse_difference(a, b)
          3
          4 def add_one_to_inverse_difference(a, b):
    ----> 5     return 1 + inverse_difference(a, b)
          6
          7 def add_2_to_inverse_difference(a, b):

    <ipython-input-100-c6e4b41c5f62> in inverse_difference(a, b)
          1 def inverse_difference(a, b):
    ----> 2     return 1/(a - b)
          3
          4 def add_one_to_inverse_difference(a, b):
          5     return 1 + inverse_difference(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 1018 zum Ergebnis
    5. Ziehe dein Geburtsjahr vom Ergebnis ab
    6. Nenne das Ergebnis

In [101]:

    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 + 1018
        step_5 = step_4 - birth_year

        return step_5

#### Beispiel:¶

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

In [102]:

    shoesize_and_age(45, 1993)

Out[102]:

    4525

Die `print()-Debugguing` Methode:

In [103]:

    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 + 1018
        print("step_4:", step_4)
        step_5 = step_4 - birth_year
        print("step_5:", step_5)

        return step_5

In [104]:

    shoesize_and_age(45, 1993)

    shoesize: 45
    step_1: 225
    step_2: 275
    step_3: 5500
    step_4: 6518
    step_5: 4525

Out[104]:

    4525

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

In [105]:

    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 + 1017
        step_5 = step_4 - birth_year

        IPython.embed()

        return step

In [106]:

    shoesize_and_age(45, 1993)

    Python 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 17:14:51)
    Type 'copyright', 'credits' or 'license' for more information
    IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

    ---------------------------------------------------------------------------
    StdinNotImplementedError                  Traceback (most recent call last)
    <ipython-input-106-1ed1f1f17aac> in <module>()
    ----> 1 shoesize_and_age(45, 1993)

    <ipython-input-105-4c2e03225a3e> in shoesize_and_age(shoesize, birth_year)
          8     step_5 = step_4 - birth_year
          9
    ---> 10     IPython.embed()
         11
         12     return step

    ~/.local/anaconda3/lib/python3.6/site-packages/IPython/terminal/embed.py in embed(**kwargs)
        384         frame.f_code.co_filename, frame.f_lineno), **kwargs)
        385     shell(header=header, stack_depth=2, compile_flags=compile_flags,
    --> 386           _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
        387     InteractiveShellEmbed.clear_instance()
        388     #restore previous instance

    ~/.local/anaconda3/lib/python3.6/site-packages/IPython/terminal/embed.py in __call__(self, header, local_ns, module, dummy, stack_depth, global_ns, compile_flags, **kw)
        227         # our call and get the original caller's namespaces.
        228         self.mainloop(local_ns, module, stack_depth=stack_depth,
    --> 229                       global_ns=global_ns, compile_flags=compile_flags)
        230
        231         self.banner2 = self.old_banner2

    ~/.local/anaconda3/lib/python3.6/site-packages/IPython/terminal/embed.py in mainloop(self, local_ns, module, stack_depth, display_banner, global_ns, compile_flags)
        322
        323         with self.builtin_trap, self.display_trap:
    --> 324             self.interact()
        325
        326         # now, purge out the local namespace of IPython's hidden variables.

    ~/.local/anaconda3/lib/python3.6/site-packages/IPython/terminal/interactiveshell.py in interact(self, display_banner)
        466
        467             try:
    --> 468                 code = self.prompt_for_code()
        469             except EOFError:
        470                 if (not self.confirm_exit) \

    ~/.local/anaconda3/lib/python3.6/site-packages/IPython/terminal/interactiveshell.py in prompt()
        236                 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
        237                 while isp.push_accepts_more():
    --> 238                     line = input(prompt_text)
        239                     isp.push(line)
        240                     prompt_text = prompt_continuation

    ~/.local/anaconda3/lib/python3.6/site-packages/IPython/utils/py3compat.py in input(prompt)
        152 # to forward requests to a frontend.
        153 def input(prompt=''):
    --> 154     return builtin_mod.input(prompt)
        155
        156 builtin_mod_name = "builtins"

    ~/.local/anaconda3/lib/python3.6/site-packages/ipykernel/kernelbase.py in raw_input(self, prompt)
        697         if not self._allow_stdin:
        698             raise StdinNotImplementedError(
    --> 699                 "raw_input was called, but this frontend does not support input requests."
        700             )
        701         return self._input_request(str(prompt),

    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 herrausfinden wie Code funktioniert.