Python Lambda: Anonyme Funktionen in Python

Lambda-Funktionen existieren seit Python 1.0 als Mittel für funktionale Programmierung. Heutzutage ist ihr Nutzen weitgehend durch andere Techniken ersetzt worden. Jedoch gibt es weiterhin einige spezialisierte Einsatzgebiete, die versierte Python-Programmierer und -Programmiererinnen kennen sollten.

Was sind Lambda-Funktionen in Python?

Unter dem Begriff „Lambda-Funktion“ versteht man in Python eine anonyme Funktion. Zum Erzeugen einer Lambda-Funktion kommt in Python das lambda-Schlüsselwort zum Einsatz. Ein Lambda-Ausdruck besteht aus dem lambda-Schlüsselwort, gefolgt von einer Liste von Argumenten, einem Doppelpunkt und einem einzelnen Ausdruck („Expression“). Der Ausdruck wird beim Aufruf der Lambda-Funktion mit den Argumenten versehen und evaluiert:

lambda argument: expression

Funktionen sind ein grundlegendes Sprachkonstrukt fast aller Programmiersprachen und stellen die kleinste wiederverwendbare Einheit von Code dar. Üblicherweise werden Funktionen in Python mit dem def-Schlüsselwort definiert. Wir zeigen beispielshalber die Quadratfunktion, die eine Zahl mit sich selbst multipliziert:

# Define square function
def square(num):
  return num * num
# Show that it works
assert square(9) == 81
python

Neben dem bekannten Weg, Funktionen in Python per def-Schlüsselwort zu definieren, kennt die Sprache die „Lambdas“. Das sind kurze, anonyme (sprich: unbenannte) Funktionen, die einen Ausdruck mit Parametern definieren. Lambdas lassen sich überall einsetzen, wo eine Funktion erwartet wird, oder per Zuweisung an einen Namen binden. Hier der zur Quadratfunktion äquivalente Lambda-Ausdruck:

# Create square function
squared = lambda num: num * num
# Show that it works
assert squared(9) == 81
python
Hinweis

Der Term „Lambda-Funktion“ bezeichnet in Python eine mit dem Lambda-Schlüsselwort erzeugte Funktion. Lambda ist nicht etwa der Name einer Funktion und es handelt sich auch nicht um einen der Python-Operatoren.

Was ist der Unterschied zwischen lambda und def?

Zunächst scheint es seltsam, dass Python mit lambda und def zwei Wege kennt, Funktionen zu erzeugen. Dabei ist lambda kein eigenes Feature, sondern lediglich eine andere Schreibweise, um kurze Funktionen lokal zu erzeugen. Jede Funktion, die mit lambda erzeugt wurde, lässt sich nämlich auch via def erzeugen. Jedoch ist dies andersherum nicht der Fall.

Auf syntaktischer Ebene sind sowohl lambda als auch def Schlüsselwörter. Ein Unterschied zwischen den beiden liegt in Python’s strikter Trennung von Anweisung („Statement“) und Ausdruck („Expression“). Kurz gesagt sind Anweisungen Schritte in der Ausführung von Code, während Ausdrücke zu einem Wert evaluiert werden.

Mit def beginnt eine Anweisung (spezifisch ein „Compound Statement“), die weitere Anweisungen enthält. Innerhalb der def-Anweisung – und nur dort – dürfen return-Anweisungen auftauchen. Beim Aufruf der mit def definierten Funktion liefert eine return-Anweisung einen Wert zurück.

Im Gegensatz zur def-Anweisung beginnt lambda einen Ausdruck, der keine Anweisungen enthalten darf. Der Lambda-Ausdruck nimmt ein oder mehrere Argumente entgegen und liefert eine anonyme Funktion zurück. Wird die erzeugte Lambda-Funktion aufgerufen, wird der enthaltene Ausdruck mit den übergebenen Argumenten evaluiert und zurückgegeben.

Was sind die Beschränkungen von Python’s Lambda-Ausdrücken?

