Recap Python

h:500


🎯 Lernziele

Nach dieser Einheit sind Sie in der Lage dazu - einfache Programmierprobleme mittels Python zu lösen - hierzu Schleifen, Bedingungen und Datenstrukturen einzusetzen


Variablen

  • Eine Variable ist ein Paar, bestehend aus einem Namen (kennzeichnet Platz im Speicher), dem ein Wert zugewiesen ist
  • Der Wert kann durch weitere Zuweisungen verändert werden (ist variabel)
  • Welcher Art der Wert (Inhalt) einer Variable ist, legt der Datentyp fest
    • z.B. Ziffern, Zeichen, Buchstaben, Wörter, Listen,...
    • dem entsprechend wir unterschiedlich viel Platz im Speicher reserviert

bg right:25% h:150


Beispiel

# der Variable foo wird der Wert 'zwanzig' als Zeichenkette / String zugeordnet
foo : str = 'zwanzig'

# der Variable ba wird der Wert 20 als Ganze Zahl / Integer zugeordnet
ba : int = 20

Primitive Datentypen


Integer

  • ist eine ganze natürliche Zahl
    • Beispiele: 9, 23, -162, 1095, 2012
  • Maximale Größe eines Integers ist von der jeweiligen Rechner-Architektur abhängig
  • Gewöhnlich ist der Wertebereich ca. +/- zwei Milliarden, da ein Integer als (vorzeichenbehafteter) 32-Bit-Wert gespeichert wird
# der Variable ba wird der Wert 20 als Ganze Zahl / Integer zugeordnet
ba = 20

# In Python müssen wir den Datentyp nicht explizit angebe
# Wir können ihn aber auch für Leser:innen angeben (Type hinting)
ba : int = 20

Double / Float

  • Fließkommazahlen sind reelle Zahlen
    • Also Zahlen mit Komma und Nachkommastellen
    • Beispiele: 5.0, 29.3. 19674.2543, 0.999999999
  • Achtung: Man muss das Komma als Punkt schreiben!
  • Maximale Größe eines Double von der Rechner-Architektur abhängig
  • als 64-Bit-Wert gespeichert, maximale Größe von ~ \(1.8 \cdot 10^{308}\) mit einer Genauigkeit von 14 Nachkommastellen
# der Variable ba wird der 3.14 als Gleitkommazahl / Float zugeordnet
ba = 3.14

ba : float = 3.14

String / Zeichenkette

  • Es ist egal, um welche Zeichen es sich handelt (auch Zahlen)
  • Werden durch " oder ' gekennzeichnet. Diese sind austauschbar, solange sie nicht gemischt werden
    • "Hallo", 'Hallo', "123", "x@xxx"
  • Ein String kann Buchstaben, Zahlen und Sonderzeichen enthalten
  • Alle in einem String enthaltenen Zeichen werden als ein Wort angesprochen und behandelt
    • Beispiele: abcxyz, PHP_ist_super, 20Euro, #*/[}%
# der Variable foo wird der Wert 'zwanzig' als Zeichenkette / String zugeordnet
foo : str = 'zwanzig'

  • Strings verhalten Sich wie Listen
  • Wir können Sie durchlaufen und manipulieren
  • Wir können über den Index auf einzelne Zeichen zugreifen
  • Das Konzept, dass sich verschiedene Datentypen gleich verhalten nennt sich Polymorphismus und wird uns in der objekt-orientierten Programmierung noch öfter begegnen
foo = 'zwanzig'

for letter in foo:
    print(letter)


print(foo[::-1])

Boolean

  • können entweder den Wert True oder False haben
  • Wird hauptsächlich bei Vergleichen und Schleifen angewendet
# den Variablen werden die Werte True und False zugeordnet 
foo = True
ba = False

# Hiermit lässt sich die Boolesche Algebra anwenden
print(foo + ba) # Gibt 1 zurück
print(foo or ba) # Gibt True zurück  

print(foo * ba) # 0
print(foo and ba) # False

Umwandlung zu anderen Typen (Casting)

  • Python kann einige Datentypen ineinander umwandeln

    int("30") # Gibt 30 als Integer zurück
    float("3.14") # Gibt 3.14 als Float zurück
    str(20+3.14) # Gibt "23.14" als String zurück
    
    foo = '30'
    print(type(foo)) # Gibt <class 'str'> zurück. foo ist also ein String
    
    ba = int(foo)
    print(type(ba)) # Gibt <class 'int '> zurück. 
    # in ba wurde foo als Integer umgewandelt gespeichert
    
    * Falsche/unerwartete Datentypen sind ein häufiger Grund für Fehler


Type Checking

  • Type Hinting kann helfen, Fehler zu vermeiden
  • Type Checking erzwingt, dass Variablen den richtigen Datentyp haben
