Funktionen

Oftmals werden bestimmte Code-Stücke an verschiedenen Stellen eines Programms eingesetzt. Derartige Teile, die gewissermaßen kleine “Unterprogramme” darstellen, werden als Funktionen definiert. Funktionen liefern am Ende stets genau ein Ergebnis zurück; dieses kann jedoch von den übergebenen Funktionsargumenten und/oder von äußeren Bedingungen abhängig sein. [1]

Definition eigener Funktionen

Eine Funktion wird in Python nach folgendem Schema definiert:

def function_name(argument_1, argument_2, ...):
    """
    Docstring (short description of the function's purpose)
    """

    function_code
    ...

    return result

Zum Benennen von Funktionen werden in Python üblicherweise nur kleine Buchstaben verwendet. Häufig besteht ein Funktionsname aus einem Verb, das die Wirkungsweise der Funktion beschreibt; liegt jedoch der Zweck einer Funktion nur darin, einen bestimmten Wert auszugeben, so kann der Funktionsname auch aus einem Substantiv bestehen, das den jeweiligen Wert beschreibt – beispielsweise wird die Wurzelfunktion meist mit sqrt() (“square root”) anstelle von calculate_sqrt() bezeichnet. Besteht ein Funktionsname aus mehreren Wörtern, so werden diese zur besseren Lesbarkeit mittels Unterstrichen getrennt.

Nach ihrer Definition kann die Funktion dann folgendermaßen aufgerufen werden:

function_name(argument_1, argument_2, ...)

Eine Funktion kann, abhängig von ihrer Definition, beim Aufruf kein, ein oder auch mehrere Argumente in der jeweiligen Reihenfolge verlangen. Bei jedem Aufruf wird dann der Funktions-Code ausgeführt und ein entsprechender Ergebniswert zurück gegeben; ist im Funktionsblock keine return-Anweisung enthalten, wird von der Funktion automatisch der Wert None zurückgegeben. [2]

Beim Aufruf der Funktion werden die einzelnen Argumente üblicherweise gemäß ihrer Position direkt übergeben, beispielsweise argument_1 = 5 ; function_name(argument_1). Es ist jedoch auch für eine gegebenenfalls bessere Lesbarkeit des Quellcodes möglich, bei der Übergabe von Argumenten deren Bezeichnung mit anzugeben, beispielsweise function_name(argument_1=5).

Da jede Funktion ein kleines Unterprogramm darstellt, sollte sie durch einen Docstring dokumentiert werden. Es gibt Werkzeuge, die Docstrings verwenden, um automatisch gedruckte Dokumentation zu erzeugen (beispielsweise Sphinx) oder um Benutzer interaktive Hilfe anzubieten (beispielsweise Ipython). Werden bei einer Funktion Argumente verlangt, so sollten diese ebenfalls im Docstring kurz beschrieben werden (Typ, Bedeutung).

Optionale Argumente

In Python ist es möglich, bei der Definition von Funktionen optionale Argumente mit einem Standard-Wert anzugeben. Wird ein solches optionales Argument beim Aufruf der Funktion nicht explizit angegeben, so wird stattdessen der Standard-Wert genutzt.

Die Definition einer solchen Funktion lautet etwa:

def my_function(argument_1, argument_2="Test")

    return (argument_1, argument_2)

Die Funktion kann anschließend wahlweise mit einem oder mit zwei Argumenten aufgerufen werden:

my_function(5)
# Ergebnis: (5, 'Test')

my_function(5, 7)
# Ergebnis: (5, 7)

Hat eine Funktion sowohl “normale” als auch optionale Argumente, so müssen die optionalen Argumente am Ende der Funktion angegeben werden.

Ebenso ist es möglich, einer Funktion eine optionale Liste oder ein optionales Dict für zusätzliche Argumente anzugeben. Üblicherweise lautet die Syntax dafür:

def any_function(argument_1, argument_2="default value", *args, **kwargs)

    pass