Python schränkt den Nutzen von Lambda-Funktionen gezielt ein, da es in der Regel besser ist, Funktionen zu benennen. Dies zwingt Programmierer und Programmiererinnen, über den Sinn der Funktion nachzudenken und Teile klar voneinander abzugrenzen.

Anders als der Körper einer via def-Schlüsselwort definierten Funktion, können Lambdas keine Anweisungen enthalten. Es ist daher nicht möglich, if, for, etc. innerhalb einer Lambda-Funktion einzusetzen. Auch das Auslösen einer Exception ist nicht möglich, da dafür die raise-Anweisung benötigt wird.

Lambda-Funktionen in Python dürfen nur einen einzelnen Ausdruck enthalten, der beim Aufruf evaluiert wird. Innerhalb des Lambda-Ausdrucks können keine Typ-Annotationen verwendet werden. Heutzutage kommen für die meisten Anwendungsfälle von Lambda-Funktionen in Python andere Techniken zum Einsatz. Vor Allem die Comprehensions sind hierbei zu nennen.

Wozu werden Lambda-Funktionen in Python eingesetzt?

Generell entstammen Lambdas der funktionalen Programmierung. In manchen Sprachen, wie JavaScript, werden anonyme Funktionen vielfach eingesetzt, ohne dass dafür ein besonderes Schlüsselwort zum Einsatz käme. In Python dienen Lambda-Ausdrücke dazu, kleine Funktionen lokal ohne großes Drumherum zu erzeugen. Wir zeigen die nützlichsten Anwendungsfälle.

Funktionen höherer Ordnung in Python mit Lambdas bestücken

Lambdas werden oft im Zusammenhang mit Funktionen höherer Ordnung wie map(), filter() und reduce() verwendet. Mit deren Hilfe lassen sich die Elemente eines „Iterable“ ohne Einsatz von Schleifen transformieren. Als Funktionen höherer Ordnung (auf Englisch „higher-order functions“) werden Funktionen bezeichnet, die als Parameter Funktionen entgegennehmen, oder eine Funktion zurückgeben.

Die map()-Funktion nimmt als Parameter eine Funktion und ein Iterable entgegen und führt die Funktion für jedes Element des Iterable aus. Betrachten wir das Problem, Quadratzahlen zu erzeugen: Wir nutzen die map()-Funktion und übergeben einen Lambda-Ausdruck als Argument, der die Quadratfunktion erzeugt. Mit map() wird die Quadratfunktion auf jedes Element der Liste angewandt:

nums = [3, 5, 7]
# Square numbers using using `map()` and `lambda`
squares = map(lambda num: num ** 2, nums)
# Show that it works
assert list(squares) == [9, 25, 49]
python
Hinweis

Seit Python 3.0 geben die map()- und filter()-Funktionen ein Iterable statt einer Liste zurück. Wir nutzen innerhalb der assert-Anweisung einen list()-Aufruf, um das Iterable in eine Liste zu entpacken.

Mit List-Comprehensions existiert heutzutage ein moderner, präferierter Ansatz zum Verarbeiten von Iterables. Anstatt auf map() zurückzugreifen und eine Lambda-Funktion zu erzeugen, beschreiben wir die Operation direkt:

nums = [3, 5, 7]
# Square numbers using list comprehension
squares = [num ** 2 for num in nums]
# Show that it works
assert squares == [9, 25, 49]
python

Mit der filter()-Funktion lassen sich die Elemente eines Iterable filtern. Wir erweitern unser Beispiel, sodass nur gerade Quadratzahlen erzeugt werden:

# List of numbers 1–4
nums = [1, 2, 3, 4]
# Square each number
squares = list(map(lambda num: num ** 2, nums))
# Filter out the even squares
even_squares = filter(lambda square: square % 2 == 0, squares)
# Show that it works
assert list(even_squares) == [4, 16]
python