# Reines Type Hinting
x : int = 10
y : int = 10
print(x + y) # Output: 20

# Type Checking
if isinstance(x, int) and isinstance(y, int):
    print(x + y) # Output: 20
else:
    print("x und y müssen Ganze Zahlen sein")

Datenstrukturen

h:200

  • Liste (Lineare Datenstruktur): Jeder Knoten hat genau einen Verweis auf seinen Nachfolger
  • Baum: Jeder Knoten kann mehrere Verweise auf Nachfolger haben, aber jeder Knoten wird nur von höchstens einem Vorgängerknoten referenziert.
  • Graph: Knoten können auch von mehreren Vorgängerknoten referenziert werden.

Listen

  • Eine Reihe von Elementen des gleichen oder unterschiedlichen Datentyps
  • Vergleichbar einer Art Liste von Objekten
  • Listen werden in Python mit eckigen Klammern definiert
  • Auf jedes dieser Objekte kann man mittels eines Indexes zugreifen
# Listen können mit eckigen Klammern erstellt werden - auch leere Listen 
empty_list = [ ]
filled_list = ['a','b','c',1]

# Der Zugriff auf den Index 0 gibt das erste Element in der Liste ('a') zurück
filled_list[0]
# > 'a'

Listen Gleichen Datentypes

  • stellen sicher, dass die gleichen Aktionen durchgeführt werden können
  • ermöglicht es Warteschlangen zu bilden
  • z.B. um die die Trainingsdauern für viele Trainings zu speichern
    dauern_in_min = [10,15,30,45,60]
    print(dauern_in_min[1])
    #> 15
    dauern_in_min_new = dauern_in_min[:].append(20)
    print(dauern_in_min_new[:])
    #> [10,15,30,45,60,20]    
    
  • Diese Liste hat 5 Elemente. Ihr Index läuft von 0 bis 4.

Arrays

  • Speicherung in einen zusammenhängenden Speicherblock
  • Homogen: alle Einträge haben denselben Datentyp
  • Statisch: Länge kann nicht verändert werden
  • Um Arrays zu verwenden, müssen wir das Modul numpy importieren


✍ Aufgabe

h:200

  • Wir möchten die durchschnittliche Leistung eines Ergometers über ein Training berechnen
  • Wir müssen dazu die Leistung \(P\) (in Watt) für jeden Zeitpunkt ermitteln
  • Die Leistung wird über die folgenden Formel berechnet: \(P = M\omega\)
    • \(M\) ist das Moment in Newtonmeter (Nm), gemessen über einen Drehmomentsensor
    • \(\omega\) ist die Winkelgeschwindigkeit in Rad/s, gemessen über einen Drehzahlsensor
    • Die Samplerate beträgt 100 Hz
    • Die Fahrt dauert ca. 27 h
  • Wir möchten ermitteln, wie viel Energie \(W\) in kJ ein Fahrradfahrer während einer Trainingseinheit verbraucht

  • Schreiben Sie ein Skript, das alle Zahlen zweier gleich langer Listen elementweise multipliziert und dann die Ergebnisse auf-addiert (entsprechend einer Matrixmultiplikation)

Sie können das folgende Skript als Vorlage verwenden

import time
start_time = time.perf_counter()

index = 0
result = 0

"""
ADD YOUR CODE HERE
"""

print("List Approach")
print("--- %s seconds ---" % (time.perf_counter() - start_time))
print(result)

✔️ Lösung

# Paket für numerische Aufgaben im Python
import numpy as np

start_time = time.perf_counter()

# Umwandlung der Listen in numpy arrays
# vgl. Konstruktor
array1 = np.array(list1_momentum_in_Nm, dtype=np.int64)
array2 = np.array(list2_omega_in_rad_per_s, dtype=np.int64)



# Aufruf der Funktion für Matrixmultiplikation des Pakets numpy
result = np.matmul(array1, array2)/len(array1)
print("Numpy")
print("--- %s seconds ---" % (time.perf_counter() - start_time))
print(result)

List Approach
--- 5.2479909490000125 seconds ---
Numpy
--- 0.018864246000248386 seconds ---
* Vektorisierung ist i.d.R. um ein Vielfaches schneller


Kopieren von Primitiven Datentypen

  • wir sind es gewohnt, dass der Zuweisungsoperator = den Inhalt einer Variable kopiert:
var_a = 1
var_b = var_a
var_b = var_b + 1
print(var_b) #> 2
print(var_a) #> 1

Kopieren von Listen

  • bei Listen passiert etwas merkwürdiges. Nach der Zuweisung zeigen beide Variablennamen auf das gleiche Objekt im Speicher
  • entsprechend werden beide Listen in Zukunft verändert, wenn wir eine von beiden verändern
