Lambda, filter, reduce und map

Lambda-Operator

Ring als Symbol der for-Schleife

Wenn es nach Guido van Rossum, dem Autor von Python, gegangen wäre, würde dieses Kapitel in unserem Tutorial fehlen. Guido van Rossum hatte lambda, reduce(), filter() and map() noch nie gemocht und sie bereits 1993 widerwillig in Python aufgenommnen, nachdem er eine Codeerweiterung mit diesen Funktionalitäten von einem, wie er glaubt, Lisp-Hacker erhalten hatte.1 In Python3 sollten sie nach seinem Willen verschwinden. Dies begründete er wie folgt:

Der lambda-Operator bietet eine Möglichkeit anonyme Funktionen, also Funktionen ohne Namen, zu schreiben und zu benutzen. Lambda-Funktionen kommen aus der funktionalen Programmierung und wurden insbesondere durch die Programmiersprache Lisp besonders bekannt. Sie können eine beliebe Anzahl von Parametern haben, führen einen Ausdruck aus und liefern den Wert dieses Ausdrucks als Rückgabewert zurück.

Anonyme Funktionen sind insbesondere bei der Anwendung der map-, filter- und reduce-Funktionen besonders vorteilhaft.

Allgemeine Syntax einer Lambda-Funktion:
lambda Argumentenliste: Ausdruck
Die Argumentenliste besteht aus einer durch Kommata getrennten Liste von Argumenten und der Ausdruck ist ein Ausdruck der diese Argumente benutzt.
Beispiel:
>>> f = lambda x, y : x + y
>>> f(1,1)
2
Die obige Funktion hat zwei Argumente, nämlich x und y und liefert als Ergebnis die Summe der beiden Argumente zurück.

Die obige Notation wirkt wie eine Spielerei, noch dazu eine unnötige Spielerei, wenn man bedenkt, dass man das obige Beispiel ebenso leicht hätte konventionell, wie wir im folgenden sehen, programmieren können:
>>> def f(x,y):
...     return x + y
... 
>>> f(1,1)
2

Wir werden nun zeigen, dass lambda-Ausdrücke durchaus keine Spielerei sind und sinnvoll eingesetzt werden können.

map-Funktion

Den Vorteil des lambda-Operators zeigt sich zum Beispiel im Zusammenspiel mit der map-Funktion.
Map ist eine Funktion mit zwei Argumenten:
r = map(func, seq)
Das erste Argument func ist eine Funktion und das zweite eine Sequenz (z.B. eine Liste oder Tupel) seq. map wendet die Funktion func auf alle Elemente von seq an und schreibt die Ergebnisse in eine neue Liste.
Im folgenden Beispiel wird die Funktion fahrenheit auf ein Tupel temp mit Temperaturen in Grad Celsius angewendet. Wir erhalten eine Liste F mit Temperaturen in Grad Fahrenheit. Anschließend wenden wir mittels map die Funktion celsius auf alle Elemente von F an, um wieder eine Liste mit Celsius-Temperaturen zu erhalten.
def fahrenheit(T):
    return ((9.0 / 5) * T + 32)
def celsius(T):
    return (5.0 / 9) * ( T - 32 )
temp = (36.5, 37, 37.5,39)

F = list(map(fahrenheit, temp))
C = list(map(celsius, F))
Die Definition der Funktionen zur Wandlung in Fahrenheit und Celsius hätte man sich auch sparen können, wie wir in der folgenden interaktiven Sitzung sehen können:
>>> Celsius = [39.2, 36.5, 37.3, 37.8]
>>> Fahrenheit = list(map(lambda x: (9.0/5)*x + 32, Celsius))
>>> print(Fahrenheit)
[102.56, 97.7, 99.14, 100.03999999999999]
>>> C = list(map(lambda x: (float(5)/9)*(x-32), Fahrenheit))
>>> print(C)
[39.2, 36.5, 37.300000000000004, 37.8]
>>> 
map kann auch gleichzeitig auf mehrere Listen angewendet werden. Dann werden die Argumente entsprechend ihrer Position und der Reihenfolge der Listenargumente entsprechend mit den Werten aus den Listen versorgt.
>>> a = [1,2,3,4]
>>> b = [17,12,11,10]
>>> c = [-1,-4,5,9]
>>> list(map(lambda x,y:x+y, a,b))
[18, 14, 14, 14]
>>> list(map(lambda x,y,z:x+y+z, a,b,c))
[17, 10, 19, 23]
>>> list(map(lambda x,y,z : 2.5*x + 2*y - z, a,b,c))
[37.5, 33.0, 24.5, 21.0]
>>> 
Wir sehen in dem obigen Beispiel, dass der Parameter x seine Werte aus der Liste a, der Parameter y seine Werte aus der Liste b und der Parameter z seine Werte aus der Liste c beziehen.

Filtern

