Vorlesungsskript 18

Funktionen

Wir haben schon einige Funktionen kennengelernt, die in Python eingebaut sind (z.B. len, max, input) oder von Modulen zur Verfügung gestellt werden (z.B. fileinput.input). Funktionen sind sozusagen „Programme innerhalb von Programmen“: Man ruft sie mit einem Input (den Argumenten) auf, sie tun etwas und geben einen Output (den Rückgabewert) zurück.

Sie können nicht nur bestehende Funktionen verwenden. Sie können auch Ihre eigenen Funktionen definieren. Auf diese Weise können Sie Ihr Programm in mehrere „Unterprogramme“ aufteilen, also Funktionen, jede mit ihrer eigenen und relativ eng begrenzten Aufgabe. Dieses Aufteilen von Programmen hat vor allem zwei Vorteile:

  1. Übersichtlichkeit. Programme, die in mehrere kleinere Einheiten (Funktionen) aufgeteilt sind, sind übersichtlicher. Fehler lassen sich leichter finden und beheben: Man sorgt zunächst dafür, dass eine Funktion funktioniert wie gewünscht. Dann wendet man sich der nächsten zu.
  2. Wiederverwendbarkeit. Viele Routineaufgaben begegnen Ihnen beim Programmieren immer wieder und sind mehreren Programmen gemeinsam oder müssen sogar an verschiedenen Stellen innerhalb desselben Programms ausgeführt werden. Zum Beispiel: eine Datei einlesen. Oder: einen String in Wörter zerlegen. Oder: aus einer Liste von Wörtern die Stopwörter herausfiltern. (Denken Sie z.B. an tokens.py und freq.py.) Es empfiehlt sich, für jede dieser Aufgaben eine eigene Funktion zu schreiben. Dann kann diese Funktion von verschiedenen Stellen im Programm aus aufgerufen werden. Und sie kann sogar unverändert durch mehrere Programme verwendet werden.

In diesem Vorlesungsskript lernen Sie, wie man Funktionen definiert und sie benutzt, um Programme in übersichtliche, wiederverwendbare Einheiten aufzuteilen.

Funktionen definieren

Funktionen definiert man in Python mit Hilfe der Schlüsselwörter def und return. Funktionsdefinitionen sind wie Verzweigungen und Schleifen Blockanweisungen mit einem Kopf und einem Körper. Hier ist ein einfaches Beispiel für eine Funktion. Sie dient dazu, den Flächeninhalt eines Dreiecks zu berechnen.

def triangle_area(base, height):
    '''Calculates the area of a triangle.

    Given a base of the triangle and the corresponding height, returns its
    area.'''
    area = base * height / 2
    return area

Einmal definiert, kann man die Funktion mit Hilfe von Funktionsaufrufen benutzen. Im folgenden Beispiel berechnen wir zum Beispiel den Flächeninhalt eines Dreiecks mit Basis 2.5 und Höhe 1.2:

>>> triangle_area(2.5, 1.2)
1.5

Der Kopf einer Funktionsdefinition besteht aus dem Schlüsselwort def (für define), dem Namen der zu definierenden Funktion, einer Parameterliste in runden Klammern und einem Doppelpunkt.

Die Parameterliste bestimmt, mit wie vielen Argumenten die Funktion aufgerufen werden muss. Unsere Beispielfunktion hat zwei Parameter – base und height – also muss die Funktion mit genau zwei Argumenten aufgerufen werden. Aufrufe wie triangle_area(), triangle_area(2.5) or triangle_area(2.5, 1.2, 0) würden zum Abbruch des Programms mit einer Fehlermeldung führen.

Der Körper einer Funktionsdefinition besteht aus einem (optionalen, aber empfohlenen) Docstring und einer Reihe von Anweisungen, darunter auch return-Anweisungen.

Der Docstring ist das zur Funktion gehörende „Handbuch“: Er beschreibt, was für Argumente die Funktion erwartet und was sie zurückgibt. Hier können Sie und andere Programmiererinnen nachschauen, wie Sie sie benutzen müssen. Der Docstring wird normalerweise als ein mehrzeiliges, durch dreifache Anführungszeichen begrenztes String-Literal gegeben, darin auf der ersten Zeile eine kurze Zusammenfassung, dann eine Leerzeile und schließlich eine detailiertere Beschreibung der Argumente und des Rückgabewerts.

