4 – Funktionen

Einführung in Python und PsychoPy

Autor

Clemens Brunner

Veröffentlicht

27. Oktober 2022

Allgemeines

Funktionen kann man sich wie “Mini-Programme” bzw. “Mini-Scripts” vorstellen. Sie gruppieren mehrere Anweisungen zu einem zusammengehörigen Block. Eine Funktion in Python ist durchaus mit einer mathematischen Funktion vergleichbar, wie zum Beispiel der Quadratfunktion \(f(x) = x^2\). Diese mathematische Funktion \(f\) berechnet aus einem gegebenen Wert \(x\) einen neuen Wert \(x^2\). Ein weiteres Beispiel ist die Quadratwurzelfunktion \(f(x) = x^{\frac{1}{2}}\), welche die Quadratwurzel einer Zahl \(x\) berechnet.

Auch in Python hat eine Funktion eine klar definierte Aufgabe. Hier dienen Funktionen unter anderem dazu, Programmcode zu strukturieren (was die Lesbarkeit erhöht) und wiederverwendbar zu machen. Funktionen machen Programme in der Regel auch kürzer, weil wiederholt auszuführender Code in Funktionen ausgelagert werden kann. Sollten einmal Änderungen notwendig sein, muss man diese nur an einer Stelle innerhalb der Funktion vornehmen. Funktionen lassen sich außerdem auch in anderen Programmen wiederverwenden.

Hinweis

Einige Funktionen wie print und type haben wir bereits in den vorigen Einheiten kennengelernt.

Funktionsaufruf

Eine Funktion aufrufen bedeutet, dass der in ihr enthaltene Code (also das “Mini-Programm”) ausgeführt wird – die Funktion “verrichtet” also ihre Arbeit.

In Python ruft man eine Funktion mit ihrem Namen gefolgt von einem runden Klammernpaar auf. Innerhalb der Klammern werden die Argumente für die Funktion übergeben (falls notwendig). Mit Argumenten übergibt man der Funktion Werte, welche diese für die Ausführung benötigt. Beispielsweise benötigt die Funktion type ein Argument, denn sonst kann sie ihre Aufgabe nicht erfüllen – nämlich den Typ des übergebenen Arguments zu bestimmen. Gleichermaßen braucht auch die Funktion math.sqrt aus dem math-Modul ein Argument, damit diese die Wurzel des Arguments (eine Zahl) berechnen kann.

Hier sind zwei Beispiele für Aufrufe zweier bereits bekannter Funktionen mit jeweils einem Argument:

print("Hello")
Hello
type("Hello")
str

Es gibt aber auch Funktionen, die man ohne Argument aufrufen kann (der folgende Funktionsaufruf bewirkt eine Ausgabe einer Leerzeile am Bildschirm):

print()

Wie viele Argumente eine Funktion tatsächlich benötigt, hängt ganz alleine von der jeweiligen Funktion ab (dies ist in ihrer Dokumentation nachzulesen).

Lässt man die Klammern weg, wird die Funktion nicht aufgerufen – es wird dann lediglich der Wert (also das Funktionsobjekt) angezeigt, wenn man Python im interaktiven Modus verwendet.

print
<function print>

Dabei handelt es sich um einen Namen, welcher auf ein Funktionsobjekt verweist:

type(print)
builtin_function_or_method

Das Prinzip der Namen in Python haben wir bereits in der letzten Einheit kennengelernt. Es ist unerheblich, auf welches Objekt ein Name verweist, dies kann eine Ganzzahl, eine Funktion, oder jedes beliebige andere Objekt sein.

Funktionsdefinition

Wir sind glücklicherweise nicht auf bereits existierende Funktionen beschränkt, sondern können selbst unsere eigenen Funktionen schreiben. In Python wird eine Funktion wie folgt definiert:

def function_name(parameter1, parameter2, ...):
    <do something>
    ...
    <optionally return something>

Eine Funktionsdefinition beginnt immer mit dem Schlüsselwort def. Danach folgt der (frei wählbare) Funktionsname. Die PEP8-Konvention schreibt vor, dass Funktionsnamen aus Kleinbuchstaben getrennt mit Unterstrichen bestehen sollten, z.B.:

def test_function

Nach dem Funktionsnamen folgt ein Paar runder Klammern. Innerhalb dieser können von der Funktion benötigte Parameter aufgelistet werden (mehrere Parameter werden durch Kommas voneinander getrennt). Die Parameter werden dann mit spezifischen Werten, welche beim Aufruf übergeben werden (den sogenannten Argumenten), ersetzt. Es gibt aber auch Funktionen, die keine Parameter haben – die beiden runden Klammern müssen aber immer vorhanden sein:

def test_function()  # ohne Parameter