Beim Aufruf der Funktion muss argument_1 angegeben werden, die Angabe von argument_2 ist optional; zusätzlich können weitere unbenannte oder benannte Argumente angegeben werden, beispielsweise any_function(5,2,7,foo=9). Im obigen Beispiel können alle unbenannten Argumente innerhalb der Funktion über die Variable args, alle benannten über die Variable kwargs aufgerufen werden. Werden beim Funktionsaufruf keine weiteren Argumente übergeben, so ist args innerhalb der Funktion ein leeres Tupel und kwargs ein leeres Dict.

def sum_it_up(num_1, num_2, *nums):

    list_sum = functools.reduce(lambda x,y: x+y, nums)

    return num_1 + num_2 + len(nums)

Hat man im Quellcode vorab eine Liste an Objekten definiert, die man dann als Argumente an eine Funktion übergeben möchte, so muss diese bei der Übergabe entpackt werden. Dies ist mittels des *-Operators möglich:

my_list = [1,3,5,7]

sum_it_up(*my_list)
# Ergebnis: 6

Im obigen Beispiel wurde die Liste my_list bei der Übergabe an die Funktion entpackt; der Aufruf von sum_it_up(*my_list) ist in diesem Fall also identisch mit sum_it_up(1,3,5,7). Die ersten beiden Zahlen werden dabei als Argumente für num_1 und num_2 angesehen, die verbleibenden beiden werden innerhalb der Funktion in die Variable nums gespeichert. [3]

Nach dem gleichen Prinzip kann ein Dict mit Schlüsselwort-Wert-Paaren mittels ** entpackt an eine Funktion übergeben werden; dabei müssen alle im Dict enthaltenen Schlüsselwörter als mögliche Funktionsargumente erlaubt sein.

Veränderliche und unveränderliche Argumente

In Python werden Argumente an Funktionen via Zuweisung (Position oder Benennung) übergeben. Wird beispielsweise eine Variable x = any_object als Argument an eine Funktion übergeben, so wird x, also eine Referenz auf any_object, als Wert übergeben. Welchen Einfluss die Funktion auf das Argument hat, hängt dabei vom Objekt-Typ ab:

  • Ist any_object ein veränderliches Objekt, beispielsweise eine Liste, ein Dict oder ein Set, so kann dieses durch die Funktion direkt verändert werden:

    my_list = [1,2,3]
    
    def foo(any_list):
        any_list.append(4)
    
    foo(my_list)
    
    print(my_list)
    # Ergebnis: [1,2,3,4]
    

    Wird allerdings der übergebenen Referenz x in der Funktion ein neues Objekt zugewiesen, so entspricht dies einer Definition einer neuen Variablen innerhalb der Funktion als Namensraum. [4] Die ursprüngliche Referenz x bleibt in diesem Fall unverändert und zeigt nach wie vor auf das ursprüngliche Objekt:

    my_list = [1,2,3]
    
    def foo(any_list):
        any_list = [1,2,3,4]
    
    foo(my_list)
    
    print(my_list)
    # Ergebnis: [1,2,3]
    
  • Ist any_object ein unveränderliches Objekt, beispielsweise ein String oder ein Tupel, so wird bei der Übergabe an die Funktion eine Kopie des Objekts erzeugt; das ursprüngliche Objekt kann somit durch die Funktion nicht verändert werden:

    my_string = 'Hello World!'
    
    def foo(any_string):
      any_string.replace('World', 'Python')
    
    foo(my_string)
    
    print(my_string)
    # Ergebnis: 'Hello World!'
    

    Möchte man den obigen Beispielcode so umschreiben, dass nach Aufruf der Funktion die Variable my_string den Wert 'Hello Python!' bekommt, so muss man folgendermaßen vorgehen:

    my_string = 'Hello World!'
    
    def foo(any_string):
      x = any_string.replace('World', 'Python')
      return x
    
    my_string = foo(my_string)
    
    print(my_string)
    # Ergebnis: 'Hello Python!'
    

    Da Strings nicht verändert werden können, kann eine Veränderung der zugehörigen Variablen also nur über eine neue Zuweisung erfolgen.

