Python nennt sich eine objektorientierte Programmiersprache. Das ist aber nur die halbe Wahrheit, denn als Multiparadigmen-Programmiersprache unterstützt sie insbesondere auch die funktionale Programmierung, und dies in immer mächtigerer Art und Weise. Nachdem sich die erste Folge dieser Reihe mit den Grundzügen der funktionale Programmierung beschäftigte, wird sich dieser Artikel den Techniken, Built-in-Funktionen und Bibliotheken rund ums funktionale Programmieren in Python widmen.
Python, zwar nicht als funktionale Sprache entworfen, nimmt doch immer mehr funktionale Charakteristiken an. Waren es anfangs nur First Class Functions, so fanden immer wieder neue funktionale Features Eingang in die Sprache:
- Closures
- anonyme Funktionen (Lambda-Funktionen)
- Generatoren und Generator-Ausdrücke.
- die klassischen Funktionen höherer Ordnung "map", "filter" und "reduce" Python
- List Comprehension
Closures
Closures oder innere Funktionen sind Funktionen, die beim Aufruf ihren Aufrufkontext konservieren. Diese spezielle Funktionen mit Gedächtnis sind eine beliebte Technik, um Dekoratoren in Python zu implementieren (siehe Rainer Grimm, "Dekoratoren in Python": Linux-Magazin 06/09, S. 96). Insbesondere ermöglichen Closures, Funktionen zu definieren, die innere Funktionen auf Anfrage zurückgeben. Für diese inneren Funktionen werden in Python gerne Lambda-Funktionen verwendet, denn durch sie können Closures sehr kompakt umgesetzt werden.
Ein klassischer Anwendungsfall für Closures und Lambda-Funktionen zugleich ist die Funktion "makefilter" aus Listing 1, die bei jedem Aufruf einen neuen Filter erzeugt. Vor dem Verständnis der Funktion "makefilter" steht zuerst das Verständnis der zwei funktionalen Komponenten Closures und Lambda-Funktionen.
Das kleine Beispiel "addWith" (Abbildung 1) zeigt, wie der Wert "first" beim Aufruf von "addWith" im Scope der Funktion gebunden wird, sodass auf ihn lesend aus der inneren Funktion "innerFunction" zugegriffen werden kann.
Abbildung 1: Lesender Zugriff auf den äußeren Scope.
Leider ist die Umsetzung von Closures unter Python 2 sehr eingeschränkt, da sich der umgebene Scope aus der inneren Funktion nur lesend referenzieren lässt (Abbildung 1).
Den schreibenden Zugriff auf eine Variable "start" aus der inneren Funktion "memorizeCount" quittiert der Interpreter mit einer schwer verständlichen Fehlermeldung (Abbildung 2). Durch den schreibenden Zugriff auf die Variable "start" in dem Ausdruck "start+=1" wird eine neue Variable in deren Scope angelegt. Bevor die Variable "start" einen Wert besitzt, wird aber versucht, diesen zu lesen. Dieser Zugriff muss scheitern.
Abbildung 2: Der schreibende Zugriff auf den äußeren Scope scheitert in Python 2.*
Mit dem neuen Schlüsselwort "nonlocal" erlaubt Python 3.0 auf den äußeren, nicht lokalen Scope schreibend zuzugreifen. Damit ist ein Zähler schnell implementiert.
Abbildung 3: Schreibender Zugriff auf den äußeren Scope mit Python 3.*
Lambda-Funktionen
Das zweite Bausteinchen für das Verständnis der Filter-Funktion (Listing 1) fehlt noch. Was sind Lambda-Funktionen? Lambda-Funktionen oder auch anonyme Funktionen sind Funktionskörper ohne Namen. Diese nehmen in Python eine beliebige Anzahl von Argumenten an und führen einen Ausdruck aus - zugleich der Rückgabewert der Funktion.
Lambda-Funktionen werden gerne im Zusammenhang mit Funktionen höherer Ordnung verwendet. Die Funktion "makefilter" aus Listing 1 ist eine First Class Function, denn sie gibt eine Funktion zurück, genauer die Lambda-Funktion "lambda s,a=allchars,d=delchars: s.translate(a, d)".
|
Listing 1: Filter-Funktion
|
def makefilter(keep):
""" Return a function that takes a string and returns a copy of that
string consisting only of the characters in 'keep'.
"""
import string
# make a string of all chars, and one of all those NOT in 'keep'
allchars = string.maketrans('', '')
delchars = ''.join([c for c in allchars if c not in keep])
# return the function
return lambda s,a=allchars,d=delchars: s.translate(a,d)
|
Aber nun von Anfang an: Die Funktion "makefilter" erzeugt bei jedem Aufruf der Funktion einen Filter, der nur die Buchstaben aus "keep" passieren lässt. Der Schlüssel zum Verständnis dieses Filters ist die Funktion "translate" aus der String-Bibliothek. "s.translate(a,d)" übersetzt einerseits alle Zeichen des Strings "s" entsprechend der Übersetzungstabelle "a" und löscht andererseits die Zeichen, die sich in "d" befinden. Nun ist die Übersetzungstabelle "a" in diesem konkreten Fall "string.maketrans('','')" die Identität und "d" das Komplement zu "keep". Das einzige fehlende Argument für die Lambda-Funktion "lambda s,a=allchars,d=delchars: s.translate(a,d)" ist der zu filternde String "s". Dieser String ist das Argument für die Filter-Funktion (Abbildung 4).
Abbildung 4: Die Filter-Funktion im Einsatz.