Die Funktion filter(funktion, liste) bietet eine elegante Möglichkeit diejenigen Elemente aus der Liste liste herauszufiltern, für die die Funktion funktion True liefert.
Die Funktion filter(f,l) benötigt als erstes Argument eine Funktion f die Wahrheitswerte liefert. Diese Funktion wird dann auf jedes Argument der Liste l angewendet. Liefert f True für ein x, dann wird x in der Ergebnisliste übernommen.
>>> fibonacci = [0,1,1,2,3,5,8,13,21,34,55]
>>> odd_numbers = list(filter(lambda x: x % 2, fibonacci))
>>> print(odd_numbers)
[1, 1, 3, 5, 13, 21, 55]
>>> even_numbers = list(filter(lambda x: x % 2 == 0, fibonacci))
>>> print(even_numbers)
[0, 2, 8, 34]
>>> 
>>> 
>>> # or alternatively:
... 
>>> even_numbers = list(filter(lambda x: x % 2 -1, fibonacci))
>>> print(even_numbers)
[0, 2, 8, 34]
>>> 

reduce-Funktion

Wie wir bereits im Anfang dieses Kapitels erwähnt haben, mag Guido van Rossum keines der hier eingeführten Konstrukte. reduce hasst er am meisten, wie wir in seinem Posting vom 10. März 2005 erfahren können.2

Aber mit reduce() konnte er sich durchsetzen. reduce() wurde in das Modul functools verbannt, gehört also nicht mehr zum Kern der Sprache.

Die Funktion
reduce(func, seq) 
wendet die Funktion func() fortlaufend auf eine Sequenz seq an und liefert einen einzelnen Wert zurück.

Falls seq = [ s1, s2, s3, ... , sn ] ist, funktioniert der Aufruf reduce(func, seq) wie folgt:

Für den Fall n = 4 können wir die vorige Erklärung auch wie folgt illustrieren:

Reduce

Das folgende Beispiel zeigt die Arbeitsweise von reduce() an einem einfachen Beispiel. Um mit reduce zu arbeiten, müssen wir in Python3 das Modul functools importieren. Das ist der wesentliche Unterschied zu früheren Python-Versionen wie zum Beispiel Python 2.7:
>>> import functools
>>> functools.reduce(lambda x,y: x+y, [47,11,42,13])
113
>>> 
Im folgenden Beispiel veranschaulichen wir die Arbeitsweise von reduce an einem konkreten Beispiel:
>>> reduce(lambda x,y: x+y, [47,11,42,13])
113
Im folgenden Diagram sind die Ergebniswerte dargestellt:

Veranschulichung von Reduce

Übungen

  1. In einer Buchhandlung findet sich in einem Abrechnungsprogramm in Python eine Liste mit Unterlisten mit folgendem Aufbau:
    Bestell-Nummer Buchtitel und Autor Anzahl Einzelpreis
    34587 Learning Python, Mark Lutz 4 40.95
    98762 Programming Python, Mark Lutz" 5 56.80
    77226 Head First Python, Paul Barry 3 32.95
    Schreibe ein Programm, dass als Ergebnis eine Liste mit Zweier-Tupel liefert. Jedes Tupel besteht aus der Bestellnummer und dem Produkt aus der Anzahl und dem Einzelpreis. Das Produkt soll jedoch um 10,- € erhöht werden, wenn der Bestellwert unter 100,00 € liegt.
    Schreibe ein Python-Programm unter Benutzung von lambda und map.
  2. Situation wie in voriger Aufgabe, aber jetzt sehen die Unterlisten wie folgt aus: [Bestellnummer, (Artikel-Nr, Anzahl, Einzelpreis), ... (Artikel-Nr, Anzahl, Einzelpreis) ] Schreibe wieder ein Programm, was eine Liste mit 2-Tupel (Bestellnummer, Gesamtpreis) liefert.

Lösungen zu den Übungen

  1. orders = [ ["34587","Learning Python, Mark Lutz", 4, 40.95], 
    	   ["98762","Programming Python, Mark Lutz", 5, 56.80], 
               ["77226","Head First Python, Paul Barry",3,32.95]]
    
    min_order = 100
    invoice_totals = list(map(lambda x: x if x[1] >= min_order else (x[0], x[1] + 10) , 
    			  map(lambda x: (x[0],x[2] * x[3]), orders)))
    
    print(invoice_totals)
    
  2. from functools import reduce
    
    orders = [ ["34587",("5464", 4, 9.99), ("8274",18,12.99), ("9744", 9, 44.95)], 
    	   ["34588",("5464", 9, 9.99), ("9744", 9, 44.95)],
    	   ["34588",("5464", 9, 9.99)],
               ["34587",("8732", 7, 11.99), ("7733",11,18.99), ("9710", 5, 39.95)] ]
    
    min_order = 100
    invoice_totals = list(map(lambda x: [x[0]] + list(map(lambda y: y[1]*y[2], x[1:])), orders))
    invoice_totals = list(map(lambda x: [x[0]] + [reduce(lambda a,b: a + b, x[1:])], invoice_totals))
    invoice_totals = list(map(lambda x: x if x[1] >= min_order else (x[0], x[1] + 10), invoice_totals))
    
    print (invoice_totals)
    
    





Fußnoten



1 Guido van van Rossum: All Things Pythonic: The fate of reduce() in Python 3000, March 10, 2005

2 dto., wörtlich "So now reduce(). This is actually the one I've always hated most, because, apart from a few examples involving + or *, almost every time I see a reduce() call with a non-trivial function argument, I need to grab pen and paper to diagram what's actually being fed into that function before I understand what the reduce() is supposed to do."