Lambda-Funk­tio­nen exis­tie­ren seit Python 1.0 als Mittel für funk­tio­na­le Pro­gram­mie­rung. Heut­zu­ta­ge ist ihr Nutzen weit­ge­hend durch andere Techniken ersetzt worden. Jedoch gibt es weiterhin einige spe­zia­li­sier­te Ein­satz­ge­bie­te, die versierte Python-Pro­gram­mie­rer und -Pro­gram­mie­re­rin­nen kennen sollten.

Was sind Lambda-Funk­tio­nen 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üs­sel­wort zum Einsatz. Ein Lambda-Ausdruck besteht aus dem lambda-Schlüs­sel­wort, gefolgt von einer Liste von Ar­gu­men­ten, einem Dop­pel­punkt und einem einzelnen Ausdruck („Ex­pres­si­on“). Der Ausdruck wird beim Aufruf der Lambda-Funktion mit den Ar­gu­men­ten versehen und evaluiert:

lambda argument: expression

Funk­tio­nen sind ein grund­le­gen­des Sprach­kon­strukt fast aller Pro­gram­mier­spra­chen und stellen die kleinste wie­der­ver­wend­ba­re Einheit von Code dar. Üb­li­cher­wei­se werden Funk­tio­nen in Python mit dem def-Schlüs­sel­wort definiert. Wir zeigen bei­spiels­hal­ber die Qua­drat­funk­ti­on, die eine Zahl mit sich selbst mul­ti­pli­ziert:

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

Neben dem bekannten Weg, Funk­tio­nen in Python per def-Schlüs­sel­wort zu de­fi­nie­ren, kennt die Sprache die „Lambdas“. Das sind kurze, anonyme (sprich: un­be­nann­te) Funk­tio­nen, die einen Ausdruck mit Pa­ra­me­tern de­fi­nie­ren. Lambdas lassen sich überall einsetzen, wo eine Funktion erwartet wird, oder per Zuweisung an einen Namen binden. Hier der zur Qua­drat­funk­ti­on äqui­va­len­te Lambda-Ausdruck:

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

Der Term „Lambda-Funktion“ be­zeich­net in Python eine mit dem Lambda-Schlüs­sel­wort erzeugte Funktion. Lambda ist nicht etwa der Name einer Funktion und es handelt sich auch nicht um einen der Python-Ope­ra­to­ren.

Was ist der Un­ter­schied zwischen lambda und def?

Zunächst scheint es seltsam, dass Python mit lambda und def zwei Wege kennt, Funk­tio­nen zu erzeugen. Dabei ist lambda kein eigenes Feature, sondern lediglich eine andere Schreib­wei­se, um kurze Funk­tio­nen lokal zu erzeugen. Jede Funktion, die mit lambda erzeugt wurde, lässt sich nämlich auch via def erzeugen. Jedoch ist dies an­ders­her­um nicht der Fall.

Auf syn­tak­ti­scher Ebene sind sowohl lambda als auch def Schlüs­sel­wör­ter. Ein Un­ter­schied zwischen den beiden liegt in Python’s strikter Trennung von Anweisung („Statement“) und Ausdruck („Ex­pres­si­on“). Kurz gesagt sind An­wei­sun­gen Schritte in der Aus­füh­rung von Code, während Ausdrücke zu einem Wert evaluiert werden.

Mit def beginnt eine Anweisung (spe­zi­fisch ein „Compound Statement“), die weitere An­wei­sun­gen enthält. Innerhalb der def-Anweisung – und nur dort – dürfen return-An­wei­sun­gen auf­tau­chen. Beim Aufruf der mit def de­fi­nier­ten Funktion liefert eine return-Anweisung einen Wert zurück.

Im Gegensatz zur def-Anweisung beginnt lambda einen Ausdruck, der keine An­wei­sun­gen enthalten darf. Der Lambda-Ausdruck nimmt ein oder mehrere Argumente entgegen und liefert eine anonyme Funktion zurück. Wird die erzeugte Lambda-Funktion auf­ge­ru­fen, wird der ent­hal­te­ne Ausdruck mit den über­ge­be­nen Ar­gu­men­ten evaluiert und zu­rück­ge­ge­ben.

Was sind die Be­schrän­kun­gen von Python’s Lambda-Aus­drü­cken?

Python schränkt den Nutzen von Lambda-Funk­tio­nen gezielt ein, da es in der Regel besser ist, Funk­tio­nen zu benennen. Dies zwingt Pro­gram­mie­rer und Pro­gram­mie­re­rin­nen, über den Sinn der Funktion nach­zu­den­ken und Teile klar von­ein­an­der ab­zu­gren­zen.