list_a = [1,2,3]
list_b = list_a
list_b.append(4)
print(list_b) #> [1,2,3,4]
print(list_a) #> [1,2,3,4]

h:250

Quelle


  • ein neues Objekt wird nur angelegt, wenn wir dies explizit mit dem copy() Befehl angeben
  • selbes gilt für verwandte Datentypen (dictionaries, pandas dataframes etc.)
list_a = [1,2,3]
list_b = list_a.copy()
list_b.append(4)
print(list_b) #> [1,2,3,4]
print(list_a) #> [1,2,3]

Dictionaries

height:500

  • Angenommen wir möchten zusammengesetzte Daten speichern (z.B. ein Trainingstagebuch)

  • Mit Listen ist dies nur eingeschränkt sinnvoll möglich
  • dates = ["16.8.2022","18.8.2022", "19.8.2022" <...>]
  • activity = ["Dauerlauf","Radfahren", "Dauerlauf" <...>]
  • über den index, lassen sich die Daten und Aktivitäten zuordnen
  • Was sind Nachteile?
    • Mehrere Aktivitäten an einem Tag?
    • Löschen von einzelnen Aktivitäten?

bg right:50% height:350


  • Alternativ können wir mit einer hierarchischen Datenstruktur basierend auf Hash-Maps arbeiten
  • Dictionaries speichern key-value-Paare

    dict = {<key> : <value>}
    

    dict = {"16.8.2022" : "Dauerlauf",
            "18.8.2022" : "Radfahren",
            "19.8.2022" : "Dauerlauf",
            [...]  }
    
    * Anstelle des Index können wir den key nutzen
    print(dict["18.8.2022"])
    >>> "Radfahren"
    


Hierarchische Dictionaries (Bäume)

  • Dictionaries können ineinander geschachtelt werden
    training_log = {"0": {   
                            "date" : "16.8.2022",
                            "time" : "8:00",
                            "activity" : "Radfahren",
                            "distance_km" : 5},
                    "1": {   
                            "date" : "16.8.2022",
                            "time" : "11:00",
                            "activity" : "Dauerlauf",
                            "distance_km" : 15},
                }
    print(training_log["0"]["activity"])
    #> "Radfahren"
    
  • Diese Struktur ist sehr flexibel und erlaubt es uns, beliebig viele Informationen zu speichern
  • Entsprechend werden sollte Ansätze auch in den Praxis genutzt, um Daten zu speichern (z.B. in NoSQL-Datenbanken, JSON-Dateien, etc.)

Kontrollstrukturen

  • Steuern den Programmablauf
  • Die wichtigsten Kontrollstrukturen sind:
    • Sequenz (Folge von Aktionen ohne jede Bedingung)
    • Alternative/Verzweigung (if, if-else, elseif)
    • Repetition/Schleife (while, for)
  • Alle weiteren Kontrollstrukturen (do-while, foreach, switch, break, usw.) sind auf die bereits genannten zurückführbar

while-Schleife - abweisende Schleife

while <logischer Ausdruck>:
    <Anweisungsblock>
- Die abweisende Schleife wiederholt die Anweisung solange die Bedingung erfüllt ist (True) - Eventuell wird die Anweisung nie ausgeführt, weil zuerst die Bedingung überprüft wird

bg h:300 right:33%


# Beispiel Ausgabe aller ganzen Zahlen im Bereich von 1...1000, 
n = 1   # Variable als Zähler
while (n <= 1000):
    print(n)    # Was eingerückt ist, ist in der Schleife und wird wiederholt 
    n = n + 1   # Das wird in jeder Schleife ausgeführt

for-Schleife - Zählschleife

  • Führt den Anweisungsblock mit einer bestimmten Anzahl von Wiederholungen aus
    for <Element> in <Liste>:
        <Anweisungsblock>
    
  • Kann zum Beispiel auch eine Liste durchgehen

    for wert in [1,2,3,4,5,6,7]:
        print(wert) # Gibt nacheinander 1,2,3,4,5,6,7 aus
    

  • Anstelle von Listen können auch Zahlenbereich definiert werden

    for wert in range(1,8):
        print(wert) # Gibt nacheinander 1,2,3,4,5,6,7 aus
    


for-Schleife - mit Counter

  • der in Befehl ist eine Verkürzung in Python
  • in anderen Programmiersprachen muss der index manuell übergeben werden
liste = [1,2,3,4,5,6,7]

start_index = 0
end_index = len(liste)
for index in range(start_index,end_index):
    wert = liste[index]
    print(wert) # Gibt nacheinander 1,2,3,4,5,6,7 aus

for-Schleife - mit Counter pythonic

  • der in Befehl ist eine Verkürzung in Python
  • in anderen Programmiersprachen muss der index manuell übergeben werden
liste = [1,2,3,4,5,6,7]

