Skip to content

Alles für WordPress, Webentwicklung — und mehr

🐍 Funktionale programmierung in Python

🐍 Funktionale programmierung in Python

Jeder Python-Entwickler hört früher oder später: „Versuchen Sie, funktional zu programmieren, Ihr Code wird sauberer." Doch wenn Sie die Dokumentation öffnen, sehen Sie map, filter und reduce mit Lambdas und wissen nicht, wo Sie anfangen sollen.

Das Problem ist nicht, dass funktionale Programmierung schwierig ist. Das Problem ist, dass die meisten Ratgeber entweder in akademische Haskell-Dickichte abtauchen oder sich auf Syntax beschränken, ohne das „Warum" zu erklären. Python als Multi-Paradigmen-Sprache stellt genau so viele funktionale Werkzeuge bereit, wie Sie für alltägliche Aufgaben benötigen, ohne Extreme.

In diesem Beitrag eine praxisnahe Aufschlüsselung der funktionalen Möglichkeiten von Python: von List Comprehensions bis zu Lambda-Funktionen, mit echten Beispielen und Szenarien, in denen jedes Werkzeug den Code tatsächlich vereinfacht.

💡 Schneller Überblick:

  • Aufschlüsselung von List Comprehensions, warum sie besser als Schleifen sind und wann man sie einsetzt
  • Generatoren beherrschen: yield, Speicher-Ersparnis und Generator-Ausdrücke
  • Durchlauf von map + filter + reduce: praktische Beispiele zum Ersetzen von Schleifen
  • Lambda-Funktionen schreiben lernen und verstehen, wo sie angebracht sind und wo sie die Lesbarkeit beeinträchtigen

Funktionale Programmierung, worüber sprechen wir

Funktionale Programmierung ist ein Ansatz, bei dem ein Programm aus mathematischen Funktionen aufgebaut wird: Sie nehmen unveränderliche Eingabedaten entgegen und liefern ein Ergebnis, ohne etwas außerhalb zu verändern. Keine Seiteneffekte, keine Veränderung globaler Zustände.

In rein funktionalen Sprachen wie Haskell werden „reine" und „unreine" (mit der Außenwelt interagierende) Programmteile strikt getrennt. Dieser Ansatz ermöglicht formale Beweise der Codekorrektheit: Der Compiler garantiert die Abwesenheit unerwarteter Seiteneffekte.

Python wurde nicht als funktionale Sprache entworfen. Es ist multi-paradigmatisch: objektorientiert, prozedural und ja, auch funktional. In Python rein funktional zu programmieren ist, als würde man Nägel mit einem Mikroskop einschlagen: technisch möglich, aber unbequem und unnatürlich. Einzelne funktionale Werkzeuge zu nehmen und in vertrauten Code einzubetten, ist hingegen ein funktionierendes und nützliches Muster.

List Comprehensions, das wichtigste funktionale Werkzeug in Python

Das tägliche Brot der funktionalen Programmierung ist die Arbeit mit Listen. Elemente nach Bedingung auswählen, jedes Element transformieren, aus einer bestehenden eine neue Liste aufbauen, all das deckt Python mit List Comprehensions ab.

Vorher, prozedurale Schleife:

1def filter_odd(li):
2 result = []
3 for i in li:
4 if i % 2 == 1:
5 result.append(i)
6 return result
7
8print(filter_odd([2, 4, 6, 7, 8, 1, 19, 200, 42, 31]))

Nachher, List Comprehension:

1li = [2, 4, 6, 7, 8, 1, 19, 200, 42, 31]
2odd_numbers = [x for x in li if x % 2 == 1]
3print(odd_numbers) # [7, 1, 19, 31]

Vier Zeilen prozeduraler Code auf eine komprimiert. Die Lesbarkeit hat nicht gelitten: Die Syntax [expression for element in iterator if condition] ist intuitiv verständlich, „nimm x aus li, wenn x ungerade ist".

Und das ist nicht nur syntaktischer Zucker. Eine List Comprehension läuft schneller als eine äquivalente Schleife mit .append(), weil sie auf C-Ebene ausgeführt wird und nicht auf Ebene des Python-Interpreters.

Was ist ein Iterator

Eine List Comprehension stützt sich auf das Konzept eines Iterators, ein Objekt, das bei Anforderung von next() das nächste Element einer Sequenz zurückgibt. Jedes Objekt, das die Methode __iter__ implementiert, wird als iterierbar bezeichnet. Liste, String, Tupel, Dictionary, Set, sie alle sind iterierbar.