Anders als der Körper einer via def-Schlüs­sel­wort de­fi­nier­ten Funktion, können Lambdas keine An­wei­sun­gen enthalten. Es ist daher nicht möglich, if, for, etc. innerhalb einer Lambda-Funktion ein­zu­set­zen. Auch das Auslösen einer Exception ist nicht möglich, da dafür die raise-Anweisung benötigt wird.

Lambda-Funk­tio­nen in Python dürfen nur einen einzelnen Ausdruck enthalten, der beim Aufruf evaluiert wird. Innerhalb des Lambda-Ausdrucks können keine Typ-An­no­ta­tio­nen verwendet werden. Heut­zu­ta­ge kommen für die meisten An­wen­dungs­fäl­le von Lambda-Funk­tio­nen in Python andere Techniken zum Einsatz. Vor Allem die Com­pre­hen­si­ons sind hierbei zu nennen.

Wozu werden Lambda-Funk­tio­nen in Python ein­ge­setzt?

Generell ent­stam­men Lambdas der funk­tio­na­len Pro­gram­mie­rung. In manchen Sprachen, wie Ja­va­Script, werden anonyme Funk­tio­nen vielfach ein­ge­setzt, ohne dass dafür ein be­son­de­res Schlüs­sel­wort zum Einsatz käme. In Python dienen Lambda-Ausdrücke dazu, kleine Funk­tio­nen lokal ohne großes Drumherum zu erzeugen. Wir zeigen die nütz­lichs­ten An­wen­dungs­fäl­le.

Funk­tio­nen höherer Ordnung in Python mit Lambdas bestücken

Lambdas werden oft im Zu­sam­men­hang mit Funk­tio­nen höherer Ordnung wie map(), filter() und reduce() verwendet. Mit deren Hilfe lassen sich die Elemente eines „Iterable“ ohne Einsatz von Schleifen trans­for­mie­ren. Als Funk­tio­nen höherer Ordnung (auf Englisch „higher-order functions“) werden Funk­tio­nen be­zeich­net, die als Parameter Funk­tio­nen ent­ge­gen­neh­men, oder eine Funktion zu­rück­ge­ben.

Die map()-Funktion nimmt als Parameter eine Funktion und ein Iterable entgegen und führt die Funktion für jedes Element des Iterable aus. Be­trach­ten wir das Problem, Qua­drat­zah­len zu erzeugen: Wir nutzen die map()-Funktion und übergeben einen Lambda-Ausdruck als Argument, der die Qua­drat­funk­ti­on erzeugt. Mit map() wird die Qua­drat­funk­ti­on 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()-Funk­tio­nen 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-Com­pre­hen­si­ons existiert heut­zu­ta­ge ein moderner, prä­fe­rier­ter Ansatz zum Ver­ar­bei­ten von Iterables. Anstatt auf map() zu­rück­zu­grei­fen und eine Lambda-Funktion zu erzeugen, be­schrei­ben 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 Qua­drat­zah­len 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, be­vor­zug­ten Ansatz, per List-Com­pre­hen­si­on dasselbe Ergebnis ohne Einsatz von Lambdas und higher-order Functions zu ge­ne­rie­ren. Dabei nutzen wir den if-Teil der Com­pre­hen­si­on, um aus den erzeugten Qua­drat­zah­len die geraden her­aus­zu­fil­tern:

# 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-Bi­blio­thek. Sie wurde in das functools-Modul aus­ge­la­gert.

Schlüssel-Funk­tio­nen in Python als Lambdas rea­li­sie­ren

Com­pre­hen­si­ons haben den Einsatz der klas­si­schen higher-order Functions map() und filter() in Python größ­ten­teils abgelöst. Mit den sog. „Key-Functions“, zu Deutsch „Schlüssel-Funk­tio­nen“ besteht jedoch ein An­wen­dungs­sze­na­rio, in dem Lambdas ihre Stärken voll aus­spie­len.

Die Python-Ver­gleichs­funk­tio­nen sorted(), min() und max() operieren auf Iterables. Beim Aufruf wird jedes Element des Iterable einem Vergleich un­ter­zo­gen. Die drei Funk­tio­nen nehmen allesamt eine Schlüssel-Funktion als op­tio­na­len key-Parameter entgegen. Die Schlüssel-Funktion wird für jedes Element auf­ge­ru­fen und liefert einen Schlüssel-Wert für die Ver­gleichs­ope­ra­ti­on.

