Lambdas
Kurz gesagt: Ein Lambda ist eine kurze Funktion ohne Namen mit einem einzigen Ausdruck, die du als Wert übergibst, zum Beispiel als key von sorted.
Problem
Eine ganze Funktion für eine Regel
Die Bestellungen sollen nach Summe sortiert werden.
class Order: def __init__(self, customer, total): self.customer = customer self.total = totalorders = [ Order("Maria", 75.50), Order("Tom", 12.90), Order("Laura", 40.00),]
def by_total(order): return order.total
orders.sort(key=by_total)
for o in orders: print(f"{o.customer}: {o.total:.2f} €")Tom: 12.90 €Laura: 40.00 €Maria: 75.50 €sort(key=...) nimmt eine Funktion, die für jedes Element sagt, wonach es sortiert wird. Hier ist das by_total: Für eine Bestellung gibt sie deren Summe zurück.
Es funktioniert. Aber für eine Regel („nach Summe“) schreibst du eine ganze Funktion mit Namen und Leerzeilen drumherum, weit weg von der Stelle, an der du sie benutzt. Und sie wird nur hier aufgerufen. Du willst nur die Regel übergeben.
Die Idee
Ein Lambda: nur die Regel
Dieselbe Sortierung in einer Zeile:
orders.sort(key=lambda o: o.total)lambda o: o.total heißt Lambda-Ausdruck, kurz Lambda. Das ist eine Funktion ohne Namen: Nach lambda kommen die Parameter, nach dem Doppelpunkt der Rumpf. return schreibst du nicht: Der Wert des Rumpfs ist das Ergebnis.
Die Schreibweisen:
- Ein Parameter:
lambda o: o.total > 50. Mehrere:lambda a, b: a.total + b.total. Ohne Parameter:lambda: .... - Der Rumpf ist ein Ausdruck. Braucht die Regel mehrere Zeilen, ein
ifoder eine Schleife, ist sie eine normale Funktion mitdef.
by_total = lambda o: o.totaltotal_of_two = lambda a, b: a.total + b.totalguest = lambda: Order("Gast", 0)
orders.sort(key=by_total)for o in orders: print(f"{o.customer}: {o.total:.2f} €")print(total_of_two(orders[0], orders[1]))print(guest().customer)Tom: 12.90 €Laura: 40.00 €Maria: 75.50 €52.9GastEin Lambda ist ein Wert: Du kannst es in eine Variable legen und später übergeben. Das gilt auch für eine Funktion, die mit def geschrieben ist: by_total ohne Klammern ist die Funktion selbst, deshalb hat sort(key=by_total) funktioniert.
Genauer, für Neugierige
Der Stilleitfaden PEP 8 rät: Hat die Regel einen Namen, schreib sie mit def, nicht als name = lambda .... Ein Lambda ist für die Stelle, an der die Regel direkt übergeben wird, wie in sort(key=lambda o: o.total). In dieser Lektion stehen manche Regeln in Variablen, damit du siehst, dass ein Lambda ein Wert wie jeder andere ist.
Die Idee
Eine Funktion ist ein Wert
Woher weiß sort, dass lambda o: o.total die Schlüsselfunktion ist? Mehr muss es nicht wissen. In Python ist eine Funktion ein Wert, wie eine Zahl oder ein String: Du kannst sie in eine Variable legen, an eine andere Funktion übergeben und mit Klammern aufrufen.
student = lambda price: price * 0.8minus_five = lambda price: price - 5
print(student(50))print(minus_five(50))40.045student ist ein Objekt vom Typ function. Du rufst es wie jede Funktion auf: student(50). Zwei Preisregeln und keine einzige Klasse.
Java braucht hier ein funktionales Interface: ein Interface mit einer Methode, das sagt, welche Art von Regel es ist. Python braucht so etwas nicht. Jede Funktion kann übergeben und aufgerufen werden.
student = lambda price: price * 0.8minus_five = lambda price: price - 5
print(student(50))print(minus_five(50))Schritt für Schritt, in Worten
- Das Lambda ist ein Objekt vom Typ function, und student zeigt darauf. Sein Rumpf läuft noch nicht.
- Eine zweite Regel, ein zweites Objekt. Keine einzige Klasse ist geschrieben.
- student(50) übergibt 50 an das Lambda: price ist 50, und price * 0.8 ergibt 40.0.
- minus_five(50): dieselbe Zahl, eine andere Regel, 45.
Genauer, für Neugierige
- Willst du aufschreiben, welche Regel du erwartest, hat Python einen Typhinweis (type hint):
Callable[[float], float]heißt „eine Funktion, die eine Zahl nimmt und eine Zahl zurückgibt“. Python prüft ihn nicht. Das Falsche kommt bis zu der Stelle, an der es aufgerufen wird, und erst dort bricht das Programm ab:
from collections.abc import Callable
PriceRule = Callable[[float], float]
def show(rule: PriceRule, price: float): print(rule(price))
show(lambda price: price * 0.8, 50)show("Student", 50)40.0Traceback (most recent call last): ...TypeError: 'str' object is not callablecallable(x)sagt dir, ob manxaufrufen kann:callable(student)istTrue, undcallable("Student")istFalse.
Die Idee
Die vier Arten von Regeln
Der Typ jeder Regel ist function, aber es gibt vier Arten von Regeln. Es hilft, sie zu erkennen, weil die Funktionen, die Regeln annehmen, eine bestimmte Art wollen:
- Bedingung: nimmt einen Wert, gibt
TrueoderFalsezurück. „Ist die Bestellung groß?“ - Umwandlung: nimmt eine Sache, gibt eine andere zurück. Aus einer Bestellung ein Etikett.
- Aktion: nimmt einen Wert und tut etwas damit: gibt ihn aus, speichert ihn. Ihr Ergebnis wird nicht benutzt.
- Lieferant: nimmt nichts, gibt ein neues Objekt zurück, wenn du fragst.
big = lambda o: o.total >= 50label = lambda o: o.customer.upper()show = lambda text: print("-> " + text)guest = lambda: Order("Gast", 0)
order = Order("Maria", 75.50)print(big(order))show(label(order))print(guest().customer)True-> MARIAGastIn Java sind Regeln Objekte mit Methoden, die sie zusammensetzen: Bedingungen verbindet man mit and, or und negate („nicht“), und Funktionen reiht man mit andThen hintereinander. Python hat solche Methoden nicht. Die neue Regel ist eine neue Funktion, die die alten aufruft: lambda o: big(o) and maria(o). Zwei Funktionen hintereinander setzt man auch mit einer Funktion zusammen:
price_with_vat = lambda price: price * 1.2rounded = lambda price: round(price, 2)then = lambda f, g: lambda x: g(f(x))
final_price = then(price_with_vat, rounded)print(final_price(10.555))12.67then nimmt zwei Funktionen und gibt eine dritte zurück: Sie ruft die erste auf und übergibt deren Ergebnis an die zweite.
Genauer, für Neugierige
- Eine Aktion gibt trotzdem etwas zurück:
None, weil das das Ergebnis vonprintist.print(show("Kaffee"))gibt „-> Kaffee“ aus und dannNone. - Das Modul
operatorhat fertige Funktionen für die häufigsten Umwandlungen.attrgetter("total")ist dasselbe wielambda o: o.total.
Probier es aus
Zusammengesetzte Bedingungen
Was gibt das Programm aus?
big = lambda o: o.total >= 50maria = lambda o: o.customer == "Maria"big_and_maria = lambda o: big(o) and maria(o)big_or_maria = lambda o: big(o) or maria(o)not_big = lambda o: not big(o)
small = Order("Maria", 30)large = Order("Tom", 80)
print(big_and_maria(small))print(big_or_maria(small))print(not_big(large))big_and_maria False, und big_or_maria ergibt True, weil sie von Maria ist. Toms Bestellung hat 80 €, sie ist groß, und not dreht die Antwort um: False.Sieh es dir an
Funktionen, die Funktionen annehmen
from collections import defaultdict
cart = ["Brot", "", "Milch", " ", "Kaffee"]cart = list(filter(lambda item: item.strip() != "", cart))for item in cart: print(item)print(max(cart, key=lambda item: len(item)))
by_city = defaultdict(lambda: [])by_city["Berlin"].append("Maria")by_city["Hamburg"].append("Tom")by_city["Berlin"].append("Laura")for city, names in sorted(by_city.items()): print(f"{city}: {names}")BrotMilchKaffeeKaffeeBerlin: ['Maria', 'Laura']Hamburg: ['Tom']filter(bedingung, liste)lässt nur die Elemente durch, für die die Bedingung wahr ist. Es liefert sie eins nach dem anderen, deshalb sammeltlist(...)sie in einer neuen Liste. Die alte ändert sich nicht: Hier zeigtcarteinfach auf die neue.max(liste, key=funktion)undminfinden das größte und das kleinste Element nach der Regel.keyist dasselbe wie beisort: Hier ist es die Länge, deshalb gewinnt „Kaffee“.defaultdict(funktion)ist ein Dictionary, das die Funktion aufruft, wenn ein Schlüssel fehlt, das Ergebnis unter den Schlüssel legt und es zurückgibt. So funktioniert das Gruppieren nach Stadt ohneif city not in by_city. Hier ist die Funktionlambda: [], sie kann aber auchlistselbst sein: Auch das gibt eine neue leere Liste zurück.- Python hat kein
forEach. Für eine Aktion mit jedem Element nimmst dufor.
Das Dictionary hält die Schlüssel in der Reihenfolge, in der sie hinzugefügt wurden. sorted(by_city.items()) sortiert sie, deshalb kommt Berlin vor Hamburg.
Die Idee
Eine Funktion über ihren Namen, ohne Klammern
lambda s: int(s). Dann übergib die Funktion selbst, über ihren Namen und ohne Klammern:names = ["maria", "tom"]join = ", ".joinupper = str.upperparse = intnew_list = list
print(join(names))print(upper("laura"))print(parse("42") + 1)items = new_list()items.append("Kaffee")print(items)maria, tomLAURA43['Kaffee']Java hat dafür eine eigene Schreibweise, die Methodenreferenz mit ::. In Python reicht der Name. Die Arten sind dieselben vier:
- eine Methode eines bestimmten Objekts:
", ".joinistlambda items: ", ".join(items); - eine Methode des Parameters:
str.upperistlambda s: s.upper(); - eine Funktion:
intistlambda s: int(s); - eine Klasse:
lististlambda: list(). Eine Klasse ruft man wie eine Funktion auf, und sie gibt ein neues Objekt zurück.
Tut das Lambda etwas mehr, zum Beispiel lambda s: s.strip().upper(), lass es als Lambda stehen.
Sieh es dir an
Sortieren nach mehreren Feldern
key kann ein Tupel zurückgeben: mehrere Werte in Klammern. Tupel werden von links nach rechts verglichen: zuerst die ersten Werte, und sind sie gleich, die nächsten.
Die Schüler werden nach Note sortiert, von der höchsten an, und bei gleicher Note nach Name:
students = [ Student("Jonas", 5.50), Student("Anna", 6.00), Student("Peter", 5.50), Student("Finn", 4.75),]students.sort(key=lambda s: (-s.grade, s.name))Anna 6.0Jonas 5.5Peter 5.5Finn 4.75-s.grade dreht nur die Richtung der Note um: Eine höhere Note ergibt eine kleinere Zahl und kommt nach vorn. Der Name bleibt alphabetisch. Kein einziges if.
Genauer, für Neugierige
- Text kann man nicht mit einem Minus umdrehen. Aber das Sortieren in Python ist stabil: Gleiche Elemente bleiben in der Reihenfolge, in der sie waren. Deshalb geht „nach Note umgekehrt, dann nach Name“ auch mit zwei Sortierungen. Zuerst nach der zweiten Regel, dann nach der ersten:
students.sort(key=lambda s: s.name)students.sort(key=lambda s: s.grade, reverse=True)Anna 6.0Jonas 5.5Peter 5.5Finn 4.75- Eine Vergleichsfunktion wie
comparein Java (eine negative Zahl, 0 oder eine positive Zahl) wird mitcmp_to_keyaus dem Modulfunctoolszu einemkey:
from functools import cmp_to_key
def compare(a, b): return (a.grade > b.grade) - (a.grade < b.grade)
students.sort(key=cmp_to_key(compare))Finn 4.75Jonas 5.5Peter 5.5Anna 6.0Probier es aus
Was kehrt reverse=True um?
(s.grade, s.name), ohne Minus, und reverse=True dreht die Richtung um.Was gibt das Programm aus?
students = [ Student("Jonas", 5.50), Student("Anna", 6.00), Student("Peter", 5.50), Student("Finn", 4.75),]
students.sort(key=lambda s: (s.grade, s.name), reverse=True)for s in students: print(s.name, s.grade)reverse=True kehrt die Sortierung nach dem ganzen Schlüssel um, und der Schlüssel ist „nach Note, dann nach Name“. Also werden beide umgekehrt: Die Noten gehen von der höchsten an, und Peter kommt vor Jonas. Willst du nur die Noten umgekehrt, steht das Minus vor der Note, wie auf der Karte davor.Probier es aus
Finde den Fehler
Wähle die Zeile mit dem Fehler.
Die Funktion add weist total etwas zu:
Traceback (most recent call last): ... total += p ^^^^^UnboundLocalError: cannot access local variable 'total' where it is not associated with a valueSobald eine Funktion einem Namen etwas zuweist, entscheidet Python, dass das eine lokale Variable der Funktion ist. total += p will zuerst das lokale total lesen, und das hat noch keinen Wert. Das äußere total wird gar nicht angefasst.
Eine Variable von außen zu lesen ist erlaubt. Ihr etwas zuzuweisen nicht, außer die Funktion sagt global total (oder nonlocal total, wenn die Variable in einer anderen Funktion ist). Das ist selten eine gute Idee. Für eine Summe nimm eine normale Schleife: for p in prices: total += p auf der obersten Ebene. In der nächsten Lektion machst du es auch in einer Zeile.
In einem Lambda geht += überhaupt nicht: Sein Rumpf ist ein Ausdruck, und eine Zuweisung ist keiner. lambda p: total += p ist ein SyntaxError.
Genauer, für Neugierige
Ein Lambda sieht die Variable selbst, keine Kopie. Ändert sich die Variable nach dem Lambda, sieht der nächste Aufruf den neuen Wert:
rate = 0.2discount = lambda price: price * (1 - rate)print(discount(100))
rate = 0.5print(discount(100))80.050.0In Java ist das genau wegen solcher Überraschungen verboten. Muss sich die Regel einen Wert merken, übergib ihn als Parameter.
Übung
Regeln als Werte
1. Sortieren nach mehreren Feldern. Eine Klasse Product mit Name, Kategorie und Preis. Sortiere die Produkte nach Kategorie, innerhalb einer Kategorie vom teuersten an und bei gleichem Preis nach Name, mit einem sort und einem Tupel in key. Die Produkte sind: Maus, Zubehör, 25 €; Laptop, Computer, 1299 €; Tastatur, Zubehör, 49,90 €; Tablet, Computer, 399 €; Kopfhörer, Zubehör, 49,90 €.
Computer | Laptop | 1299.00 €Computer | Tablet | 399.00 €Zubehör | Kopfhörer | 49.90 €Zubehör | Tastatur | 49.90 €Zubehör | Maus | 25.00 €2. Bestellungen filtern. Gib Order eine Stadt. Schreib eine Funktion select(orders, rule), die eine neue Liste nur mit den Bestellungen zurückgibt, für die die Regel wahr ist. Mach zwei Regeln, hamburg und big (mindestens 50 €), und gib die Bestellungen aus, für die beide wahr sind, die außerhalb von Hamburg und die „in Hamburg oder mindestens 100 €“. Jede der drei Regeln ist ein Lambda, das hamburg und big aufruft. Die Bestellungen: Maria, Hamburg, 75,50 €; Tom, Dresden, 12,90 €; Laura, Hamburg, 40 €; Lukas, Berlin, 120 €.
Hamburg, ab 50 €: Maria, Hamburg: 75.50 €Außerhalb von Hamburg: Tom, Dresden: 12.90 € Lukas, Berlin: 120.00 €Kostenlose Lieferung (Hamburg oder ab 100 €): Maria, Hamburg: 75.50 € Laura, Hamburg: 40.00 € Lukas, Berlin: 120.00 €then(f, g), das eine Funktion zurückgibt: zuerst f, dann g. Verkette damit die Rabatte in zwei Reihenfolgen: zuerst den Studentenrabatt und zuerst den Freitagsrabatt. Probier es mit 120 € und mit 15 €. Spielt die Reihenfolge eine Rolle?120.00 € -> Student, dann Freitag: 88.00 €; Freitag, dann Student: 90.00 €15.00 € -> Student, dann Freitag: 0.00 €; Freitag, dann Student: 0.00 €Weitere Aufgaben
- Eine Liste aufräumen. Entferne aus einer Liste von E-Mail-Adressen die leeren und die ohne
@mit einem einzigenfilterund gib den Rest mitforaus. - Schüler nach Klasse. Gruppiere Schüler nach Klasse (
"10A","10B") in einemdefaultdict(list)und gib die Klassen der Reihe nach mitsortedaus. - Validatoren. Mach ein Dictionary mit drei Validatoren als Lambdas: nicht leerer Text, nur Ziffern (
str.isdigit), Länge bis 10. Probier jeden an ein paar Strings aus.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Interfaces als abstrakte Klassen · Listen: append, remove und sort · dict und set · Klassen und Daten in Klassen
- Geht weiter in: List Comprehensions und Generatoren · dataclass, match und Muster · Entwurfsmuster · Tests mit unittest
- Aufgaben zum Thema: Modernes Python