Falls eine Funktion zwei Parameter haben soll, würde man dies wie folgt anschreiben:

def test_function(n, v)  # zwei Parameter namens n und v

Unabhängig von den Parametern schließt ein Doppelpunkt den sogenannten Funktionskopf ab:

def test_function(n, v):

Nun folgt der Code, welcher von der Funktion ausgeführt wird wenn diese aufgerufen wird – dieser Code muss eingerückt sein. Man spricht hier vom sogenannten Funktionskörper. Im Funktionskörper kann man insbesondere alle Parameter wie normale Namen verwenden – diese sind nur innerhalb der Funktion vorhanden und erhalten die Werte der Argumente, welche beim Funktionsaufruf übergeben wurden.

Wichtig

Eine Funktion kann Parameter haben, welche bei der Definition aufgelistet werden müssen. Wenn man die Funktion aufruft, muss man für alle Parameter konkrete Werte übergeben – diese konkreten Werte nennt man Argumente.

Das folgende Beispiel definiert eine Funktion namens test_function, welche zwei Zeilen Code im Funktionskörper aufweist:

def test_function():
    s = "Hello world!"
    print(s)

Man beachte, dass die Funktion hier lediglich definiert wurde, d.h. sie wurde noch nicht ausgeführt. Sie ist aber dem Python-Interpreter ab jetzt bekannt (d.h. es existiert ein Name test_function, welcher auf ein Funktionsobjekt verweist):

test_function
<function __main__.test_function()>
type(test_function)
function

Aufgerufen kann unsere Funktion nun wie folgt werden (die runden Klammern sind essentiell):

test_function()
Hello world!

Im Fall einer Funktion mit Parametern würden die Definition und der Aufruf so aussehen:

def test_function_2(n, v):  # Definition, zwei Parameter
    if v:
        print(n)
test_function_2("Hello world!", True)  # Aufruf mit konkreten Argumenten
Hello world!
test_function_2("Hi!", False)  # weiterer Aufruf mit anderen Argumenten

Übergibt man beim Aufruf dieser Funktion nicht genau die erwartete Anzahl an Argumenten, bekommt man einen Fehler:

test_function_2()  # zwei Argumente erwartet, aber keine übergeben!
TypeError: test_function_2() missing 2 required positional arguments: 'n' and 'v'
Tipp