Die Übergabe eines veränderlichen Datentyps, beispielsweise einer Liste, als Argument an eine Funktion ist zwar performance-technisch günstig, da nur eine neue Referenz auf die Liste erzeugt werden muss; es können aber unerwartete Fehler auftreten, wenn durch eine Funktion das übergebene Original der Liste unmittelbar verändert wird. Durch Verwendung von Tupeln anstelle von Listen kann dies auf Kosten von CPU und Memory ausgeschlossen werden.

Globaler und lokaler Namensraum

Jeder Funktionsblock hat einen eigenen Namensraum, was bedeutet, dass Variablen, die innerhalb des Funktionsblocks definiert werden, nur im Programm existieren, bis der Funktionsblock abgeschlossen ist. Derartige Variablen, die nur innerhalb der Funktion “sichtbar” sind, werden “lokale” Variablen genannt.

Einer lokalen Variablen kann dabei der gleiche Name zugewiesen werden wie einer Variablen, die außerhalb des Funktionsblocks definiert wurden; sie “überdeckt” in diesem Fall die nicht-lokale Variable: Der Interpreter sucht primär im lokalen Namensraum nach dem Variablennamen und erst sekundär im nicht-lokalen Namensraum, wenn keine lokale Variable mit dem angegebenen Namen existiert. und nur.

Wird einer lokalen Variablen ein Wert zugewiesen, so ändert sich also der Wert einer nichtlokalen Variablen nicht:

# Nicht-lokale Variable definieren:
x = 0

# Funktion mit lokaler Variablen definieren:
def myfunc():
    x = 1
    print(x)

# Funktion aufrufen:

myfunc()
# Ergebnis: 1

# Wert von x prüfen:

x
# Ergebnis: 0

Soll der Wert einer nicht-lokalen Variablen durch die Funktion verändert werden, so kann diese bei einem veränderlichen Datentyp als Argument an die Funktion übergeben werden. Variablen mit nicht veränderlichem Datentyp können, wie im letzten Abschnitt beschrieben, eine Veränderung erreicht werden, nur durch eine Zuweisung des Rückgabewerts der Funktion geändert werden.

Eine weitere prinzipielle Möglichkeit, die jedoch möglichst vermieden werden sollte, ist es, mittels des Schlüsselworts global einen Variablennamen primär im nicht-lokalen Namensraum zu suchen:

# Nicht-lokale Variable definieren:
x = 0

# Funktion mit globaler Variablen definieren:
def myfunc():
    global x = 1
    print(x)

# Funktion aufrufen:

myfunc()
# Ergebnis: 1

# Wert von x prüfen:

x
# Ergebnis: 1

Das Schlüsselwort global hat nur Auswirkungen innerhalb der Funktion, eine Variable kann also nicht von außerhalb der Funktion als “global” gekennzeichnet und als solche der Funktion aufgezwungen werden. Dennoch kann die Verwendung von global zu unerwarteten Seiteneffekten führen, wenn eine Variable prinzipiell von mehreren Stellen aus verändert werden kann, da in diesem Fall nicht immer auf den ersten Blick einwandfrei feststellbar ist, von wo aus die Variable verändert wurde.

Lambda-Ausdrücke

Bei so genannten Lambda-Ausdrücken handelt es sich um Mini-Funktionen, die sehr kompakt implementiert werden können. Das Schlüsselwort zur Definition eines Lambda-Ausdrucks ist lambda, gefolgt von möglichen Argumenten, die der Funktion beim Aufruf übergeben werden sollen, und einem Doppelpunkt. Hinter diesem Doppelpunkt wird in der gleichen Zeile die Wirkungsweise des Lambda-Ausdrucks definiert, beispielsweise:

add = lambda x1, x2: x1 + x2

add(5,3)
# Ergebnis: 8

