Python Einführungskurs für das Physikalische Anfängerpraktikum der Universität Heidelberg | Startseite


103 - Funktionen und Module

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:

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:

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:

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:

import module
module.function_name()

Häufig verwendeten Modulen können wir einen abgekürzten Namen geben:

import module as m
m.function_name()

Wir können auch nur einzelne Funktionen eines Moduls importieren:

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 <TAB>-Vervollständigung im Jupyter Notebook verwenden:

In [ ]:
import math # Importiere das Modul, indem du diese Zelle ausführst
In [ ]:
#math.<TAB> # Entferne das '#'-Symbol und drücke die <TAB>-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

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.

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 | >> 201 - Numerik mit Numpy