1spam_iter = iter("foobar")
2result = "".join([c.upper() for c in spam_iter])
3print(result) # FOOBAR

Hier gibt iter() einen String-Iterator zurück, und die List Comprehension durchläuft ihn und wendet .upper() auf jedes Zeichen an.

Generatoren, faule Sequenzen

Ein Iterator kann nicht nur aus einer bestehenden Sammlung erstellt werden, sondern auch mithilfe einer Generator-Funktion. Statt return verwendet sie yield: Die Funktion „schläft ein", merkt sich ihren Zustand und fährt beim nächsten next()-Aufruf an derselben Stelle fort.

1def gen(max_val):
2 i = 1
3 while i < max_val:
4 yield i
5 i += 1
6
7g = gen(1000)
8for _ in range(12):
9 print(next(g))

Dieser Code gibt Zahlen von 1 bis 12 aus. Die Funktion gen erzeugt keine Liste mit tausend Elementen im Speicher, sie generiert Werte einzeln, bei Bedarf. Bei Sequenzen mit Millionen von Datensätzen (Logdateizeilen, Datenbank-Stream) beträgt der Unterschied im Speicherverbrauch Größenordnungen.

Generator-Ausdrücke

Kompakte Syntax: dasselbe wie List Comprehensions, aber mit runden statt eckigen Klammern.

1g = (c.upper() for c in "foobar")

Das ist kein Tupel, das ist ein Generator. Die Regel ist einfach: eckige Klammern → Liste (eifrig, alles im Speicher), runde Klammern → Generator (faul, ein Element nach dem anderen).

Generatoren sind eine Brücke zwischen prozeduralem und funktionalem Stil in Python: Sie bieten faule Auswertung, ohne in die Monadentheorie abzutauchen.

Map, filter und reduce: drei Säulen der funktionalen Verarbeitung

Drei eingebaute Funktionen, die in funktionalen Sprachen die Grundlage von allem bilden. Python implementiert sie auf seine eigene Weise, und es ist wichtig, die Nuancen der Python-spezifischen Versionen zu kennen.

Map, eine Funktion auf jedes Element anwenden

1def square(x):
2 return x * x
3
4result = map(square, [1, 2, 3])
5print(list(result)) # [1, 4, 9]

map nimmt eine Funktion und eine Sequenz entgegen und gibt einen Iterator mit den Ergebnissen der Anwendung der Funktion auf jedes Element zurück. In Python 3 gibt map einen Iterator zurück, keine Liste, daher benötigen Sie list(), um das Ergebnis zu sehen.

Für einfache Transformationen liest sich eine List Comprehension oft besser:

1[x * x for x in [1, 2, 3]] # same thing, but more familiar

Filter, Elemente nach Bedingung auswählen

1def is_upper(c):
2 return c == c.upper()
3
4result = filter(is_upper, "FreedominObscureandoutlandishcOde")
5print(list(result)) # ['F', 'O', 'O']

filter behält nur die Elemente, für die die Prädikatfunktion True zurückgibt. Wie map gibt es in Python 3 einen Iterator zurück.

Reduce, eine Sequenz auf einen einzelnen Wert falten

Anders als map und filter lebt reduce nicht im eingebauten Gültigkeitsbereich, sondern im Modul functools. Es wendet sequenziell eine Funktion auf Elemente an und akkumuliert das Ergebnis.

1from functools import reduce
2
3def add(a, b):
4 return a + b
5
6print(reduce(add, range(1, 6))) # 15

Unter der Haube: ((((1 + 2) + 3) + 4) + 5) = 15. Zum Summieren von Zahlen gibt es in Python das eingebaute sum(), daher wird reduce häufiger für nicht standardmäßige Faltungen verwendet: einen Baum aus einer flachen Liste aufbauen, verschachtelte Dictionaries zusammenführen, den größten gemeinsamen Teiler einer Sequenz berechnen.

Lambda-Funktionen, anonyme Helfer

Wenn eine Funktion genau einmal benötigt wird und ihr Körper in einen Ausdruck passt, verwendet man lambda:

1lambda arguments: expression

Dasselbe filter-Beispiel lässt sich ohne separates def umschreiben:

1result = list(filter(lambda c: c == c.upper(), "FreedominObscureandoutlandishcOde"))

Lambda ist ein Kompromiss. Plus: Keine Notwendigkeit, für eine triviale Prüfung eine separate Funktion zu deklarieren. Minus: Die Lesbarkeit sinkt, wenn der Ausdruck komplexer wird als ein paar Operationen. Faustregel: Wenn das Lambda nicht in eine Zeile passt oder Sie anfangen, Lambdas ineinander zu verschachteln, extrahieren Sie ein reguläres def.

