Vorlesung#

Warum Python für wissenschaftliches Rechnen?#

Python ist eine freie, offene, plattformunabhängige, üblicherweise interpretierte, höhere Programmiersprache. Python Code ist gut lesbar und einfach zu lernen. Es gibt sehr viele Pakete und Einsatzgebiete für Python, siehe PyPI - the Python Package Index. Für den Bereich des wissenschaftlichen Rechnens sind insbesondere die SciPy-Pakete nützlich. Python erfreut sich einer großen und wachsenden Beliebtheit und besitzt daher eine umfangreiche und breitgefächert Community. So ziemlich jedes Problem mit Lösung/en findet man z. B. unter stackoverflow. Der Name bezieht sich übrigens auf die englische Komikergruppe Monty Python.

Nachfolgende Lehrveranstaltungen, die ebenfalls Python verwenden:

IDI:

  • im 4. Semester: Lineare Algebra und Operations Research

  • im 5. Semester: Angewandte Statistik und Data Analytics

UMW:

  • im 1. Semester: Mathematik für Umwelttechnik

  • im 2. Semester: Lineare Algebra und Operations Research

  • im 3. Semester: Angewandte Statistik

Zu den Alternativen im Bereich wissenschaftliches Rechnen zählen

  • Julia: frei, offen und plattformunabhängig

  • R: frei, offen und plattformunabhängig

  • Matlab: kommerziell

  • Mathematica: kommerziell

Es gibt, grob gesagt, zwei Typen von Entwicklungsumgebungen:

Installation#

Python und Python Pakete können auf sehr unterschiedliche Art und Weise installiert werden. Wir verwenden die von vielen empfohlene Variante der Python Distribution Anaconda. Um die Anaconda Distribution zu installieren, folgen Sie den Hinweisen entsprechend Ihrem Betriebssystem.

JupyterLab#

Starten Sie anschließen JupyterLab via dem Anaconda Navigator oder einfacher über eine Kommandozeile mit dem Befehl jupyter lab. Das meiste der Oberfläche ist selbsterklärend. Wenn Sie ein (neues) Jupyter-Notebook (Dateiendung .ipynb) starten, wird ein Kernel gestartet. Der Kernel führt Ihre Python Befehle aus und beeinhaltet alle verwendeten Objekte (Variablen, Funktionen, Pakete etc.). Achtung: Verwenden Sie generell für Ordner- und und Dateinamen keine Umlaute, keine Sonderzeichen und keine Leerzeichen!

Ein Jupyter-Notebook besteht aus einer Liste von Zellen (engl. cells). Zellen können im Command Mode oder im Edit Mode bearbeitet werden. Die wichtigsten zwei Zelltypen sind Code und Markdown.

Code Zellen:

  • Wir schreiben unseren Python Code in die Code-Zellen eines Jupyter-Notebooks. Zum Ausführen des Codes einer Code-Zelle drücken Sie Strg+Return oder Shift+Return. Bei der ersten Variante bleibt der Fokus auf der Code-Zelle, bei der zweiten springt man in die nächste Zelle.

  • Der Rückgabewert des letzten Befehls einer Code Input-Zelle wird in einer folgenden Code Output-Zelle ausgegeben. Sie können die Ausgabe mit einem Strichpunkt am Ende des letzten Befehls unterdrücken.

  • Kommentare beginnen mit dem Rautezeichen #.

  • Funktionen haben runde Klammern. Hilfe z. B. zur Funktion print erhalten Sie durch help(print) oder print? oder SHIFT-TAB drücken, wenn der Cursor nach der ersten runden Klammer von print() steht.

  • Eine Liste der von Ihnen definierten Variablen erhalten Sie mit dem magic command %whos. Löschen einzelner Variablen, hier z. B. der Variable x, erfolgt mit dem Befehl del(x). Löschen aller selber definierten Variablen erfolgt mit dem Befehl %reset -s.

  • Tipp: Verwenden Sie die Tabulator-Vervollständigung beim Coden!

  • Tipp: Mit der Tastenkombination Strg+I öffnet sich der sehr hilfreiche Contextual Help Tab.

  • Tipp: Öffnen Sie eine Console für Ihr Notebook, um außerhalb des Notebooks interaktiv Befehle im Namespace des Notebook-Kernels auszuführen.