Beim Aufruf einer Funktion – z.B. triangle_area(2.5, 1.2) – passiert Folgendes: Die Argumente werden automatisch in den entsprechenden Parametern gespeichert (Parameter sind eine Art Variablen), also z.B. 2.5 in base und 1.2 in height. Dann werden die Anweisungen im Körper der Funktion ausgeführt, in unserem Beispiel zunächst die Zuweisung area = base * height / 2 und dann die return-Anweisung return area. return-Anweisungen beenden das Ausführen der Funktion und legen gleichzeitig den Rückgabewert des Funktionsaufrufs fest. So gibt unser Aufruf triangle_area(2.5, 1.2) den Wert zurück, den die Funktion zuvor in der Variablen area gespeichert hat (1.5).

Endet die Ausführung einer Funktion ohne return-Anweisung, gibt der Funktionsaufruf None zurück.

Lokale Variablen

Funktionen sind vom Rest des Programms unabhängige Einheiten. Das heißt auch, dass man nicht außerhalb einer Funktion auf Variablen zugreifen kann, die innerhalb der Funktion definiert werden: diese Variablen sind lokal, also nur innerhalb der Funktion gültig. Auch die Parameter sind lokale Variablen. Wenn wir nach unserem triangle_area-Aufruf versuchen, auf die lokalen Variablen zuzugreifen, gibt es daher Fehlermeldungen:

>>> triangle_area(2.5, 1.2)
1.5
>>> base
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'base' is not defined
>>> height
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'height' is not defined
>>> area
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'area' is not defined

Umgekehrt kann eine Funktion auf Variablen, die außerhalb der Funktion definiert sind (globale Variablen), zwar lesend, aber normalerweise nicht schreibend zugreifen, wie folgende Session demonstriert:

>>> x = 5
>>> def test1():
...     '''Prints the value of the global variable x.'''
...     print(x)
... 
>>> test1()
5
>>> def test2():
...     '''Tries to set the value of the global variable x to 7.'''
...     x = 7
... 
>>> test2()
>>> x
5

Im obigen Beispiel definieren wir im interaktiven Modus Funktionen, müssen also im interaktiven Modus mehrzeilige Blockanweisungen eingeben. Das von >>> zu ... veränderte Python-Prompt zeigt an, dass wir noch in einer Blockanweisung stecken und die Eingabe noch nicht beendet ist. Man beendet die Eingabe mit einer Leerzeile.

Schreibend zugreifen kann eine Funktion auf globale Variablen, wenn sie sie mit einer global-Anweisung als global deklariert:

>>> def test3():
...     '''Sets the value of the global variable x to 7.'''
...     global x
...     x = 7
... 
>>> test3()
>>> x
7

Die Möglichkeit, in einer Funktion lesend oder sogar schreibend auf eine globale Variable zuzugreifen, kann zwar manchmal nützlich sein. Es kann jedoch auch verwirrend sein und ist in den meisten Fällen nicht nötig. Deswegen sollte man globale und lokale Variablen normalerweise strikt trennen. Die Funktion und der Rest des Programms tauschen dann Daten ausschließlich über die Objekte aus, die als Argumente übergeben werden bzw. zurückgegeben werden.

Funktionen testen

Ein einfacher Weg, Funktionen zu testen, ist es, ein Programm zu schreiben, das die Funktion enthält sowie eine oder mehrere Anweisungen, die die Funktion aufrufen. Zum Beispiel können wir ein Programm triangle.py schreiben mit folgendem Inhalt:

def triangle_area(base, height):
    '''Calculates the area of a triangle.

    Given a base of the triangle and the corresponding height, returns its
    area.'''
    area = base * height / 2
    return area

print(triangle_area(2.5, 1.2))

Dieses Programm lässt sich dann wie folgt ausführen:

$ python3 triangle.py
1.5

Weitere Beispiele für Funktionen

def read_file_into_list(path):
    '''Returns the lines in the given file as a list.

    Trailing whitespace is removed from each line.'''
    lines = []
    infile = open(path, 'r')
    for line in infile:
        lines.append(line.rstrip())
    infile.close()
    return lines
def remove_stopwords(words, stopwords):
    '''Removes stopwords from a sequence of words.

    Given an iterable of words and a collection of stopwords, returns a new
    list containing only those words from the iterable that are not
    stopwords.'''
    filtered = []
    for word in words:
        if word not in stopwords:
            filtered.append(word)
    return filtered
def extract_words(string):
    '''Extracts the words from a string.

    Returns a list of the word occurrences in string. Every contiguous sequence
    of alphanumeric characters is considered a word.'''
    words = []
    filtered_string = ''
    for character in string:
        if character.isalnum():
            filtered_string += character
        else:
            filtered_string += ' '
    return filtered_string.split()

Feinheiten

Funktionsaufrufe mit Keyword-Argumenten