Wo Lambdas tatsächlich nützlich sind

Das häufigste Szenario ist das Sortieren nach einem nicht standardmäßigen Schlüssel:

1users = [{"name": "Alice", "age": 31}, {"name": "Bob", "age": 25}]
2users.sort(key=lambda u: u["age"])

Oder eine schnelle Transformation in map/filter, wenn der Körper wirklich trivial ist:

1squares = list(map(lambda x: x * x, range(10)))

Aber wenn Sie zu einem Lambda für Logik mit vier oder fünf Operationen greifen, halten Sie inne. Ein Funktionsname wirkt als Dokumentation, und def bietet Platz für einen Docstring.

⁉️🤔 Häufige Fragen

Wann ist eine List Comprehension besser als map?

Eine List Comprehension wie [x*2 for x in data] liest sich natürlicher als list(map(lambda x: x*2, data)). Eine eingebaute Operation mit einem Lambda hat zwei Indirektionsebenen, eine List Comprehension eine. Wenn die Funktion jedoch bereits als separates def existiert, ist map damit prägnant. Die List Comprehension gewinnt bei der Lesbarkeit für einfache Transformationen, besonders mit Filterung durch [... if ...]. Für komplexe Logik mit Zwischenvariablen ist eine reguläre for-Schleife unschlagbar.

Generator oder Liste, wofür soll man sich entscheiden?

Wenn das Ergebnis mehrfach benötigt wird (Iteration, Indexierung, Länge), nehmen Sie eine Liste. Wenn Sie nur einmal iterieren und das Datenvolumen groß ist, spart ein Generator Speicher. Eine Liste mit einer Million Ganzzahlen benötigt etwa 8 MB, ein Generator für denselben Bereich weniger als ein Kilobyte. Aber ein Generator kann nicht „zurückgespult" oder per Index angesprochen werden. Praktische Regel: Daten aus Datei oder Datenbank, Generator; Ergebnis zur wiederholten Verwendung, list().

Beeinträchtigen Lambdas die Lesbarkeit des Codes?

In kleinen Dosen und für triviale Logik, nein. key=lambda x: x["price"] liest sich sofort. Aber sobald ein Lambda aufhört, „ein Gedanke" zu sein, extrahieren Sie es in eine reguläre Funktion. Teams mit Code-Review beschränken Lambdas auf einen Ausdruck ohne Verschachtelung. Wenn ein Kollege mehr als fünf Sekunden braucht, um es zu verstehen, schreiben Sie es als def um. Der Name dient als Dokumentation: def by_price(item): return item["price"] ist klarer als ein anonymes lambda.

Wie viel langsamer ist funktionales Python als prozedurales?

Für die meisten alltäglichen Aufgaben ist der Unterschied vernachlässigbar, Bruchteile einer Mikrosekunde. Eine List Comprehension ist oft schneller als eine äquivalente Schleife, weil sie in C implementiert ist. Der Flaschenhals in Python-Programmen ist nicht die Wahl zwischen map und einer Schleife, sondern Ein-/Ausgabe, Netzwerkanfragen und die Architektur insgesamt. Schreiben Sie klar, analysieren Sie Hot Spots mit cProfile und optimieren Sie nur das, was tatsächlich bremst.

Was Sie heute in Ihrem Code einsetzen sollten

Pythons funktionale Werkzeuge sind kein Dogma, sondern Hebel. Sie ersetzen keinen prozeduralen und objektorientierten Code, sondern ergänzen ihn an bestimmten Stellen.

Beginnen Sie mit List Comprehensions, das ist die häufigste und sicherste Anwendung. Meistern Sie Generatoren: Sobald Sie eine temporäre Liste sehen, die nur einmal verwendet wird, fragen Sie sich, sollte das nicht durch yield ersetzt werden? Verwenden Sie map und filter, wo sie eine Transformationskette wirklich vereinfachen. Heben Sie Lambdas für key= beim Sortieren und triviale Callbacks auf.

Funktionaler Stil in Python dreht sich nicht um „Reinheit", sondern um Ausdruckskraft. Wenn nach dem Ersetzen einer Schleife durch eine List Comprehension der Code klarer wurde, haben Sie alles richtig gemacht. Versuchen Sie, eine problematische Methode funktional umzuschreiben und vergleichen Sie den Diff: Oft spricht das Ergebnis für sich selbst.