Markdown Zellen: Markdown ist eine vereinfachte Auszeichnungssprache, die bereits in der Ausgangsform ohne weitere Konvertierung leicht lesbar ist. Sie können in Markdown sehr leicht folgenden strukturierten Text erstellen:

  • Überschriften: Rautesymbol(e) vor der Überschrift

  • Listen: mit Minus-, Plus- oder Sternzeichen

  • Links: mit Syntax [Name](URL)

  • Bilder: mit Syntax ![Name](Pfad-zu-Bilddatei)

  • Mathematische Formeln wie z. B. \(K = \frac{mv^2}{2}\) via dem LaTeX-Code $K = \frac{mv^2}{2}$

  • Tipp: Cheatsheet

Keyboard Shortcuts

Die Keyboard Shortcuts sind in den Menüs neben den Auswahlen angegeben. Hier eine persönliche Auswahl:

Shortcut

Effekt

Ctrl+s

save notebook

Ctrl+Shift+q

close and shutdown notebook

Ctrl+f

Find

Enter

enter edit mode

Escape

enter command mode

Ctrl+Enter

run cell

Shift+Enter

run cell and got to cell below

Alt+Enter

run cell and insert a new cell below

Ctrl+b

toggle left sidebar

in edit mode: Ctrl+Shift+-

split cell

in command mode: m

set cell type to markdown

in command mode: y

set cell type to code

in command mode: d d

delete cell

in command mode: z

undo deleting of cell

in command mode: a

insert cell above

in command mode: b

insert cell below

in command mode:Shift+m

merge selected cells

in command mode: x/c/v

cut/copy/paste cells

in command mode: UP/DOWN arrow

selected previous/next cell

in command mode: i+i

interrupt kernel

in command mode: 0+0

restart kernel

in command mode: Shift+l

toggle all line numbers

Bevor Sie JupyterLab beenden, vergessen Sie nicht, Ihre Jupyter-Notebooks zu speichern, zu schließen sowie die zugehörigen Kernel herunterzufahren. Wenn Sie JupyterLab von der Kommandozeile gestartet haben, beenden Sie JupyterLab mit zweimal Strg+C in der selben Kommandozeile.

Exportieren: Sie könne Jupyter-Notebooks über den Menüeintrag File/Export Notebook As oder über Systembefehle in andere Dateiformate exportieren. Systembefehle können Sie in einem System-Terminal, im Anaconda Command Prompt ausführen, oder in einer Codezelle, wenn Sie ein Rufezeichen vor den Systembefehl setzen. Hier eine Auswahl:

  • HTML: Systembefehl jupyter nbconvert Mein_Notebook.ipynb

  • PDF: Systembefehl jupyter nbconvert --to pdf Mein_Notebook.ipynb

  • LaTeX: Systembefehl jupyter nbconvert --to latex Mein_Notebook.ipynb

  • Python-Script: Systembefehl jupyter nbconvert --to script Mein_Notebook.ipynb

Unter nbconvert finden Sie die Syntax für noch weitere Outputformate und Infos zu evtl. Zusatzsoftware, die für die Konvertierung notwendig ist.

Navigationsbefehle im Dateisystem: Für die Arbeit in einem System-Terminal, dem Anaconda Command Prompt oder in einer Codezelle sind oft folgende Navigationsbefehle nützlich:

  • pwd: print working/current directory

  • ls oder dir: list files in current directory

  • cd DIR: change into absolute or relative directory DIR

  • cd ..: change to parent directory

Extensions: Folgende der vielen JupyterLab Extensions könnten Sie interessieren:

Noch ein Tipp zum Schluss: Schauen Sie mal in diese Liste von Jupyter Notebooks rein.

Einführung in Python für wissenschaftliches Rechnen#

Die folgende Einführung lehnt sich stark an die Kapitel 1 bis 4 des empfehlenswerten Buchs “Programming for Computations - Python. A Gentle Introduction to Numerical Simulations with Python 3.6.” von Svein Linge und Hans Petter Langtangen, 2. Auflage, 2020, an. Hier der Link zum Verlag.

