#!/usr/bin/env python # coding: utf-8 # Python Einführungskurs für das Physikalische Anfängerpraktikum der Universität Heidelberg | [Startseite](index.ipynb) # # --- # # 103 - Funktionen und Module # - [Funktionen sind die Bausteine eines Programms](#Funktionen-sind-die-Bausteine-eines-Programms) # - [Aufgabe 1 - Mehr Primzahlen](#Aufgabe-1---Mehr-Primzahlen) # - [Aufgabe 2 - Fakultät](#Aufgabe-2---Fakultät) # - [Funktionen aus Modulen importieren](#Funktionen-aus-Modulen-importieren) # - [Aufgabe 3 - Gaussian](#Aufgabe-3---Gaussian) # Wir können nun bereits Variablen setzen und _control flow_ Anweisungen geben um kleine Programme zu schreiben. Damit können wir schon vieles berechnen! Wenn wir jedoch komplexere Berechnungen ausführen möchten, kann ein Programm schnell sehr lang und unleserlich werden. Eine wichtige Regel in der Programmierung ist es daher, **Wiederholung zu vermeiden**. Dazu definieren wir wiederverwendbare **Funktionen** und verwenden schon vorhandene Funktionalität aus **Modulen**. # # ## Funktionen sind die Bausteine eines Programms # # Eine Funktion ist ein Codeblock, der eine abgeschlossene Aufgabe erfüllt. Funktionen haben immer einen **Namen**, können **Argumente** annehmen und **Rückgabewerte** zurückgeben. In Python haben Funktionen folgende Syntax: # # ```python # def function_name(arguments): # # code here # return values # ``` # # Beachtet wieder die Abgrenzung des Codeblocks durch Einrückung, wie wir es bereits bei `if`-Abfragen und Schleifen kennengelernt haben. # # Ist die Funktion definiert, können wir sie mit folgender Syntax aufrufen: # # ```python # function_name(arguments) # ``` # # Mit Funktionen können wir ein komplexes Problem in lösbare Teilprobleme zerlegen, die wir dann zu einem vollständigen Programm zusammensetzen. # # > **Beispiel:** Haben wir einmal eine Funktion geschrieben, die eine Liste sortiert, können wir immer darauf zurückgreifen, anstatt den Code jedes mal aufs Neue zu schreiben. # # > **Hinweis:** Nur weil du Code in Funktionen auslagern _kannst_ solltest du das nicht immer tun. Schreibe dann eine Funktion, wenn du dadurch Wiederholungen vermeidest oder das Programm klarer strukturierst. **Häufig ist eine Funktion dann sinnvoll, wenn du ihr einen deskriptiven Namen geben kannst.** # ### Argumente und Rückgabewerte # # Eine Funktion kann mehrere Argumente annehmen... # In[ ]: def add(a, b): return a + b print(add(1,3)) print(add(1.,3.2)) print(add(4,3.)) # ... und kann mehrere Werte zurückgeben: # In[ ]: def double_and_halve(value): return value * 2, value / 2 print(double_and_halve(5)) # Die Rückgabewerte können wir einer oder mehreren Variablen zuweisen: # In[ ]: d, h = double_and_halve(5.) print(d) print(h) # Funktionen können **andere Funktionen aufrufen**: # In[ ]: def do_a(): print("doing A") def do_b(): print("doing B") def do_a_and_b(): do_a() do_b() do_a_and_b() # Argumente können auch einen **_default_-Wert** besitzen und damit **optional** sein: # In[ ]: def say_hello(to_name="World"): print("Hello {}!".format(to_name)) say_hello() say_hello("Alice") # Argumente können in der **Reihenfolge** gegeben werden, in der die Funktion sie definiert, oder mit Angabe des Argumentnamens in beliebiger Reihenfolge: # In[ ]: def say_hello(to_name="World", my_name=None): if my_name is None: print("Hello {}!".format(name)) else: print("Hello {}! My name is {}.".format(to_name, my_name)) say_hello("Alice", "Bob") say_hello(my_name="Bob", to_name="Alice") # ## Aufgabe 1 - Mehr Primzahlen # # Definiere eine Funktion mit dem Namen `is_prime`, die eine Zahl annimmt und `True` zurückgibt wenn die Zahl eine Primzahl ist, bzw. `False` wenn nicht. # # Kopiere dazu deinen Code um Primzahlen zu finden von zuvor und verändere ihn entsprechend. # In[ ]: def is_prime(n): for m in range(2, n): if n % m == 0: return False return True # In[ ]: from nose.tools import assert_true, assert_false try: is_prime except NameError: raise NameError("Es gibt keine Funktion mit dem Namen 'is_prime'. Stelle sicher, dass deine Funktion so benannt ist.") assert_true(is_prime(47), "Die Zahl 47 sollte eine Primzahl sein. Prüfe den Code deiner Funktion.") assert_false(is_prime(48), "Die Zahl 48 sollte _keine_ Primzahl sein. Prüfe den Code deiner Funktion.") print("Klappt.") # ## Aufgabe 2 - Fakultät # # Schreibe eine Funktion mit dem Namen `factorial`, die eine Zahl annimmt und die Fakultät `n! = n*(n-1)*...*3*2*1` dieser Zahl zurückgibt. # # **Hinweis:** Du kannst zunächst versuchen, die Aufgabe mit einer Schleife zu lösen. Funktionen können sich jedoch auch selbst aufrufen (sog. _rekursive_ Funktionen). Versuche eine Funktion zu schreiben, die _keine Schleife_ verwendet! # In[ ]: def factorial(n): if n==1: return n return n * factorial(n-1) # In[ ]: from nose.tools import assert_equal try: factorial except NameError: raise NameError("Es gibt keine Funktion mit dem Namen 'factorial'. Stelle sicher, dass deine Funktion so benannt ist.") assert_equal(factorial(1), 1, "1! sollte 1 ergeben. Prüfe den Code deiner Funktion.") assert_equal(factorial(2), 2, "2! sollte 2 ergeben. Prüfe den Code deiner Funktion.") assert_equal(factorial(5), 120, "5! sollte 120 ergeben. Prüfe den Code deiner Funktion.") print("🙌 Funktioniert!") # ### Einzeilige `lambda`-Funktionen sind praktisch für Mathematik # # Mit der `lambda`-Funktionssyntax können wir Funktionen in nur einer Zeile definieren: # # ```python # function_name = lambda arguments: return_value # ``` # # Das ist für mathematische Funktionen häufig sehr praktisch: # In[ ]: linear = lambda x, a, b: a * x + b linear(0, a=1, b=1) # ### Variablen sind dort verfügbar, wo sie definiert wurden # # Argumente von Funktionen und Variablen, die im Codeblock der Funktion definiert wurden, sind nur innerhalb der Funktion verfügbar (**_local scope_**): # In[ ]: def do_something(): local_var = 1 # Diese Variable ist innerhalb der Funktion definiert... # ... und ist außerhalb nicht verfügbar: print(local_var) # Variablen, die zum Zeitpunkt des Funktionsaufrufs außerhalb der Funktion definiert sind, sind ebenfalls in der Funktion verfügbar (**_global scope_**): # In[ ]: PI = 3.14 # Diese Variable ist global definiert... # ...und kann innerhalb von Funktionen verwendet werden: degrees_to_radians = lambda degrees: degrees / 180 * PI degrees_to_radians(90) # > **Verwende in Funktionen nur solche Variablen aus dem _global scope_, die während der Ausführung des Programms konstant bleiben.** Wenn die Funktion Input-Parameter benötigt solltest du sie der Funktion immer als Argumente übergeben. # # > Per Konvention schreiben wir Konstanten in Python in Großbuchstaben wie `PI`. # ### Lokale Variablen werden bevorzugt # Wenn eine Variable sowohl global als auch lokal definiert ist, wird die lokale Variable bevorzugt: # In[ ]: a = 1 def show_var(): a = 2 print(a) show_var() # Hier wird die lokale Variable verwendet... print(a) # ... und hier die Globale. # ## Funktionen aus Modulen importieren # Die beruhigende Nachricht ist: viele Probleme wurden schon gelöst. Für häufige Aufgaben, wie bspw. das Sortieren von Listen, existieren sogar hochoptimierte und getestete Lösungen, die wir tunlichst verwenden sollten, anstatt unsere eigene zu schreiben! # # Abgesehen von einigen grundlegenden Datentypen und Funktionen wie `print` oder `len` sind diese Funktionen nicht in der Python Standard Library enthalten sondern in **Modulen** ausgelagert. Mit der folgenden Syntax können wir ein Modul **importieren**, um auf die enthaltenen Funktionen zugreifen zu können: # # ```python # import module # module.function_name() # ``` # # Häufig verwendeten Modulen können wir einen abgekürzten Namen geben: # # ```python # import module as m # m.function_name() # ``` # # Wir können auch nur einzelne Funktionen eines Moduls importieren: # # ```python # from module import function_name # function_name() # ``` # Anstatt die Funktion `factorial` aus der obigen Aufgabe selbst zu schreiben, können wir nun einfach die gleichnamige Funktion aus dem Modul `math` verwenden: # In[ ]: import math math.factorial(5) # > Hinweis: Um herauszufinden welche Funktionen ein Modul zur Verfügung stellt, kannst du wieder die ``-Vervollständigung im Jupyter Notebook verwenden: # In[ ]: import math # Importiere das Modul, indem du diese Zelle ausführst # In[ ]: #math. # Entferne das '#'-Symbol und drücke die -Taste nach dem Punkt # Die `from`-`import`-Syntax ist insbesondere für mathematische Ausdrücke hilfreich, sodass wir das Modul nicht immer schreiben müssen: # In[ ]: from math import cos, sin from math import pi as PI x = lambda r, phi, theta: r * cos(phi) * sin(theta) x(1, 0, PI/2) # ## Module bieten vielseitige Funktionalität # Neben eingebauten Modulen wie `math` haben Python-Entwickler eine Vielzahl von Modulen für jeden Anwendungsbereich geschrieben. So können wir mit wenigen Zeilen Code äußerst komplexe Programme schreiben. # # > Beispielsweise lesen wir mit `numpy` unseren Datensatz ein, berechnen mit `scipy` einen Fit und plotten beides mit `matplotlib`. Den Umgang mit diesen Modulen lernen wir im nächsten Kapitel. # # Funktionen zur Berechnung von Mittelwert und Standardabweichung stellt bspw. `numpy` zur Verfügung: # In[ ]: import numpy as np li = [1,2,7,3,1,3] np.mean(li), np.std(li) # > Es gibt natürlich nicht nur Module für die wissenschaftliche Anwendung. Python wird höchst vielseitig eingesetzt, sodass du bspw. auch # > - eine [Webseite erstellen](http://getpelican.com), # > - einen [Webserver programmieren](http://www.djangoproject.com) oder # > - ein [Spiel entwickeln](http://www.pygame.org) kannst! # ## Aufgabe 3 - Gaussian # # Importiere die Funktionen `exp` aus `numpy` oder `math`. # # Definiere eine **einzeilige** (`lambda`-) Funktion mit dem Namen `gaussian`, welche die Argumente `x`, `mu`, `sigma` und `A` annimmt und den Wert $$A\,\mathrm{exp}\!\left(\frac{(x-\mu)^2}{2\sigma^2}\right)$$ zurückgibt. # In[ ]: from numpy import exp, sqrt gaussian = lambda x, mu, sigma, A: A * exp((x-mu)**2/2/sigma**2) # In[ ]: from nose.tools import assert_equal, assert_almost_equal try: exp except NameError: raise NameError("Es gibt keine Funktion mit dem Namen 'exp'. Hast du die Funktion mit der `from module import function_name` Syntax importiert?") try: gaussian except NameError: raise NameError("Es gibt keine Funktion mit dem Namen 'gaussian'. Stelle sicher, dass deine Funktion so benannt ist.") assert_equal(gaussian(x=0, mu=0, sigma=1, A=1), 1, "gaussian(x=0, mu=0, sigma=1, A=1) sollte 1 ergeben. Prüfe den Code deiner Funktion.") assert_almost_equal(gaussian(x=0, mu=1, sigma=2, A=3), 3.4, 1, "gaussian(x=0, mu=0, sigma=1, A=1) sollte 1 ergeben. Prüfe den Code deiner Funktion.") print("👍 Stimmt so.") # --- # # Nun kannst du vollständige Programme schreiben und Funktionen aus Modulen verwenden. Erinnere dich daran - du musst nicht alles selbst schreiben! Baue lieber auf der Vorarbeit von schlauen Entwicklern auf der ganzen Welt auf, die schon hochoptimierte und getestete Lösungen für viele Probleme geschrieben haben. [giyf](http://www.google.de). # # In den nächsten drei Lektionen lernen wir die Grundlagen jeweils eines Moduls, das in der wissenschaftlichen Programmierung mit Python allgegenwärtig ist und beginnen mit dem Numerik-Modul _Numpy_. # # [Startseite](index.ipynb) | [**>> 201 - Numerik mit Numpy**](201%20-%20Numerik%20mit%20Numpy.ipynb)