Wir zeigen wiederum den modernen, bevorzugten Ansatz, per List-Comprehension dasselbe Ergebnis ohne Einsatz von Lambdas und higher-order Functions zu generieren. Dabei nutzen wir den if-Teil der Comprehension, um aus den erzeugten Quadratzahlen die geraden herauszufiltern:

# List of numbers 1–4 squared
squares = [num ** 2 for num in range(1, 5)]
# Filter out the even squares
even_squares = [square for square in squares if square % 2 == 0]
# Show that it works
assert even_squares == [4, 16]
python
Hinweis

Python’s reduce()-Funktion ist seit Python 3.0 nicht mehr Teil der Standard-Bibliothek. Sie wurde in das functools-Modul ausgelagert.

Schlüssel-Funktionen in Python als Lambdas realisieren

Comprehensions haben den Einsatz der klassischen higher-order Functions map() und filter() in Python größtenteils abgelöst. Mit den sog. „Key-Functions“, zu Deutsch „Schlüssel-Funktionen“ besteht jedoch ein Anwendungsszenario, in dem Lambdas ihre Stärken voll ausspielen.

Die Python-Vergleichsfunktionen sorted(), min() und max() operieren auf Iterables. Beim Aufruf wird jedes Element des Iterable einem Vergleich unterzogen. Die drei Funktionen nehmen allesamt eine Schlüssel-Funktion als optionalen key-Parameter entgegen. Die Schlüssel-Funktion wird für jedes Element aufgerufen und liefert einen Schlüssel-Wert für die Vergleichsoperation.

Betrachen wir als Beispiel, das folgende Problem. Uns liegt ein Ordner mit Bild-Dateien vor, deren Namen in einer Python-Liste abgebildet sind. Wir möchten die Liste sortieren. Die Dateinamen beginnen alle mit img, gefolgt von einer Nummerierung:

# List of image file names
images = ['img1', 'img2', 'img30', 'img3', 'img22', 'img100']
python

Nutzen wir Python’s sorted()-Funktion, kommt die sog. „lexikographische Ordnung“ zum Einsatz. Das bedeutet, dass aufeinander folgende Ziffern als einzelne Zahlen behandelt werden. So werden die Nummern ['1', '2', '100'] in die Reihenfolge ['1', '100', '2'] gebracht. Das Ergebnis entspricht nicht unseren Erwartungen:

# Sort using lexicographic order
sorted_image = sorted(images)
# Show that it works
assert sorted_image == ['img1', 'img100', 'img2', 'img22', 'img3', 'img30']
python

Um die Sortierung unseren Wünschen entsprechend zu gestalten, übergeben wir einen lambda-Ausdruck, der eine Schlüssel-Funktion erzeugt. Die Schlüssel-Funktion extrahiert den numerischen Teil eines Dateinamens, der von sorted() als Schlüssel verwendet wird:

# Extract numeric component and sort as integers
sorted_image = sorted(images, key=lambda name: int(name[3:]))
# Show that it works
assert sorted_image == ['img1', 'img2', 'img3', 'img22', 'img30', 'img100']
python

Die Schlüssel-Funktion wird nur lokal und nur einmalig verwendet. Es ist unnötig, dafür extra eine benannte Funktion zu definieren. Lambdas sind daher das geeignete Mittel zum Erzeugen von Schlüssel-Funktionen. Betrachten wir zwei weitere Beispiele.

Neben sorted() nehmen die eingebauten Python-Funktionen min() und max() eine optionale Schlüssel-Funktion entgegen. Die Funktionen finden das kleinste bzw. größte Element einer Liste oder sonstigem Iterable. Was genau das kleinste bzw. größte Element ausmacht, ist Definitionssache und lässt sich über die Schlüsselfunktion festlegen.

Bei Listen einfacher Werte, beispielsweise einer Liste von Zahlen, ist klar, was mit „kleinstem“ bzw. „größtem“ Element gemeint ist. Hier benötigen wir keine spezielle Schlüssel-Funktion:

