Funktionale Programmierung in Python
In den letzten beiden Artikeln haben wir uns mit der funktionalen Programmierung befasst. Zunächst haben wir deren grundlegende Eigenschaften sowie die Vor- und Nachteile dieser Vorgehensweise vorgestellt. Im zweiten Teil haben wir uns dann mit der Programmiersprache Haskell beschäftigt. Hierbei handelt es sich um eine rein funktionale Programmiersprache. Neben Sprachen, die wie Haskell ausschließlich auf die funktionale Programmierung ausgelegt sind, gibt es jedoch noch viele weitere Möglichkeiten, um funktionale Programme zu erstellen. Viele Programmiersprachen, die eigentlich auf die imperative Programmiertechnik ausgelegt sind, unterstützen als Alternative auch eine funktionale Vorgehensweise. Ein Beispiel hierfür ist Python. Dieser Artikel stellt vor, wie Sie mit Python funktionale Programme erstellen können.
Python: auch für die funktionale Programmierung geeignet
Python ist in erster Linie als objektorientierte Programmiersprache bekannt. Dabei handelt es sich jedoch um ein Programmierparadigma, das in vielen Bereichen der funktionalen Programmierung widerspricht. Ein Beispiel hierfür ist der Aufruf einer Methode. Diese nutzt normalerweise die Attribute eines Objekts. Das bedeutet, dass ihr Ergebnis abhängig von einem Zustand des Programms ist – in diesem Fall vom Wert eines Attributs. Außerdem dienen Methoden häufig dazu, Attribute zu verändern. Auch das widerspricht den Grundsätzen der funktionalen Programmierung.
Obwohl Python in erster Linie als objektorientierte Programmiersprache bekannt ist, ist es hierbei nicht erforderlich, sich auf dieses Paradigma zu beschränken. Um genau zu sein, handelt es sich bei Python um eine multiparadigmatische Programmiersprache. Das schließt auch die funktionale Programmierung mit ein. Obwohl die meisten Python-Programme objektorientiert sind und eine imperative Vorgehensweise verwenden, ist es mit dieser Sprache auch möglich, rein funktionale Programme zu verfassen.
Funktionen nach den Kriterien der funktionalen Programmierung verwenden
Funktionen kommt bei der funktionalen Programmierung eine zentrale Bedeutung zu. Doch auch viele imperative Programme verwenden sie. Allerdings gelten für deren Anwendung ganz andere Regeln. Wenn Sie eine Programmiersprache wie Haskell verwenden, die rein funktional gestaltet ist, ist bei der Verwendung von Funktionen sichergestellt, dass diese dem funktionalen Programmierparadigma entsprechen. Dafür sorgen die grundlegenden Syntax-Regeln der Sprache. In Python sind Sie jedoch nicht an diese Vorgaben gebunden. Das bedeutet, dass Sie selbst dafür verantwortlich sind, die Regel der funktionalen Programmierung zu berücksichtigen. Daher stellen wir Ihnen zunächst vor, wie Sie Funktionen in Python für einen funktionalen Programmierstil verwenden können.
Funktionen ohne Seiteneffekte erzeugen
Für unser erstes Beispiel erstellen wir eine Funktion in Python. Diese soll das erste Element aus einer Liste entfernen. Wenn Sie bisher Python mit einem imperativen Programmierstil genutzt haben, könnten Sie das Programm auf folgende Weise umsetzen:
Das dargestellte Programm erfüllt die gestellte Ausgabe und verwendet hierfür eine Funktion. Dennoch handelt es sich hierbei nicht um ein funktionales Programm. Das liegt unter anderem daran, dass die Funktion auf die Liste aus dem Hauptprogramm zugreift. Ihre Wirkung hängt daher vom Zustand dieser Variable ab. Außerdem verändert sie diese Liste. Nach den Vorgaben der funktionalen Programmierung darf eine Funktion jedoch lediglich Ein- und Ausgabewerte verwenden. Sie muss unabhängig vom Zustand anderer Variablen sein und darf deren Werte auch nicht beeinflussen. Das bedeutet, dass die Funktion keine Seiteneffekte haben darf. Daher müssen wir zum einen die Liste per Übergabewert an die Funktion übermitteln. Zum anderen dürfen wir den Zustand der Liste aus dem Hauptprogramm nicht verändern. Daher ist es erforderlich, das Ergebnis über den return-Befehl zurückgeben. Diese Einschränkung führt dazu, dass bei der funktionalen Programmierung alle Funktionen ein return-Statement enthalten sollten. Tun sie dies nicht, haben sie entweder keinen Zweck oder sie erfüllen die Vorgaben der funktionalen Programmierung nicht.
Demnach gestalten wir unser Programm neu. Dabei erzeugen wir innerhalb der Funktion eine neue Liste und geben diese dann an das Hauptprogramm zurück:
Die Ausführung des Programms ist hierbei genau die gleiche wie im vorigen Beispiel. Das bedeutet, dass wir auch in diesem Fall zunächst die ursprüngliche Liste und daraufhin die Liste ohne das erste Element ausgeben. Allerdings beachten wir nun die Vorgabe der funktionalen Programmierung, dass eine Funktion lediglich auf einer Ein- und Ausgabe basieren darf und unabhängig von den Werten anderer Variablen sein muss.
Referenzielle Transparenz
Ein weiterer wichtiger Grundsatz der funktionalen Programmierung ist die referenzielle Transparenz. Das bedeutet, dass der Zugriff auf eine Variable stets den gleichen Wert zurückgeben muss – unabhängig davon, zu welchem Zeitpunkt dieser erfolgt. Auch diesen Grundsatz hat unser erstes Programmbeispiel in Python nicht befolgt. Hierbei haben wir zuerst eine Liste mit den Werten 2, 7, 9 und 4 erzeugt. Beim ersten Zugriff auf diese Variable innerhalb des print()-Befehls haben wir genau diesen Wert erhalten. Danach haben wir jedoch den Zustand verändert, indem wir das erste Element entfernt haben. Das hat zur Folge, dass der zweite Zugriff nur noch die Zahlen 7, 9 und 4 zurückgab. Das zeigt, dass der Wert dieser Variable vom zeitlichen Verlauf des Programms abhängig ist. Das bedeutet, dass dieses Programm die referenzielle Transparenz nicht beachtet hat.
Wenn Sie nun das zweite Programm betrachten, stellen Sie fest, dass hierbei jede Variable über die gesamte Laufzeit hinweg den gleichen Wert aufweist. Auch dieser Aspekt zeigt, dass wir hierbei die Regeln der funktionalen Programmierung beachtet haben. Rein funktionale Programmiersprachen wie Haskell erlauben es nicht, den Wert einer Variable zu verändern. In anderen Sprachen können wir dies verhindern, indem wir sie als Konstante definieren. Python kennt diese Möglichkeit jedoch nicht. Daher müssen wir hier selbst darauf achten, die referenzielle Transparenz zu gewährleisten. Das bedeutet, dass wir einer Variablen, die einmal einen Wert erhalten hat, keinen neuen Wert zuweisen dürfen. Bei zusammengesetzten Datentypen bietet es sich an, Tupel statt Listen zu verwenden. Diese sind unveränderlich und erfüllen daher die Anforderung der referenziellen Transparenz.
Funktionen als Übergabe verwenden
Ein wichtiges Prinzip der funktionalen Programmierung besteht darin, dass Funktionen gleichberechtigt zu allen anderen Daten sein müssen. Eine Folge daraus ist, dass es auch möglich sein muss, sie als Übergabewert für eine weitere Funktion zu verwenden. Das ist bei zahlreichen imperativen Programmiersprachen nicht möglich.
Python bietet uns diese Möglichkeit jedoch. Dabei kommt uns zugute, dass die Implementierung von Python einen strikten objektorientierten Ansatz verfolgt. Hier gilt die Regel „everything is an object“. Diese gilt auch für Funktionen. Das bedeutet, dass diese wie ein Objekt behandelt werden, sodass wir sie auch als Übergabewert für andere Funktionen verwenden können. Das soll wieder ein an einem Beispiel aufgezeigt werden.
Hierfür erstellen wir zunächst eine einfache Funktion, die zwei Zahlen addiert und das Ergebnis dann zurückgibt:
def addieren (x, y): return x + y
Nun erzeugen wir eine weitere Funktion, die es erlauben soll, verschiedene Rechenoperationen durchzuführen. Sie soll zum einen zwei Zahlen als Übergabewert erhalten. Um die gewünschte Berechnung vorzunehmen, soll sie jedoch eine weitere Funktion verwenden. Dazu müssen wir diese einfach als Übergabewert aufnehmen. Danach können wir sie innerhalb der Funktion problemlos anwenden:
def berechnen (func, x, y): return func(x, y)
Nun können wir die Funktion berechnen() im Hauptprogramm aufrufen. Als Übergabewert verwenden wir neben den beiden Zahlen die Funktion addieren(). Das bedeutet, dass die Funktion berechnen() nun die beiden Zahlen addiert.
Die Ausführung zeigt, dass wir auf diese Weise die beiden Werte mit der Funktion berechnen() addieren können. Es wäre aber auch möglich, weitere Funktionen für andere Rechenoperationen zu erstellen. Das gibt uns die Möglichkeit, die Funktion berechnen() für verschiedene Aufgaben zu verwenden. Das soll nun das folgende Beispiel zeigen. Dieses erstellt neben den bereits vorhandenen Funktionen noch die Funktion multiplizieren(). Das Hauptprogramm ruft die Funktion berechnen() dann mit beiden Übergabewerten auf.
Lambda-Funktionen in Python verwenden
Python unterstützt außerdem Lambda-Funktionen. Dabei handelt es sich um sogenannte anonyme Funktionen. Das bedeutet, dass wir im gewöhnlichen Programmcode eine Funktion definieren und direkt anwenden können, ohne dass dafür ein Name erforderlich ist. Außerdem ist es hierfür nicht notwendig, die Funktion in einem separaten Block zu erstellen. Das erlaubt es, Funktionen ineinander zu verschachteln. Auch das ist ein wichtiger Aspekt der funktionalen Programmierung. Außerdem ist es auf diese Weise möglich, eine Funktion als Rückgabewert zu verwenden.
Als Beispiel hierfür erstellen wir ein weiteres Programm. Dieses soll einen Wert vervielfachen. Das bedeutet jedoch, dass wir hierfür verschiedene Funktionen benötigen – je nachdem, ob wir ihn verdoppeln, verdreifachen oder vervierfachen wollen. Diese Funktionen erstellen wir nun nicht alle in einer separaten Definition. Stattdessen erstellen wir eine allgemeine Funktion vervielfachen(). Diese erhält eine Zahl als Übergabewert – beispielsweise 2 zum Verdoppeln und 3 zum Verdreifachen. Als Rückgabewerte berechnet sie jedoch kein konkretes Ergebnis, sondern verwendet eine Funktion. Auf diese Weise erzeugen wir direkt im Hauptprogramm die Funktionen verdoppeln() und verdreifachen() – ohne dafür eine separate Funktionsdefinition zu erstellen.
Hierfür erstellen wir zunächst die Funktion vervielfachen():
def vervielfachen(n): return lambda a : a * n
Diese gibt eine Lambda-Funktion zurück. Das bedeutet, dass wir als Rückgabewert eine Funktion erhalten, die einen Wert mit dem vorgegebenen Wert n multipliziert. Auf diese Weise erzeugen wir im Hauptprogramm die Funktionen verdoppeln() und verdreifachen():
verdoppeln = vervielfachen(2) verdreifachen = vervielfachen(3)
Danach können wir diese wie gewöhnliche Funktionen aufrufen. Sie berechnen stets das gewünschte Ergebnis. Das komplette Programm sieht dann so aus:
Die hier praktizierte Vorgehensweise stellt auch ein Beispiel für das Currying dar. Dabei handelt es sich um die Umwandlung einer Funktion mit mehreren Übergabewerten in eine Funktion, die nur ein einziges Argument erhält. Dieses Verfahren ist nach dem Mathematiker Haskell Brooks Curry benannt, der auch Namensgeber der Programmiersprache Haskell ist. Das Currying stellt einen wichtigen Aspekt der funktionalen Programmierung dar. Das obige Beispiel zeigt, dass sich diese Vorgehensweise problemlos mit Python umsetzen lässt.
Fazit: Python bietet zahlreiche Möglichkeiten für die funktionale Programmierung
Der Artikel hat gezeigt, dass es mit Python problemlos möglich ist, funktionale Programme zu erstellen. Von besonderer Bedeutung ist hierbei, dass Funktionen in Python genau gleich wie alle übrigen Variablen behandelt werden. Das bedeutet, dass es sich hierbei um First-Class-Functions handelt, die wir auch als Übergabe- und Rückgabewerte verwenden können. Auch die Unterstützung anonymer Lambda-Funktionen ist für die Umsetzung funktionaler Programme sehr wichtig.
Dennoch gibt es wichtige Unterschiede zu rein funktionalen Programmiersprachen wie Haskell. Von großer Bedeutung ist beispielsweise der Aspekt, dass Python die Regeln der funktionalen Programmierung nicht kontrolliert. Während es in Haskell zu einer Fehlermeldung kommt, wenn Sie einer Variable einen neuen Wert zuweisen, stellt das in Python kein Problem dar. Das bedeutet, dass Sie stets selbst für die Einhaltung der entsprechenden Regeln verantwortlich sind. Außerdem ist die Syntax von Python nicht an der funktionalen Programmierung ausgerichtet. Das bedeutet, dass für manche Aufgaben deutlich mehr Code erforderlich ist als in rein funktionalen Programmiersprachen. Trotz dieser Einschränkungen stellt Python eine hervorragende Möglichkeit dar, um funktionale Programme umzusetzen.