for index, wert in enumerate(liste):
    print(index, wert) # Gibt nacheinander 1,2,3,4,5,6,7 aus

if-Abfrage - einseitige Alternative

if <logischer Ausdruck>:
    <Anweisungsblock>
- Der logische Ausdruck wird ausgewertet - TRUE: Anweisungsblock ausführen - FALSE: Anweisungsblock überspringen

# Beispiel printed nur, wenn condition = True
pin_correct = True

if pin_correct:
     print('Pin is correct!') 
bg h:300 right:33%


🧠 if-else-Abfrage - zweiseitige Alternative

if <logischer Ausdruck>:
    <Anweisungsblock a>
else:
    <Anweisungsblock b>    
- Der logische Ausdruck wird ausgewertet - TRUE: Anweisungsblock a ausführen - FALSE: Anweisungsblock b ausführen

# Beispiel printed Lösung
a = 4
b = 3
if a>b:
     print("a ist größer b")
else:
     print("b ist größer a")    
bg h:300 right:33%


# Beispiel Ausgabe aller ganzen Zahlen im Bereich von 1...1000, 
# die durch 13 gerade teilbar sind
n = 0
while (n <= 1000):
  if (n % 13 == 0): # Die Bedingung prüft, ob n genau durch 13 teilbar ist
    print(n) # Das wird nur ausgeführt, wenn die Bedingung im if Block wahr ist
  n = n + 1 # Das wird in jeder Schleife ausgeführt, weil es nicht im if-Block steht

Ausnahmebehandlung

  • (Un)erwartete Fehler im Code zur Laufzeit
  • Programme sollen nicht abstürzen
  • Behandlung von Fehlern zur Laufzeit
numbers = [1,2,0,4,5]

for number in numbers:

        result = 10 / number
        print("{}/{}={}".format(10,number,result))

Try-Except-Blöcke

numbers = [1,2,0,4,5]

for number in numbers:

    # Versuche folgende Operation
    try:
        result = 10 / number
        print("{}/{}={}".format(10,number,result))
    # Wenn ein ZeroDivisionError auftritt
    except ZeroDivisionError:
        # Mache folgendes
        print("Division durch Null ist nicht erlaubt.")
        # Anstelle die Ausführung des Programms zu unterbrechen

Weitere Struktur

  • try-Block führt einen Code-Block versuchsweise aus
  • Der except-Block ist der Code, der bei einem Fehler ausgeführt wird
  • else-Block wird ausgeführt, wenn keine Exception auftritt
  • Der finally-Block wird auf jeden Fall ausgeführt

Beispiel eine weiteren except-Block

numbers = [1, 2, 0, "3", 4, 5]

for number in numbers:

    # Versuche folgende Operation
    try:
        result = 10 / number
        print("{}/{}={}".format(10,number,result))
    # Wenn ein ZeroDivisionError auftritt
    except ZeroDivisionError:
        # Mache folgendes
        print("Division durch Null ist nicht erlaubt.")
        # Anstelle die Ausführung des Programms zu unterbrechen
    except TypeError:
        print("Division von String nicht erlaubt")

✍️ Anwendungsfall: Leistungstest mit Ergometer

h:500

Quelle


Stufentest

h:500

Quelle



Auswertung

bg left:40% h:700

  • aerobe Schwelle ist der Punkt, an dem der Laktatspiegel im Blut zu steigen beginnt
  • aerob-anaerobe Schwelle (Laktatschwelle): höchstmögliche Belastungsintensität, welche unter Aufrechterhaltung eines Gleichgewichtszustandes zwischen der Bildung und dem Abbau (der weiteren oxidativen Verstoffwechselung) von Laktat erbracht werden kann Quelle

🏆 Sakai-Aufgabe 1: Anlegen eines Versuchs


Stellen Sie sich vor, Sie erstellen eine Liste mit zehn Leistungstests, die alle heute auszuführen sind und mittels eines Dictionaries beschrieben werden. Diese Tests haben alle Sie als Versuchsleiter, dasselbe Erstellungsdatum und eine eindeutige Id_Nummer, die mit bei einer beliebigen Variable first_experiment_id beginnt.

  • Schreiben Sie eine Schleife, um die 10 Experimente zu erstellen.
  • Bauen Sie ein try-except-Block ein, um sicherzustellen, dass das Programm bei Eingaben von floats und strings als first_experiment_id nicht abstürzt
  • Nutzen Sie Type Hinting, um den Datentyp der Variablen zu spezifizieren
  • Schreiben Sie eine zweite Schleife, um die ersten fünf Experimente anzuzeigen

  • Geben Sie aus, wie viele Experimente mit einer geraden ID erstellt wurden

  • Kopieren Sie ihren Python Code in die Lösung

✍️ Freiwillige Übungs-Aufgabe

Google Colab