nums = [42, 69, 51, 13]
assert min(nums) == 13
assert max(nums) == 69
python
Hinweis

Wird keine Schlüssel-Funktion übergeben, kommt implizit die Identitäts-Funktion f(x) = x zum Einsatz. Diese lässt sich leicht als Python-Lambda mit lambda x: x definieren.

Was jedoch, wenn die Elemente eines Iterable jeweils mehrere Daten umfassen? Stellen wir uns eine List von Dicts vor, die Personen mit Name und Alter repräsentieren. Nach welchem Kriterium sollen min() und max() entscheiden, welches Element am kleinsten bzw. größten ist? Genau dafür kommt die Schlüssel-Funktion zum Einsatz.

Zum Veranschaulichen, wie Schlüssel-Funktionen funktionieren, benötigen wir Beispiel-Daten. Wir erzeugen eine Funktion Person(), die als Konstruktor dient:

# Constructor function for dict representing a person
def Person(name, age):
  return {'name': name, 'age': age}
# Check that it works as expected
assert Person('Jim', 42) == {'name': 'Jim', 'age': 42}
python

Mithilfe unserer Konstruktor-Funktion erzeugen wir eine Liste von Personen:

# Create list of people
people = [Person('Jim', 42), Person('Jack', 51), Person('John', 69)]
python

Im Anschluss finden wir die älteste Person via max()-Aufruf. Dabei erzeugen wir per Lambda-Ausdruck eine Schlüssel-Funktion, die ein Person-Dict entgegennimmt und daraus das Alter als Vergleichselement extrahiert:

# Find the oldest person
oldest = max(people, key=lambda person: person['age'])
# Check that it works
assert oldest == Person('John', 69)
python

Der Ansatz funktioniert genau so für die min()-Funktion. Hier definieren wir die Schlüssel-Funktion außerhalb des min()-Aufrufs, wobei wir wiederum einen Lambda-Ausdruck nutzen. Dies verbessert die Lesbarkeit und lohnt sich, wenn die Schlüsselfunktion mehrfach lokal Verwendung findet:

# Define key function to compare people by age
by_age = lambda person: person['age']
# Find the youngest person
youngest = min(people, key=by_age)
# Check that it works
assert youngest == Person('Jim', 42)
python

Closures mit Python Lambdas erzeugen

Ein weiterer Einsatzort für Python Lambdas liegt im Definieren sog. „Closures“. Dabei handelt es sich um Funktionen, die von anderen Funktionen erzeugt werden und dabei einen Wert speichern. Mit Closures lassen sich beispielsweise Familien ähnlicher Funktionen erstellen. Wir zeigen das geläufige Beispiel, Potenzfunktionen zu erzeugen.

Potenzfunktionen nehmen ein Argument entgegen und potenzieren dieses. Bekannte Beispiele sind die Quadratfunktion f(x) = x ^ 2 und die kubische Funktion f(x) = x ^ 3. Mittels einer Konstruktor-Funktion lassen sich beliebige Potenzfunktionen als Closures erzeugen. Wir nutzen einen Lambda-Ausdruck und sparen uns damit das definieren einer inneren benannten Funktion:

# Define constructor function for power functions
def power(n):
  return lambda num: num ** n

# Create square and cubic functions as closures
square = power(2)
cubic = power(3)

# Show that it works
assert square(10) == 100
assert cubic(10) == 1000
python

Immediately Invoked Function Expression (IIFE) mit Python Lambdas

Bei IIFE, ausgesprochen „iffy“ handelt es sich um ein bekanntes Muster in JavaScript. Dabei wird eine anonyme Funktion definiert und sofort ausgeführt.

Wenn auch durch die Beschränkung in Python wenig nützlich, lassen sich Lambdas als IIFEs verwenden. Wir benötigen lediglich Klammern um den Lambda-Ausdruck herum:

(lambda num: num * num)
python

Sowie ein weiteres Paar Klammern, welche das oder die Argumente enthält:

assert (lambda num: num * num)(3) == 9
python