Funktionsaufrufe können Argumente auch als Keyword-Argumente übergeben, also explizit dazusagen, welchen Parameter das jeweilige Argument füllen soll. Keyword-Argumente müssen nach den positionsabhängigen Argument kommen. Ihre Reihenfolge untereinander ist aber egal, weil Python ja durch die Keywords weiß, für welche Parameter sie bestimmt sind.

Zum Beispiel können wir unsere Funktion triangle_area auch wie folgt aufrufen. Wir übergeben hier das zweite Argument als Keyword-Argument:

>>> triangle_area(2.5, height=1.2)
1.5

Wir können natürlich auch beide Argumente als Keyword-Argumente übergeben:

>>> triangle_area(base=2.5, height=1.2)
1.5

In diesem Fall spielt die Reihenfolge keine Rolle mehr:

>>> triangle_area(height=1.2, base=2.5)
1.5

Funktionsdefinitionen mit Default-Werten für Parameter

Normalerweise müssen Funktionsaufrufe alle Argumente angeben. Argumente für Parameter, für die in der Parameterliste ein Default-Wert angegeben ist, sind jedoch optional. Werden sie nicht übergeben, wird der Default-Wert verwendet.

def greet(name, greeting='Hello', punctuation='!'):
    print('{}, {}{}'.format(greeting, name, punctuation))
>>> greet('Harry')
Hello, Harry!
>>> greet('Harry', 'Good day')
Good day, Harry!
>>> greet('Harry', 'Good day', '.')
Good day, Harry.

Auch hier können wir Argumente statt positionsbasiert als Keyword-Argumente übergeben. Dann können wir auch Argumente überspringen, also z.B. punctuation angeben, aber greeting nicht:

>>> greet('Harry', punctuation='.')
Hello, Harry.

Funktionsdefinitionen mit variadischen Parametern

Normalerweise müssen alle Argumente, die bei einem Funktionsaufruf übergeben werden, entsprechende Parameter haben, sonst kommt es zu einer Fehlermeldung:

>>> greet('Harry', 'Good day', '.', 'Voldemort', 'Hagrid')
Traceback (most recent call last):
  File "", line 1, in 
TypeError: greet() takes from 1 to 3 positional arguments but 5 were given
>>> greet('Harry', 'Good day', '.', 'Voldemort', 'Hagrid', location='Hogwarts', time=6)
ceback (most recent call last):
  File "", line 1, in 
TypeError: greet() got an unexpected keyword argument 'location'

Man kann in einer Funktionsdefinition jedoch auch den Aufruf mit beliebig vielen weiteren positionsabhängigen und/oder Keyword-Argumenten erlauben, indem man in die Parameterliste die variadischen Parameter *args und/oder **kwargs aufnimmt. Zusätzliche positionsabhängige Argumente werden dann in der Variablen args gespeichert, zusätzliche Keyword-Argumente als Dictionary in kwargs.

def greet(name, greeting='Hello', punctuation='!', *args, **kwargs):
    print('{}, {}{}'.format(greeting, name, punctuation))
    print('Additional positional arguments: {}'.format(args))
    print('Additional keyword arguments: {}'.format(kwargs))
>>> greet('Harry', 'Good day', '.', 'Voldemort', 'Hagrid', location='Hogwarts', time=6)
Hello, Harry.
Additional positional arguments: ('Voldemort', 'Hagrid')
Additional keyword arguments: {'location': 'Hogwarts', 'time': 6}

So ist es möglich, Funktionen zu definieren, die wie print, min, max etc. beliebig viele Argumente akzeptieren.

Funktionsaufrufe mit variadischen Argumenten

Auch beim Aufruf von Funktionen ist es möglich, statt einzelner Argumente eine Liste mit weiteren positionsabhängigen Argumenten und/oder ein Dictionary mit weiteren Keyword-Argumenten zu übergeben. Hierfür setzen wir * bzw. ** davor. Unser obiger Aufruf triangle_area kann somit u.a. auch auf diese Weisen erfolgen:

>>> args = [2.5, 1.2]
>>> triangle_area(*args)
1.5
>>> kwargs = {'base': 2.5, 'height': 1.2}
>>> triangle_area(**kwargs)
1.5
>>> args = [2.5]
>>> kwargs = {'height': 1.2}
>>> triangle_area(*args, **kwargs)
1.5

Hier variadische Argumente zu verwenden dient natürlich keinem Zweck außer der Illustration. Sie brauchen sie hingegen zum Beispiel, wenn in Ihrem Programm eine Liste existiert und Sie dieser einer Funktion übergeben wollen, die die Elemente als einzelne Argumente erwartet, wie z.B. die print-Funktion:

>>> tokens = ['dog', 'cat', 'mouse']
>>> print(*tokens)
dog cat mouse