Weitere nützliche Quellen:

Erste Schritte#

Wir schreiben unseren Python Code in die Code-Zellen eines Jupyter-Notebooks. Zum Ausführen des Codes einer Code-Zelle drücken Sie Strg+Return oder Shift+Return. Bei der ersten Variante bleibt der Fokus auf der Code-Zelle, bei der zweiten springt man in die nächste Zelle.

Hinweis: Um Ihren Code bei Bedarf außerhalb der JupyterLab-Umgebung auszuführen, können Sie

  • Ihr Jupyter-Notebook mynotebook.ipynb in ein Python Script mit Endung .py exportieren und anschließend von einem System-Terminal aus mit python mynotebook.py starten oder

  • in einem System-Terminal jupyter nbconvert --to notebook --execute mynotebook.ipynb verwenden.

Code-Kommentare beginnen mit dem Rautezeichen #. Funktionen haben immer runde Klammern. Das Potenzieren von Zahlen wird mit ** gemacht.

Hier ein erstes Beispiel: Der Code berechnet die Höhe \(y(t) = v_0 t - \frac{1}{2}gt^2\) eines Balls zum Zeitpunkt \(t \geq 0\), der zum Zeitpunkt \(t=0\) aus der Höhe \(y = 0\) mit der Geschwindigkeit \(v_0\) vertikal in die Höhe geworfen wird.

# program for computing the height of a ball in vertical motion

v0 = 5     # initial velocity in m/s
g  = 9.81  # acceleration of gravity in m/s^2
t  = 0.6   # time in s

y = v0*t - 0.5*g*t**2  # vertical position in m

print(f"At time t = {t} s the ball is at height y(t) = {y} m.")  # formated printing
At time t = 0.6 s the ball is at height y(t) = 1.2342 m.

Um Funktionen außerhalb der Python Standard Library, in der sich z. B. die Funktion print befindet, zu verwenden, importieren wir die gewünschten Python Pakete in den Namespace unseres Jupyter-Notebooks. Die Pakete NumPy und Matplotlib verwenden wir unter anderen in der Lehrveranstaltung.

# imports of packages into the namespace:
import numpy as np
import matplotlib.pyplot as plt

Die Funktionen und Module des Pakets numpy können nach dem obigen Import mit np.* angesprochen werden. Analoges gilt für die Funktionen von matplotlib.pyplot.

Mit NumPy können wir die Berechnung der vertikalen Position des Balls vektorisieren, und mit Matplotlib können wir diese grafisch darstellen.

t = np.linspace(0, 1, 42)

y = v0*t - 0.5*g*t**2

plt.plot(t, y, '.-m') # plots all y coordinates vs. all t coordinates
plt.xlabel("t (s)")   # places the text t (s) on x-axis
plt.ylabel("y (m)")   # places the text y (m) on y-axis
plt.grid(True)
../_images/Vorlesung_19_0.png

Jetzt aber der Reihe nach!

Import von Paketen#

Anstatt das gesamte Paket some_package mit import some_package as some_prefix mit einem selbstgewählten Präfix some_prefix zu importieren, können auch einzelne Funktionen importiert werden.

from numpy import linspace, pi

# after the import linspace and pi are part of the namespace, e. g.:
linspace(0, 5, 11)
array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. ])
from numpy import cos as cosinus

cosinus(pi)
-1.0

Für den Import von Paketen müssen nicht unbedingt Präfixe wie z. B. np vergeben werden.

import numpy

numpy.linspace(0, 5, 11)
array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. ])

Achtung: Es ist nicht empfohlen, alle Funktionen und Module eines Pakets mit einem Import vom Typ from math import * in den Namespace zu importieren! Warum?

Datentypen - eine Auswahl#

Von den integrierten Datentypen verwenden wir die unten angeführten. Dabei besprechen wir deren Funktionalitäten nicht vollständig!

String: immutable

x = "Hello World"  # or 'Hello World'
print(type(x))
print("Applied " + "Mathematics") # adding is concatenating
<class 'str'>
Applied Mathematics

Integer: immutable

x = 42
print(type(x))
<class 'int'>