Es ist üblich, gleich in der ersten Zeile des Funktionskörpers eine kurze Beschreibung der Funktion in einem sogenannten Docstring anzugeben (dieser kann sich auch über mehr als eine Zeile erstrecken). Ein Docstring wird von jeweils drei Anführungszeichen """ umschlossen. Das Vorhandensein eines Docstrings ändert nichts an der Funktionsweise, dient aber der Dokumentation und gehört zum guten Coding-Stil dazu, wie folgendes Beispiel demonstriert:

def test_function():
    """Print hello world."""
    s = "Hello world!"
    print(s)

Rückgabewerte

Funktionen können Werte (Ergebnisse) zurückgeben. In beiden soeben definierten Funktionen test_function und test_function_2 wird allerdings kein Wert explizit zurückgegeben (d.h. die Funktionen führen nur Code aus und geben None zurück). Diese beiden Funktionen haben also keinen Wert wenn man sie aufruft, sie sind also keine Ausdrücke.

Wenn eine Funktion explizit einen Wert (ein Ergebnis) zurückgeben soll, verwendet man dazu das Schlüsselwort return gefolgt vom gewünschten Rückgabewert:

def add_one(number):
    """Increment a given number by one."""
    return number + 1

Wenn die Funktion aufgerufen wird, dann wird ihr Code ausgeführt bis die Zeile mit return erreicht wird. Diese bewirkt, dass die Funktion sofort verlassen und der angegebene Wert zurückgegeben wird. Sollten also nach dem return-Befehl noch weitere Zeilen Code im Funktionskörper folgen, werden diese nicht mehr ausgeführt.

add_one(5)
6

Ein Funktionsaufruf wird also auf seinen Rückgabewert reduziert. Nun kann man diesem auch einen Namen geben:

x = add_one(9)
x
10

Oder man kann den Wert einer Funktion (entspricht dem Rückgabewert) auch explizit am Bildschirm ausgeben (d.h. als Argument der print-Funktion übergeben):

print(add_one(122))
123

Man kann überall dort, wo man einen Wert angeben kann, auch einen Ausdruck (eine beliebige Kombination aus Werten und Operatoren) einsetzen – eben auch eine Funktion, die einen Wert zurückgibt. Dies wird in der Informatik als Komposition bezeichnet. Die Werte werden der Reihe nach (von innen nach außen) verarbeitet und eingesetzt, bis zum Schluss ein einziger Wert übrig bleibt:

add_one(add_one(add_one(1)))
4

Argumente

Default-Argumente

Eine Funktion kann keine Parameter haben oder eine bestimmte (bzw. auch unbestimmte) Anzahl an Parametern erwarten. Wenn eine bestimmte Anzahl an Parametern im Funktionskopf definiert wird, können einzelnen Parametern Standardwerte (sogenannte Default-Argumente) zugewiesen werden. Dies bedeutet, dass die Funktion auch mit weniger Argumenten als erwartet aufgerufen werden kann, wenn für die fehlenden Argumente Standardwerte existieren.

def add(number, increment=1):  # das Default-Argument für increment ist 1
    return number + increment

Die Funktion kann jetzt mit zwei Argumenten oder nur mit dem ersten Argument aufgerufen werden:

add(7, 1)
8
add(7)  # hier wird das Default-Argument verwendet
8
add(7, 3)
10

Keyword-Argumente

Funktionen können auch so aufgerufen werden, dass die Namen der Parameter gemeinsam mit den Argumenten in der Form kwarg=value explizit hingeschrieben werden. Man spricht dann von Keyword-Argumenten (nicht zu verwechseln mit den Python-Schlüsselwörtern). D.h. die obige Funktion add kann auch so aufgerufen werden:

add(number=5)
6
add(number=5, increment=2)
7
add(increment=2, number=5)
7

Dies dient einerseits der besseren Lesbarkeit, da unmittelbar klar ist, welche Parameter welche konkreten Argumente erhalten. Andererseits kann man so die Argumente auch in beliebiger Reihenfolge übergeben.

Wenn der Argumentname nicht angegeben wird, wird die Position des Arguments bei der Zuweisung herangezogen. Man spricht in diesem Fall von positionalen Argumenten. Man kann positionale und Keyword-Argumente beim Funktionsaufruf auch mischen, aber alle positionalen Argumente müssen vor dem ersten Keyword-Argument kommen.

add(5, increment=2)
7

Zur Veranschaulichung dient folgende etwas komplexere Funktionsdefinition:

def test(name, number, exponent=5, skip=7, text="Hello"):
    print(text, name, end=" ")
    return number**exponent - skip

Die folgenden drei Funktionsaufrufe verwenden nur positionale Argumente:

test("Python", 2)
Hello Python 
25
test("Python", 3)
Hello Python 
236
test("Test", 3, 4)
Hello Test 
74

Keyword-Argumente sind sehr praktisch, wenn man für die meisten Parameter deren Default-Argumente verwenden möchte, aber z.B. für einen einzigen Parameter einen anderen Wert setzen will. Dann muss man nämlich nicht alle Argumente der Reihe nach übergeben, sondern nur jene, für die man andere Werte als die Standardwerte haben möchte:

test("Test", 2, skip=2)
Hello Test 
30

Ohne Keyword-Argumente würde derselbe Aufruf nämlich wie folgt aussehen:

test("Test", 2, 5, 2)
Hello Test 
30

Man muss also in diesem Beispiel ein zusätzliches Argument 5 (für den Parameter exponent) übergeben, wenn man positionale Argumente verwendet.

Gültigkeitsbereiche (Scopes)

Alles, was innerhalb einer Funktion definiert wird, ist nur innerhalb dieser Funktion sichtbar und zugreifbar. Man spricht von einem lokalen Scope, welcher sich auf die Funktion und weitere untergeordnete Bereiche erstreckt. Gültigkeitsbereiche entsprechen in Python im Prinzip den Einrückungen.

def test():
    s = 15  # s ist nur lokal in der Funktion definiert
    print(s)

test()
15
print(s)  # außerhalb der Funktion existiert s nicht
NameError: name 's' is not defined

Umgekehrt kann man aber sehr wohl auf Namen, die außerhalb eines Gültigkeitsbereichs definiert wurden, zugreifen:

s = 15  # globales s

def test():
    print(s)  # s aus dem globalen Scope ist zugänglich

test()
print(s)  # s existiert global
15
15

Wenn man innerhalb einer Funktion einen neuen Namen erstellt, welcher auch außerhalb existiert, dann ist nur der neue lokale Name zugänglich (und der Name von außerhalb wird innerhalb der Funktion versteckt). Im folgenden Beispiel verweisen die beiden Namen s auf unterschiedliche Objekte:

s = 15  # globales s

def test():
    s = 12  # lokales s ändert globales s nicht, versteckt es aber in der Funktion
    print(s)

test()
print(s)
12
15

Besonders seltsam mutet folgendes Beispiel an, welches eine Fehlermeldung verursacht:

s = 15  # globales s

def test():
    print(s)  # lokales s existiert noch nicht - daher Fehler!
    s = 12  # lokales s
    print(s)

test()
print(s)
UnboundLocalError: local variable 's' referenced before assignment

Die Zeile print(s) in der Funktion verursacht einen Fehler, weil Python aufgrund der folgenden Zeile bereits weiß, dass s ein lokaler Name sein wird. Daher kann print(s) auch nicht funktionieren, weil s in der ersten Zeile noch nicht existiert! Hier mischt Python also nicht zwischen globalen und lokalen Namen.

Möchte man aber tatsächlich den global definierten Namen verwenden, kann man dies mit dem Schlüsselwort global tun:

s = 15  # globales s

def test():
    global s  # Ermöglicht Zugriff auf das globale s
    print(s)
    s = 12  # ändert globales s
    print(s)

test()
print(s)
15
12
12

Prinzipiell sollte man die Deklaration global in lokalen Scopes aber vermeiden und globale/lokale sauber voneinander Scopes trennen. Will man auf ein Objekt aus einem äußeren Scope zugreifen, definiert man einen Parameter damit man das gewünschte Objekt als Argument der Funktion übergeben kann:

s = 15  # globales s

def test(s):
    print(s)  # lokales s (Argument)
    s = 12
    print(s)

print(s)
test(s)
print(s)
15
15
12
15

Will man eine lokale Variable in einem äußeren Scope weiter nutzen, gibt man den entsprechenden Wert am besten mit return zurück:

s = 15  # globales s

def test(s):
    print(s)  # lokales s (Argument)
    s = 12
    print(s)
    return s

print(s)
s = test(s)  # globales s bekommt Rückgabewert der Funktion (lokales s)
print(s)
15
15
12
12

Übungen

Übung 1

Suchen Sie sich aus der Liste der eingebauten Funktionen (siehe vorige Einheit) drei beliebige Funktionen aus.

  • Rufen Sie den Hilfetext zu jeder Funktion auf und lesen Sie diesen durch, um herauszufinden was die Funktion tut.
  • Rufen Sie die drei Funktionen mit geeigneten Argumenten auf.
  • Geben Sie für jede Funktion an, ob diese einen Wert zurückgibt oder nicht. Falls ein Wert zurückgegeben wird, geben Sie diesen Wert sowie dessen Typ an!

Übung 2

Schreiben Sie eine Funktion mult, welche zwei Zahlen multipliziert und deren Produkt zurückgibt. Die beiden Zahlen sollen als Argumente übergeben werden können. Rufen Sie Ihre Funktion mit ein paar Wertepaaren auf und stellen Sie sicher, dass Ihre Funktion wie gewünscht funktioniert. Die Funktion soll das Ergebnis als Wert zurückgeben, d.h. verwenden Sie innerhalb der Funktion kein print sondern return!

Übung 3

Schreiben Sie eine Funktion to_fahrenheit, welche einen Parameter celsius hat und diese Celsius-Temperatur in Fahrenheit umwandelt und zurückgibt. Zum Testen rufen Sie Ihre Funktion mit den Celsius-Temperaturen 0, 20, 38 und 100 auf.

Schreiben Sie außerdem eine Funktion to_celsius (mit entsprechendem Parameter fahrenheit) und wandeln Sie die vier Fahrenheit-Temperaturen, die Sie vorher beim Aufruf der Funktion to_fahrenheit erhalten haben, wieder in Celsius-Temperaturen um!

Übung 4

Nennen Sie mindestens drei Gründe, warum man Funktionen verwendet. Erklären Sie auch kurz den Unterschied zwischen Funktionsdefinition und Funktionsaufruf! Erklären Sie den Unterschied zwischen Parameter und Argument!

Übung 5

Schreiben Sie eine Funktion namens nonsense, welche drei Parameter namens a, b und c hat. Die beiden Argumente b und c sollen optional sein und die Standardwerte 10 bzw. 13 besitzen. Die Funktion soll a**2 - b * 2 + c**2 berechnen und zurückgeben (wir setzen voraus, dass man für a, b und c immer Zahlenwerte übergibt). Rufen Sie die Funktion dann mit folgenden Argumenten auf:

  1. Ohne Argumente
  2. Mit drei positionalen Argumenten
  3. Mit zwei positionalen Argumenten
  4. Mit einem Keyword-Argument
  5. Mit zwei Keyword-Argumenten
  6. Mit zwei positionalen Argumenten und einem Keyword-Argument
  7. Mit einem positionalem und einem Keyword-Argument

Wie sehen diese Funktionsaufrufe aus? Wie lauten die Rückgabewerte? Geben Sie für jeden Aufruf die Werte aller drei Argumente an!

Anmerkung: Nur der erste Funktionsaufruf (ohne Argumente) verursacht einen Fehler, alle weiteren Aufrufe sind möglich!