Betrachen wir als Beispiel, das folgende Problem. Uns liegt ein Ordner mit Bild-Dateien vor, deren Namen in einer Python-Liste ab­ge­bil­det sind. Wir möchten die Liste sortieren. Die Da­tei­na­men beginnen alle mit img, gefolgt von einer Num­me­rie­rung:

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

Nutzen wir Python’s sorted()-Funktion, kommt die sog. „le­xi­ko­gra­phi­sche Ordnung“ zum Einsatz. Das bedeutet, dass auf­ein­an­der folgende Ziffern als einzelne Zahlen behandelt werden. So werden die Nummern ['1', '2', '100'] in die Rei­hen­fol­ge ['1', '100', '2'] gebracht. Das Ergebnis ent­spricht nicht unseren Er­war­tun­gen:

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

Um die Sor­tie­rung unseren Wünschen ent­spre­chend zu gestalten, übergeben wir einen lambda-Ausdruck, der eine Schlüssel-Funktion erzeugt. Die Schlüssel-Funktion ex­tra­hiert den nu­me­ri­schen Teil eines Da­tei­na­mens, 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 de­fi­nie­ren. Lambdas sind daher das geeignete Mittel zum Erzeugen von Schlüssel-Funk­tio­nen. Be­trach­ten wir zwei weitere Beispiele.

Neben sorted() nehmen die ein­ge­bau­ten Python-Funk­tio­nen min() und max() eine optionale Schlüssel-Funktion entgegen. Die Funk­tio­nen finden das kleinste bzw. größte Element einer Liste oder sonstigem Iterable. Was genau das kleinste bzw. größte Element ausmacht, ist De­fi­ni­ti­ons­sa­che und lässt sich über die Schlüs­sel­funk­ti­on festlegen.

Bei Listen einfacher Werte, bei­spiels­wei­se 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 Iden­ti­täts-Funktion f(x) = x zum Einsatz. Diese lässt sich leicht als Python-Lambda mit lambda x: x de­fi­nie­ren.

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 re­prä­sen­tie­ren. Nach welchem Kriterium sollen min() und max() ent­schei­den, welches Element am kleinsten bzw. größten ist? Genau dafür kommt die Schlüssel-Funktion zum Einsatz.

Zum Ver­an­schau­li­chen, wie Schlüssel-Funk­tio­nen funk­tio­nie­ren, benötigen wir Beispiel-Daten. Wir erzeugen eine Funktion Person(), die als Kon­struk­tor 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 Kon­struk­tor-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 ent­ge­gen­nimmt und daraus das Alter als Ver­gleichs­ele­ment ex­tra­hiert:

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

Der Ansatz funk­tio­niert genau so für die min()-Funktion. Hier de­fi­nie­ren wir die Schlüssel-Funktion außerhalb des min()-Aufrufs, wobei wir wiederum einen Lambda-Ausdruck nutzen. Dies ver­bes­sert die Les­bar­keit und lohnt sich, wenn die Schlüs­sel­funk­ti­on mehrfach lokal Ver­wen­dung 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 Ein­satz­ort für Python Lambdas liegt im De­fi­nie­ren sog. „Closures“. Dabei handelt es sich um Funk­tio­nen, die von anderen Funk­tio­nen erzeugt werden und dabei einen Wert speichern. Mit Closures lassen sich bei­spiels­wei­se Familien ähnlicher Funk­tio­nen erstellen. Wir zeigen das geläufige Beispiel, Po­tenz­funk­tio­nen zu erzeugen.

Po­tenz­funk­tio­nen nehmen ein Argument entgegen und po­ten­zie­ren dieses. Bekannte Beispiele sind die Qua­drat­funk­ti­on f(x) = x ^ 2 und die kubische Funktion f(x) = x ^ 3. Mittels einer Kon­struk­tor-Funktion lassen sich beliebige Po­tenz­funk­tio­nen als Closures erzeugen. Wir nutzen einen Lambda-Ausdruck und sparen uns damit das de­fi­nie­ren 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

Im­me­dia­te­ly Invoked Function Ex­pres­si­on (IIFE) mit Python Lambdas

Bei IIFE, aus­ge­spro­chen „iffy“ handelt es sich um ein bekanntes Muster in Ja­va­Script. Dabei wird eine anonyme Funktion definiert und sofort aus­ge­führt.

Wenn auch durch die Be­schrän­kung 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
Zum Hauptmenü