Bei der Definition eines Lambda-Ausdrucks entfällt also das Schlüsselwort def, und die Argumente werden unmittelbar hinter dem Schlüsselwort lambda ohne runde Klammern angegeben.

Ein Nachteil von Lambda-Ausdrücken ist, dass in ihnen keine Schleifen, Kontrollstrukturen oder komplexeren Anweisungsblöcke vorkommen dürfen. [5] Ein wichtiger Vorteil ist hingegen, dass Lambda-Ausdrücke keinen expliziten Namen haben müssen. Beispielsweise kann der gesamte Lambda-Ausdruck, wenn man ihn in runde Klammern setzt, unmittelbar wie eine Funktion aufgerufen werden:

result = (lambda x1, x2: x_1 + x_2)(5,3)

print(result)
# Ergebnis: 8

Lambda-Ausdrücke werden auch häufig in Kombination mit den Builtin-Funktion filter() und map() eingesetzt, um jeweils auf alle Elemente einer Liste angewendet zu werden:

# Beispiel 1:

my_list = [1,2,3,4,5,6,7,8,9]

even_numbers = filter(lambda x: x % 2 == 0, my_list)

list(even_numbers)
# Ergebnis: [2,4,6,8]


# Beispiel 2:

my_list = [1,2,3,4]

even_numbers = map(lambda x: x * 2, [1,2,3,4])

list(even_numbers)
# Ergebnis: [2,4,6,8]

Ebenso kann ein Lambda-Ausdruck als Kriterium für die sort()-Funktion genutzt werden, um beispielsweise eine Liste von Zweier-Tupeln nach den ersten Elementen der Tupel zu sortieren:

my_list = [(3, 1), (1, 2), (11, -3), (5, 10)]

my_list.sort(key=lambda x: x[0])

print(my_list)
# Ergebnis: [(1, 2), (3, 1), (5, 10), (11, -3)]

Auch bei der Entwicklung von graphischen Oberflächen sind Lambda-Ausdrücke nützlich, um einzelnen Maus-Events oder Tastenkombinationen bestimmte Funktionen zuzuweisen.

Builtin-Funktionen

Neben der bereits erwähnten print()-Funktion, die zur Anzeige von Texten und Variablen auf dem Bildschirm dient, gibt es in Python weitere grundsätzlich verfügbare Funktionen, so genannte “Builtin”-Funktionen; Diese Funktionen sind ohne das Importieren weiterer Module unmittelbar verfügbar. Eine Übersicht über diese Funktionen findet sich im Anhang: Standard-Funktionen.


Anmerkungen:

[1]Soll eine Funktion mehrere Ergebnisse liefern, so müssen diese als Liste oder Tupel zurückgegeben werden.
[2]

In einer Funktionsdefinition können auch, wenn if-Bedingungen in ihr vorliegen, an mehreren Stellen return-Anweisungen auftreten. Sobald eine return-Anweisung erreicht wird, wird die Funktion unmittelbar beendet und der jeweilige Ergebniswert zurück gegeben. Steht nur return (ohne expliziten Ergebniswert), so wird ebenfalls der Wert None zurück gegeben.

Soll eine Funktion mehrere Werte als Ergebnis liefern, so müssen diese als Liste oder Tupel an das Programm zurückgegeben werden.

[3]Dass die obige Funktion die Länge der zusätzlichen Zahlenliste zum Ergebnis dazu addiert, soll an dieser Stelle nur die Funktionalität der optionalen Argumente zeigen. Um alle Elemente dieser Liste zu summieren, muss zusätzlich das Modul functools geladen werden; in der Beispielaufgabe Quader wird dies näher behandelt.
[4]Variablen sind nur innerhalb ihres Namensraums gültig. Ausnahmen sind globale Variablen, die beispielsweise zu Beginn eines Moduls definiert sind. Eine solche kann dann innerhalb einer Funktion mittels global var_name als global gekennzeichnet werden, wobei var_name der Name der Variablen ist.
[5]Eine Bedingte Wertzuweisung in der Form lambda x: a if condition else b ist allerdings erlaubt.