Float: immutable

# float is short for floating point number

x = 42.123456789
print(type(x))
<class 'float'>
print(type(12/3)) # result is of type float
<class 'float'>
print(4/4*3)
print(4/(4*3))  # do not forget the brackets if you wanted this.
3.0
0.3333333333333333
# computations:

print(4.5 + 1.5)  # addition
print(4.5 - 5)    # subtraction 
print(2*7.1)      # multiplication
print(2**5)       # exponentiation
print(7.5/3)      # division
print(7.5//3)     # floor division, integer division
print(7.5%3)      # remainder, modulo
6.0
-0.5
14.2
32
2.5
2.0
1.5

List: mutable

# A list is a collection which is ordered and changeable (= mutable).

x = ['Eric', 42, 0.3]
print(type(x))
print(len(x))  # length, i. e. number of items, of the list
<class 'list'>
3
# accssing list items:
print(x[0])     # Python starts counting with 0!
print(x[1:3])   # item with index 1 included, item with index 3 excluded!
print(x[:2])    # items up to index 2 excluded
print(x[1:])    # items starting from index 1 included
print(x[-1])    # last item
print(x[-2])    # second last item
print(x[-2:])   # from the end up to second last item included 
print(x[-2:-1]) #  should be clear now
Eric
[42, 0.3]
['Eric', 42]
[42, 0.3]
0.3
42
[42, 0.3]
[42]
# With the dot notation methods of the object can be accessed.
x.append(137)
x
['Eric', 42, 0.3, 137]
# lists can also be added and multiplied:
x = ['Eric', 42, 0.3]
print(x + ['John', 137])
print(2*x)
['Eric', 42, 0.3, 'John', 137]
['Eric', 42, 0.3, 'Eric', 42, 0.3]

Tuple: immutable

# A tuple is a collection which is ordered and unchangeable (= immutable).

x = ('Eric', 42, 1/3)
print(type(x))
print(x[1])
# But x has for example no append method.
# you can make it a list with 
list(x)
<class 'tuple'>
42
['Eric', 42, 0.3333333333333333]

Dictionary: mutable

x = {"name" : "Eric", 
     "age"  : 42}
print(type(x))
<class 'dict'>
# accessing dictionary items:
x['name']
'Eric'
# adding dict items:

x['course'] = 'AM'
x
{'name': 'Eric', 'age': 42, 'course': 'AM'}

Boolean: immutable

# boolean:

print(type(True))
print(type(False))
print(4 == 12/3)
print(4 >= 3)
print(4  < 3)
print(4 != 3)
print(42 in ['Eric', 42, 1/3])
<class 'bool'>
<class 'bool'>
True
True
False
True
True

Achtung! Zu den unveränderlichen (immutable) Objekten zählen integers, floats, strings und andere, während Numpy Arrays (siehe unten) und Listen Beispiele für veränderliche (mutable) Objekte sind. Das hat folgende wichtige Auswirkungen:

x = 1   # integer is immutable
y = x
y = 4
print(x)
1
# however:
x = [1, 2, 3]   # list is mutable
y = x   # Python creates a reference to the mutable object x
y[0] = 4
print(x)
[4, 2, 3]
# workaround with copying values:
x = [1, 2, 3]
y = x.copy()   # Python creates a new object y
y[0] = 4
print(x)
[1, 2, 3]

NumPy Arrays, short arrays: mutable

  • 1-dimensionale Arrays für Vektorrechnung

  • 2-dimensionale Arrays für Matrizenrechnung

x = np.array([1, 2.1, -5])
print(x)
print(type(x))
print(len(x))     # length, i. e. number of items
print(np.ndim(x)) # number of dimensions
[ 1.   2.1 -5. ]
<class 'numpy.ndarray'>
3
1
# accssing list items works the same as with lists:
print(x[0])     # Python starts counting with 0!
print(x[1:3])   # item with index 1 included, item with index 3 excluded!
print(x[:2])    # items up to index 2 excluded
print(x[1:])    # items starting from index 1 included
print(x[-1])    # last item
print(x[-2])    # second last item
print(x[-2:])   # from the end up to second last item included 
print(x[-2:-1]) # should be clear now
1.0
[ 2.1 -5. ]
[1.  2.1]
[ 2.1 -5. ]
-5.0
2.1
[ 2.1 -5. ]
[2.1]
# With the dot notation many methods be accessed. 
# Use the TAB-key after the dot to see them! Here' an example:
x.mean()
-0.6333333333333333
# Arrays can also be added and multiplied, 
# but now in the sense of vector algebra!
y = np.array([3, -1.9, 42])

x + y # vector addition: elementwise!
array([ 4. ,  0.2, 37. ])
print(x)
print(x + 100)   # adding a scalar to all elements
[ 1.   2.1 -5. ]
[101.  102.1  95. ]
3*x   # multiplying a scalar to all elements
array([  3. ,   6.3, -15. ])

Das innere Produkt zweier Vektoren wird im Englischen oft “dot product” genannt.

np.dot(x, y)
-210.99
# alternatively with the @ operator:
x@y
-210.99
np.cross(x, y) # cross product: only für vectors with 3 elements
array([ 78.7, -57. ,  -8.2])
x*x   # caution: elementwise!
array([ 1.  ,  4.41, 25.  ])
x/x   # elementwise!
array([1., 1., 1.])
x**3  # elementwise!
array([   1.   ,    9.261, -125.   ])
# constructors:

x = np.arange(start = 0, stop = 5, step=2)
print(x)

x = np.linspace(start = 0, stop = 5, num=11)
print(x)

x = np.zeros(4)
print(x)

x = np.ones(7)
print(x)
[0 2 4]
[0.  0.5 1.  1.5 2.  2.5 3.  3.5 4.  4.5 5. ]
[0. 0. 0. 0.]
[1. 1. 1. 1. 1. 1. 1.]
# The thing about copying ... :

x = np.array([1,2,3])
y = x
y[0] = 4
print(x)

x = np.array([1,2,3])
y = x.copy()
y[0] = 4
print(x)
[4 2 3]
[1 2 3]
# 2-dim arrays, aka matrices:

M = np.array([[1, 2, 3],
              [4, 5, 6]])

print(M)
print(np.ndim(M)) # number of dimensions, number of square brackets
print(M.shape)    # number of rows and number of columns
[[1 2 3]
 [4 5 6]]
2
(2, 3)
# accessing items and slices:
print(M[1,2])    # select one element
print(M[1,:])    # select row as 1-dim array
print(M[:,0])    # select columns as 1-dim array
print(M[:,[0]])  # select columns as 2-dim array
6
[4 5 6]
[1 4]
[[1]
 [4]]

Overview of mutable and immutable data types:

  • mutable data types: list, dictionary, array, …

  • immutable data types: int, float, bool, string, tuple, …

Formatiertes Drucken#

Wir verwenden “f-strings” für das formatierte Drucken. Zwei weitere Methoden werden z. B. im Buch von Linge und Langtangen beschrieben. f-strings beginnen mit einem f vor den Anführungszeichen, die den string kennzeichnen. Variablenwerte werden in geschwungenen Klammern eingebunden. Zeichenketten (=strings), die über mehrere Zeilen laufen, können mit dreifachen Anführungszeichen geschrieben werden. \n bewirkt eine neue Zeile im Ausdruck. Mehr zu f-strings finden Sie z. B. im Python f-string tutorial.

my_name  = "Klaus"
my_age   = 47
my_float = 123.123456789

print(f"Hallo! My name is {my_name}.")
print(f"Two of my objects:\n  {my_name = }\n  {my_age = }.") # self-documenting expression
print(f"I am {my_age} years old. These are about {my_age*365} days.")
print(f"""My number in different formats:
  {my_float:.3f} or
  {my_float:10.5f} or
  {my_float:.3e} or
  {my_float:10.2e}""")
Hallo! My name is Klaus.
Two of my objects:
  my_name = 'Klaus'
  my_age = 47.
I am 47 years old. These are about 17155 days.
My number in different formats:
  123.123 or
   123.12346 or
  1.231e+02 or
    1.23e+02

Grafiken#

Wir werden nur wenige Beispiele in diesem Abschnitt aufzeigen. Die große Vielfalt an grafischen Darstellungsmöglichkeiten mit dem Python-Paket Matplotlib finden Sie z. B. unter Matplotlib Gallery. Es gibt neben Matplotlib noch viele andere Grafik-Pakete. Interaktive Grafiken können Sie z. B. mit ipywidgets erstellen.

x = np.linspace(-1, 3, num = 11)
y = -x**2 + 8

plt.figure(figsize=(8,5))
plt.plot(x, y, 'o-g', label='Parabel')
plt.xlabel('Weite $x$ (m)')
plt.ylabel('Höhe $y$ (m)')
plt.ylim(-2, 10)
plt.title('Parabel')
plt.legend(numpoints=1, loc='best')
plt.grid(True)

plt.savefig('/home/kr/lehre/am/web/1_Python/Parabel.pdf')
../_images/Vorlesung_78_0.png
t = np.linspace(-2, 2, 100)
f_values = t**2
g_values = np.exp(t)

plt.figure(figsize=(8,5))
plt.plot(t, f_values, 'r', 
         t, g_values, 'b--')
plt.xlabel('t')
plt.ylabel('f and g')
plt.legend(['$f(t) = t^2$', 
            '$g(t) = e^t$'])
plt.title('Plotting of two functions ($t^2$ and $e^t$)')
plt.grid(True)
plt.axis([-3, 3, -1, 10]);
../_images/Vorlesung_79_0.png

Kontrollstrukturen#

for-Schleife: hat die Struktur

for loop_variable in iterable_object:
    <code line 1>
    <code line 2>
    etc.
# first code line after the loop
# example 1: iteration over a list

for item in ["1", 2, np.pi]:
    print(item)
1
2
3.141592653589793
# example 2: iteration over an array

v = np.linspace(0, 3, 7)
for x in v:
    print(x, end=", ")
0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 
# example 3: using enumerate to get the indices too

my_list= ['Sokrates', 'Platon', 'Aristoteles']
for index, value in enumerate(my_list):
    print(f"{index + 1}. {value}")
1. Sokrates
2. Platon
3. Aristoteles
# list comprehension:

my_list_1 = [-x for x in np.arange(-3, 4)]
print(my_list_1)

my_list_2 = [-x for x in np.arange(-3, 4) if x < 0]
print(my_list_2)

my_list_3 = [-x if x < 0 else x for x in np.arange(-3, 4)]
print(my_list_3)
[3, 2, 1, 0, -1, -2, -3]
[3, 2, 1]
[3, 2, 1, 0, 1, 2, 3]

while-Schleife: hat die Struktur

while some_condition:
    <code line 1>
    <code line 2>
    etc.
# first code line after the loop
# find the first (i. e. in ascending order) data points nearest to zero:
x = np.linspace(-3, 3, num=100)
y = x**2 - 4

plt.plot(x, y, '.-')
plt.xlabel('x')
plt.ylabel('y')

ind = 0  # staring index, proceed to increasing values
sign_change = False
while not sign_change:
    old_sign = np.sign(y[ind])
    ind += 1
    new_sign = np.sign(y[ind])
    if new_sign != old_sign:
        sign_change = True

print(f"y[{ind -1}] = {y[ind - 1]}")
print(f"y[{ind   }] = {y[ind    ]}")
plt.plot(x[ind - 1], y[ind - 1], 'or')
plt.plot(x[ind    ], y[ind    ], 'og')
plt.grid(True)
y[16] = 0.12213039485766775
y[17] = -0.12029384756657491
../_images/Vorlesung_86_1.png

if-elif-else-Abfragen: hat die Struktur

if condition_1: 
    <code line 1>
    <code line 2>
    ...
elif condition_2: # optinonal
    <code line 1>
    <code line 2>
    ...
elif condition_3: # optinonal
    <code line 1>
    <code line 2>
    ...
else:             # optinonal
    <code line 1>
    <code line 2>
    ...
# First line after if-elif-else construction
T = 5 # select a water temperature in degree centigrade

if T <= 20:
    print("Do not swim. Too cold!")
elif T <= 36:
    print("Great, jump in!")
else:
    print("Do not swim. Too hot!")
Do not swim. Too cold!

Tipps:

  • Mit der break-Anweisung gelangen Sie direkt zur ersten Codezeile nach einer Schleife.

  • Mit der continue-Anweisung fahren Sie direkt mit der nächsten Iteration fort.

Funktionen#

Eine Funktion hat die Struktur

def function_name(pos1, pos2, ..., kw1=default_1, kw2=default_2, ...):   # function header
    """This is a docstring"""         # function body
    <code line>                       # function body
    <code line>                       # function body
    ...                               # function body
    return result_1, result_2, ...    # last line in function body
# First line after function definition

Die return Zeile, die die Rückgabewerte definiert, und die Argumente pos1, ..., kw1 = ... sind jeweils optional. Eine Funktion kann daher auch so aussehen:

def my_function():
    """function without arguments 
    and without return values"""
    print("Ahoi!")
my_function()
Ahoi!

Der Doc-String ist der Hilfetext zur Funktion und kann aufgrund der dreifachen doppelten Anführungszeichen über mehrere Zeilen laufen.

help(my_function)
Help on function my_function in module __main__:

my_function()
    function without arguments 
    and without return values

Argumente:

  • Die Argumente pos1, pos2, ... sind sogenannte positional parameters.

  • Die Argumente kw1, kw2, ... sind sogenannte keyword parameters.

Positional parameters müssen vor keyword parameters angeführt werden. Die Werte der Parameter, die beim Aufruf der Funktion übergeben werden heißen positional bzw. keyword arguments. Die Werte default_1 und default_2 sind Default-Werte, die verwendet werden, wenn keine entsprechenden keyword arguments beim Aufruf der Funktion angegeben werden.

Hier ein Beispiel:

def my_greet(first_name, second_name, msg="Good morning!"):
    print(f"Hello {first_name} {second_name}! {msg}")
my_greet('Albert', 'Einstein')
my_greet('Albert', 'Einstein', msg='Good night!')
my_greet('Albert', 'Einstein', 'Good night!')
my_greet('Good night!', 'Albert', 'Einstein')  # gives no error, but is not what you intended!
my_greet(first_name="Albert", second_name='Einstein', msg="How do you do?")
my_greet("Albert", second_name='Einstein', msg="How do you do?")
my_greet(msg = "How do you do?", first_name="Albert", second_name='Einstein') 
my_greet(second_name='Einstein', first_name="Albert", msg = "How do you do?")
# my_greet(msg="How do you do?", "Albert", "Einstein") # gives "SyntaxError: positional argument follows keyword argument"!
Hello Albert Einstein! Good morning!
Hello Albert Einstein! Good night!
Hello Albert Einstein! Good night!
Hello Good night! Albert! Einstein
Hello Albert Einstein! How do you do?
Hello Albert Einstein! How do you do?
Hello Albert Einstein! How do you do?
Hello Albert Einstein! How do you do?

Erkenntnisse:

  • Keyword arguments können auch ohne keyword angegeben werden.

  • Es ist möglich, die Namen von positional parameters als keywords zu verwenden.

  • Die Reihenfolge von keyword arguments kann geändert werden.

  • Eine Funktion kann mit positional oder mit keyword arguments oder aus einer Mischung aufgerufen werden. Bei der Mischvariante müssen die positional arguments jedoch zuerst angeführt werden müssen.

  • Solange in einem Funktionsaufruf für alle Argumente keywords verwendet werden, kann eine beliebige Reihenfolge der Argumente verwendet werden.

Geltungsbereich von Variablen (engl. Scope of Variables):

if "y" in locals():
    del(y)
k =   2  # global variable defined in the main program
d = 100  # global variable defined in the main program

def my_line_value(x):
    # The global variables k and d are known from "outside".
    k = 10  # k is now also the name of a local variable. 
    # The assignment k = 10 changes only the local variable inside the function.
    # The "name-brother" global variable outside is not affected!
    print(f"inside values: k = {k}, d = {d}")  
    y = k*x + d 
    print(f"inside computation {y} = {k}*{x} + {d}")
    return y

x = 3

print(f"outside values before function call: k = {k}, d = {d}")
print(f"return value of input {x}: {my_line_value(x)}.")
print(f"outside values after function call: k = {k}, d = {d}")
# print(y) # gives "NameError: name 'y' is not defined"
outside values before function call: k = 2, d = 100
inside values: k = 10, d = 100
inside computation 130 = 10*3 + 100
return value of input 3: 130.
outside values after function call: k = 2, d = 100

Achtung: Veränderliche (d. h. mutable) äußere Objekte werden in Funktionen geändert!

x = [1, 2]  # mutable object
print(f"outside value: {x = }")

def change(y):
    y[0] = 4
    print(f"inside value:  {y = }")

change(x)
print(f"outside value: {x = }")
outside value: x = [1, 2]
inside value:  y = [4, 2]
outside value: x = [4, 2]
# Auch ohne Übergabe:

x = [1, 2]  # mutable object
print(f"outside value: {x = }")

def change():
    x[0] = 4
    print(f"inside value:  {x = }")

change()
print(f"outside value: {x = }")
outside value: x = [1, 2]
inside value:  x = [4, 2]
outside value: x = [4, 2]
# Aber:

x = 1  # immutable object
print(f"outside value: {x = }")

def change():
    x = 4
    print(f"inside value:  {x = }")

change()
print(f"outside value: {x = }")
outside value: x = 1
inside value:  x = 4
outside value: x = 1

Lambda Funktionen: sind eine Möglichkeit, auf kompakte Art “Einzeiler”-Funktionen zu definieren.

g = lambda x: x**2
# is equivalent to:
# def g(x):
#     return x**2

g(3)
9

Allgemeine Form einer Lambda Funktion:

function_name = lambda arg1, arg2, ... : <some_expression>

Funktionen als Argumente von Funktionen:

def my_plot(fct, start=-1, stop=3, legend=True):
    x = np.linspace(start, stop, num=100)
    y = fct(x)
    plt.plot(x, y, label=fct.__name__)
    plt.xlabel('x')
    plt.ylabel('y')
    if legend:
        plt.legend()
    plt.grid(True)
my_plot(np.sin, 0, 2*np.pi)
../_images/Vorlesung_111_0.png
my_plot(lambda x: x - x**2, legend=False)
../_images/Vorlesung_112_0.png

Daten IO#

Es gibt sehr viele Arten von Daten und Dateitypen. Wir beschränken uns hier auf das Laden und Speichern von einfachen CSV-Dateien. Als Beispiel nehmen wir die global temperature anomalies with respect to the 20th century average, genauer die dort bereitgestellte csv-Datei data.csv. Anstatt die Daten Zeile für Zeile einzulesen und zu parsen, verwenden wir das sehr mächtige und allen Data Scientists empfehlenswerte Paket pandas.

import pandas as pd
filepath = "/home/kr/lehre/am/web/1_Python/data.csv"
df = pd.read_csv(filepath, skiprows=4, index_col=0) # read file into a pandas DataFrame
df
Value
Year
1880 -0.09
1881 0.03
1882 -0.14
1883 -0.16
1884 -0.22
... ...
2016 0.95
2017 0.88
2018 0.82
2019 0.87
2020 0.95

141 rows × 1 columns

# We can directly plot the DataFrame ...

df.plot(figsize=(8,6), grid=True, marker='.')
plt.ylabel('°C');
../_images/Vorlesung_116_0.png
# ... or we convert the data to data types we already know ...

years  = df.index.values     # to numpy array
values = df['Value'].values  # to numpy array

# ... and plot these:

plt.figure(figsize=(8,6))
plt.plot(years, values, '.-', label='Values')
plt.xlabel('Year')
plt.ylabel('°C')
plt.legend()
plt.grid(True)
../_images/Vorlesung_117_0.png
# exporting data to files:

# from DataFrame to Excel:
my_filepath = "/home/kr/lehre/am/web/1_Python/data_exported_with_pandas.xlsx"
df.to_excel(my_filepath)  

# from numpy array to csv:
my_filepath = "/home/kr/lehre/am/web/1_Python/data_exported_with_numpy.csv"
np.savetxt(my_filepath, values, delimiter=',') # using numpy