Verwendung von Python Lambda-Funktionen

Verwendung von Python Lambda-Funktionen

In Python und anderen Sprachen wie Java, C # und sogar C ++ wurden Lambda-Funktionen zu ihrer Syntax hinzugefügt, während Sprachen wie LISP oder die ML-Sprachfamilie Haskell, OCaml und F # Lambdas als Kernkonzept verwenden.

Python-Lambdas sind kleine, anonyme Funktionen, die einer restriktiveren, aber präziseren Syntax unterliegen als reguläre Python-Funktionen.

Am Ende dieses Artikels wissen Sie:

  • Wie Python Lambdas entstanden sind

  • Wie Lambdas mit regulären Funktionsobjekten verglichen werden

  • Wie schreibe ich Lambda-Funktionen

  • Welche Funktionen in der Python-Standardbibliothek nutzen Lambdas?

  • Wann sollten Python-Lambda-Funktionen verwendet oder vermieden werden?

Notes: Es werden einige Codebeispiele mitlambda angezeigt, die die Best Practices im Python-Stil offensichtlich ignorieren. Dies soll nur Lambda-Kalkül-Konzepte veranschaulichen oder die Fähigkeiten von Pythonlambda hervorheben.

Diese fragwürdigen Beispiele werden im Verlauf des Artikels mit besseren Ansätzen oder Alternativen verglichen.

Dieses Tutorial richtet sich hauptsächlich an fortgeschrittene bis erfahrene Python-Programmierer, ist jedoch für alle neugierigen Köpfe zugänglich, die sich für Programmierung und Lambda-Berechnung interessieren.

Alle in diesem Tutorial enthaltenen Beispiele wurden mit Python 3.7 getestet.

__ Take the Quiz: Testen Sie Ihr Wissen mit unserem interaktiven Quiz „Python Lambda-Funktionen“. Nach Abschluss erhalten Sie eine Punktzahl, mit der Sie Ihren Lernfortschritt im Laufe der Zeit verfolgen können:

Free Bonus:Click here to get access to a chapter from Python Tricks: The Book zeigt Ihnen die Best Practices von Python anhand einfacher Beispiele, die Sie sofort anwenden können, um schöneren + Pythonic-Code zu schreiben.

Lambda-Kalkül

Lambda-Ausdrücke in Python und anderen Programmiersprachen haben ihre Wurzeln in der Lambda-Rechnung, einem von Alonzo Church erfundenen Berechnungsmodell. Sie werden herausfinden, wann der Lambda-Kalkül eingeführt wurde und warum es sich um ein grundlegendes Konzept handelt, das im Python-Ökosystem landete.

Geschichte

Alonzo Church formalisiertelambda calculus, eine Sprache, die auf reiner Abstraktion basiert, in den 1930er Jahren. Lambda-Funktionen werden auch als Lambda-Abstraktionen bezeichnet, ein direkter Hinweis auf das Abstraktionsmodell der ursprünglichen Schöpfung der Alonzo-Kirche.

Der Lambda-Kalkül kann jede Berechnung codieren. Es istTuring complete, aber im Gegensatz zum Konzept vonTuring machine ist es rein und behält keinen Zustand.

Funktionale Sprachen haben ihren Ursprung in der mathematischen Logik und der Lambda-Rechnung, während imperative Programmiersprachen das von Alan Turing erfundene zustandsbasierte Berechnungsmodell umfassen. Die beiden Berechnungsmodelle Lambda-Kalkül undTuring machines können ineinander übersetzt werden. Diese Äquivalenz ist alsChurch-Turing hypothesis bekannt.

Funktionale Sprachen erben direkt die Lambda-Kalkül-Philosophie und verfolgen einen deklarativen Programmieransatz, bei dem Abstraktion, Datentransformation, Zusammensetzung und Reinheit im Vordergrund stehen (kein Zustand und keine Nebenwirkungen). Beispiele für funktionale Sprachen sindHaskell,Lisp oderErlang.

Im Gegensatz dazu führte die Turing-Maschine zu einer zwingenden Programmierung in Sprachen wieFortran,C oderPython.

Der imperative Stil besteht darin, mit Anweisungen zu programmieren und den Programmablauf Schritt für Schritt mit detaillierten Anweisungen zu steuern. Dieser Ansatz fördert die Mutation und erfordert die Verwaltung des Zustands.

Die Trennung in beiden Familien weist einige Nuancen auf, da einige funktionale Sprachen imperative Merkmale wieOCaml enthalten, während funktionale Merkmale die imperative Sprachfamilie insbesondere mit der Einführung von Lambda-Funktionen inJava durchdrungen haben. oder Python.

Python ist von Natur aus keine funktionale Sprache, hat jedoch schon früh einige funktionale Konzepte übernommen. Im Januar 1994 wurden der Sprachemap(),filter(),reduce() und der Operatorlambda hinzugefügt.

Erstes Beispiel

Hier sind einige Beispiele, die Ihnen Appetit auf Python-Code und funktionalen Stil machen sollen.

identity function, eine Funktion, die ihr Argument zurückgibt, wird mit einer Standard-Python-Funktionsdefinition unter Verwendung des Schlüsselwortsdef wie folgt ausgedrückt:

>>>

>>> def identity(x):
...     return x

identity() nimmt ein Argumentx und gibt es beim Aufruf zurück.

Wenn Sie dagegen eine Python-Lambda-Konstruktion verwenden, erhalten Sie Folgendes:

>>>

>>> lambda x: x

Im obigen Beispiel besteht der Ausdruck aus:

  • Das Schlüsselwort: lambda

  • Eine gebundene Variable: x

  • Ein Körper: x

Note: Im Kontext dieses Artikels ist einbound variable ein Argument für eine Lambda-Funktion.

Im Gegensatz dazu ist afree variable nicht gebunden und kann im Hauptteil des Ausdrucks referenziert werden. Eine freie Variable kann eine Konstante oder eine Variable sein, die im umschließenden Funktionsumfang definiert ist.

Sie können ein etwas ausführlicheres Beispiel schreiben, eine Funktion, die einem Argument1 wie folgt hinzufügt:

>>>

>>> lambda x: x + 1

Sie können die obige Funktion auf ein Argument anwenden, indem Sie die Funktion und ihr Argument in Klammern setzen:

>>>

>>> (lambda x: x + 1)(2)
3

Reduction ist eine Lambda-Kalkül-Strategie zur Berechnung des Wertes des Ausdrucks. Es besteht darin,x durch das Argument2 zu ersetzen:

(lambda x: x + 1)(2) = lambda 2: 2 + 1
                     = 2 + 1
                     = 3

Da eine Lambda-Funktion ein Ausdruck ist, kann sie benannt werden. Daher können Sie den vorherigen Code wie folgt schreiben:

>>>

>>> add_one = lambda x: x + 1
>>> add_one(2)
3

Die obige Lambda-Funktion entspricht dem Schreiben von:

def add_one(x):
    return x + 1

Diese Funktionen haben alle ein einziges Argument. Möglicherweise haben Sie bemerkt, dass die Argumente in der Definition der Lambdas keine Klammern enthalten. Funktionen mit mehreren Argumenten (Funktionen, die mehr als ein Argument annehmen) werden in Python-Lambdas ausgedrückt, indem Argumente aufgelistet und durch Komma (,) getrennt werden, ohne sie jedoch in Klammern zu setzen:

>>>

>>> full_name = lambda first, last: f'Full name: {first.title()} {last.title()}'
>>> full_name('guido', 'van rossum')
'Full name: Guido Van Rossum'

Diefull_name zugewiesene Lambda-Funktion akzeptiert zwei Argumente und gibt eine Zeichenfolge zurück, die die beiden Parameterfirst undlast interpoliert. Wie erwartet listet die Definition des Lambda die Argumente ohne Klammern auf, während der Aufruf der Funktion genau wie bei einer normalen Python-Funktion erfolgt, wobei die Argumente in Klammern stehen.

Anonyme Funktionen

Die folgenden Begriffe können je nach Typ und Kultur der Programmiersprache austauschbar verwendet werden:

  • Anonyme Funktionen

  • Lambda-Funktionen

  • Lambda-Ausdrücke

  • Lambda-Abstraktionen

  • Lambda-Form

  • Funktionsliterale

Für den Rest dieses Artikels nach diesem Abschnitt wird meistens der Begrifflambda function angezeigt.

Wörtlich genommen ist eine anonyme Funktion eine Funktion ohne Namen. In Python wird eine anonyme Funktion mit dem Schlüsselwortlambdaerstellt. Lockerer kann ihm ein Name zugewiesen werden oder nicht. Stellen Sie sich eine anonyme Funktion mit zwei Argumenten vor, die mitlambda definiert, aber nicht an eine Variable gebunden ist. Dem Lambda wird kein Name gegeben:

>>>

>>> lambda x, y: x + y

Die obige Funktion definiert einen Lambda-Ausdruck, der zwei Argumente akzeptiert und deren Summe zurückgibt.

Abgesehen davon, dass Sie das Feedback erhalten, dass Python mit diesem Formular vollkommen in Ordnung ist, führt es zu keiner praktischen Verwendung. Sie können die Funktion im Python-Interpreter aufrufen:

>>>

>>> _(1, 2)
3

Das obige Beispiel nutzt die Funktion nur für interaktive Interpreter, die über den Unterstrich (_) bereitgestellt wird. Weitere Informationen finden Sie im Hinweis unten.

Sie konnten keinen ähnlichen Code in ein Python-Modul schreiben. Betrachten Sie die_ im Interpreter als Nebeneffekt, den Sie ausgenutzt haben. In einem Python-Modul würden Sie dem Lambda einen Namen zuweisen oder das Lambda an eine Funktion übergeben. Sie werden diese beiden Ansätze später in diesem Artikel verwenden.

Note: Im interaktiven Interpreter ist der einzelne Unterstrich (_) an den zuletzt ausgewerteten Ausdruck gebunden.

Im obigen Beispiel zeigt_ auf die Lambda-Funktion. Weitere Informationen zur Verwendung dieses Sonderzeichens in Python finden Sie unterThe Meaning of Underscores in Python.

Ein anderes Muster, das in anderen Sprachen wie JavaScript verwendet wird, besteht darin, sofort eine Python-Lambda-Funktion auszuführen. Dies ist alsImmediately Invoked Function Expression (IIFE, sprechen Sie "iffy" aus) bekannt. Hier ist ein Beispiel:

>>>

>>> (lambda x, y: x + y)(2, 3)
5

Die obige Lambda-Funktion wird definiert und dann sofort mit zwei Argumenten (2 und3) aufgerufen. Es gibt den Wert5 zurück, der die Summe der Argumente ist.

Einige Beispiele in diesem Lernprogramm verwenden dieses Format, um den anonymen Aspekt einer Lambda-Funktion hervorzuheben und zu vermeiden, sich in Python auflambda zu konzentrieren, um eine Funktion kürzer zu definieren.

Python empfiehlt nicht, sofort aufgerufene Lambda-Ausdrücke zu verwenden. Es resultiert einfach daraus, dass ein Lambda-Ausdruck im Gegensatz zum Körper einer normalen Funktion aufgerufen werden kann.

Lambda-Funktionen werden häufig mithigher-order functions verwendet, die eine oder mehrere Funktionen als Argumente verwenden oder eine oder mehrere Funktionen zurückgeben.

Eine Lambda-Funktion kann eine Funktion höherer Ordnung sein, indem eine Funktion (normal oder Lambda) als Argument wie im folgenden erfundenen Beispiel verwendet wird:

>>>

>>> high_ord_func = lambda x, func: x + func(x)
>>> high_ord_func(2, lambda x: x * x)
6
>>> high_ord_func(2, lambda x: x + 3)
7

Python macht Funktionen höherer Ordnung als integrierte Funktionen oder in der Standardbibliothek verfügbar. Beispiele sindmap(),filter(),functools.reduce() sowie Schlüsselfunktionen wiesort(),sorted(),min() undmax() . Sie verwenden Lambda-Funktionen zusammen mit Python-Funktionen höherer Ordnung inAppropriate Uses of Lambda Expressions.

Python Lambda und reguläre Funktionen

Dieses Zitat ausPython Design and History FAQ scheint den Ton für die allgemeine Erwartung hinsichtlich der Verwendung von Lambda-Funktionen in Python festzulegen:

Im Gegensatz zu Lambda-Formularen in anderen Sprachen, in denen sie Funktionen hinzufügen, sind Python-Lambdas nur eine Kurzschreibweise, wenn Sie zu faul sind, um eine Funktion zu definieren. (Source)

Lassen Sie sich jedoch nicht von dieser Anweisung davon abhalten, Pythonslambdazu verwenden. Auf den ersten Blick können Sie akzeptieren, dass eine Lambda-Funktion eine Funktion ist, bei der einigesyntactic sugarden Code verkürzen, um eine Funktion zu definieren oder aufzurufen. In den folgenden Abschnitten werden die Gemeinsamkeiten und subtilen Unterschiede zwischen normalen Python-Funktionen und Lambda-Funktionen hervorgehoben.

Funktionen

An dieser Stelle fragen Sie sich vielleicht, was eine an eine Variable gebundene Lambda-Funktion grundlegend von einer regulären Funktion mit einer einzelnenreturn-Linie unterscheidet: unter der Oberfläche fast nichts. Lassen Sie uns überprüfen, wie Python eine Funktion sieht, die mit einer einzelnen return-Anweisung erstellt wurde, im Vergleich zu einer Funktion, die als Ausdruck (lambda) erstellt wurde.

Das Moduldis stellt Funktionen zur Analyse des vom Python-Compiler generierten Python-Bytecodes bereit:

>>>

>>> import dis
>>> add = lambda x, y: x + y
>>> type(add)

>>> dis.dis(add)
  1           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> add
 at 0x7f30c6ce9ea0>

Sie können sehen, dassdis() eine lesbare Version des Python-Bytecodes verfügbar macht, mit der die Anweisungen auf niedriger Ebene überprüft werden können, die der Python-Interpreter während der Ausführung des Programms verwendet.

Sehen Sie es jetzt mit einem regulären Funktionsobjekt:

>>>

>>> import dis
>>> def add(x, y): return x + y
>>> type(add)

>>> dis.dis(add)
  1           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> add

Der von Python interpretierte Bytecode ist für beide Funktionen gleich. Möglicherweise stellen Sie jedoch fest, dass die Benennung unterschiedlich ist: Der Funktionsname lautetadd für eine mitdef definierte Funktion, während die Python-Lambda-Funktion alslambda angesehen wird.

Zurück verfolgen

Sie haben im vorherigen Abschnitt gesehen, dass Python im Kontext der Lambda-Funktion nicht den Namen der Funktion angegeben hat, sondern nur<lambda>. Dies kann eine Einschränkung sein, die zu berücksichtigen ist, wenn eine Ausnahme auftritt, und ein Traceback zeigt nur<lambda> an:

>>>

>>> div_zero = lambda x: x / 0
>>> div_zero(2)
Traceback (most recent call last):
    File "", line 1, in 
    File "", line 1, in 
ZeroDivisionError: division by zero

Der Traceback einer Ausnahme, die ausgelöst wird, während eine Lambda-Funktion ausgeführt wird, identifiziert nur die Funktion, die die Ausnahme verursacht, als<lambda>.

Hier ist dieselbe Ausnahme, die von einer normalen Funktion ausgelöst wird:

>>>

>>> def div_zero(x): return x / 0
>>> div_zero(2)
Traceback (most recent call last):
    File "", line 1, in 
    File "", line 1, in div_zero
ZeroDivisionError: division by zero

Die normale Funktion verursacht einen ähnlichen Fehler, führt jedoch zu einem genaueren Traceback, da der Funktionsnamediv_zero angegeben wird.

Syntax

Wie Sie in den vorherigen Abschnitten gesehen haben, weist eine Lambda-Form syntaktische Unterschiede zu einer normalen Funktion auf. Insbesondere weist eine Lambda-Funktion die folgenden Eigenschaften auf:

  • Es kann nur Ausdrücke und keine Anweisungen in seinem Hauptteil enthalten.

  • Es wird als einzelne Ausführungszeile geschrieben.

  • Typanmerkungen werden nicht unterstützt.

  • Es kann sofort aufgerufen werden (IIFE).

Keine Aussagen

Eine Lambda-Funktion kann keine Anweisungen enthalten. In einer Lambda-Funktion lösen Anweisungen wiereturn,pass,assert oderraise eineSyntaxError-Ausnahme aus. Hier ist ein Beispiel für das Hinzufügen vonassert zum Körper eines Lambda:

>>>

>>> (lambda x: assert x == 2)(2)
  File "", line 1
    (lambda x: assert x == 2)(2)
                    ^
SyntaxError: invalid syntax

Dieses erfundene Beispiel wollteassert, dass der Parameterx einen Wert von2 hatte. Der Interpreter identifiziert jedoch einSyntaxError, während er den Code analysiert, der die Anweisungassert im Hauptteil deslambda enthält.

Einzelner Ausdruck

Im Gegensatz zu einer normalen Funktion ist eine Python-Lambda-Funktion ein einzelner Ausdruck. Obwohl Sie im Hauptteil vonlambda den Ausdruck mithilfe von Klammern oder einer mehrzeiligen Zeichenfolge auf mehrere Zeilen verteilen können, bleibt er ein einzelner Ausdruck:

>>>

>>> (lambda x:
... (x % 2 and 'odd' or 'even'))(3)
'odd'

Das obige Beispiel gibt die Zeichenfolge'odd' zurück, wenn das Lambda-Argument ungerade ist, und'even', wenn das Argument gerade ist. Es erstreckt sich über zwei Zeilen, da es in Klammern enthalten ist, bleibt jedoch ein einzelner Ausdruck.

Geben Sie Anmerkungen ein

Wenn Sie mit der Übernahme von Typhinweisen begonnen haben, die jetzt in Python verfügbar sind, haben Sie einen weiteren guten Grund, normale Funktionen Python-Lambda-Funktionen vorzuziehen. Schauen Sie sichPython Type Checking (Guide) an, um mehr über Python-Typhinweise und Typprüfung zu erfahren. In einer Lambda-Funktion gibt es kein Äquivalent für Folgendes:

def full_name(first: str, last: str) -> str:
    return f'{first.title()} {last.title()}'

Jeder Typfehler mitfull_name() kann von Tools wiemypy oderpyre abgefangen werden, während zur Laufzeit einSyntaxError mit der entsprechenden Lambda-Funktion ausgelöst wird:

>>>

>>> lambda first: str, last: str: first.title() + " " + last.title() -> str
  File "", line 1
    lambda first: str, last: str: first.title() + " " + last.title() -> str

SyntaxError: invalid syntax

Wie beim Versuch, eine Anweisung in ein Lambda aufzunehmen, führt das Hinzufügen von Typanmerkungen zur Laufzeit sofort zu einemSyntaxError.

IIFE

Sie haben bereits mehrere Beispiele fürimmediately invoked function execution gesehen:

>>>

>>> (lambda x: x * x)(3)
9

Außerhalb des Python-Interpreters wird diese Funktion in der Praxis wahrscheinlich nicht verwendet. Dies ist eine direkte Folge davon, dass eine Lambda-Funktion wie definiert aufgerufen werden kann. Auf diese Weise können Sie beispielsweise die Definition eines Python-Lambda-Ausdrucks an eine Funktion höherer Ordnung wiemap(),filter() oderfunctools.reduce() oder an eine Schlüsselfunktion übergeben.

Argumente

Wie ein normales Funktionsobjekt, das mitdef definiert ist, unterstützen Python-Lambda-Ausdrücke alle verschiedenen Arten der Übergabe von Argumenten. Das beinhaltet:

  • Positionsargumente

  • Benannte Argumente (manchmal auch als Schlüsselwortargumente bezeichnet)

  • Variable Liste von Argumenten (oft alsvarargs bezeichnet)

  • Variable Liste der Schlüsselwortargumente

  • Nur-Schlüsselwort-Argumente

Die folgenden Beispiele veranschaulichen Optionen, die Ihnen zur Übergabe von Argumenten an Lambda-Ausdrücke offen stehen:

>>>

>>> (lambda x, y, z: x + y + z)(1, 2, 3)
6
>>> (lambda x, y, z=3: x + y + z)(1, 2)
6
>>> (lambda x, y, z=3: x + y + z)(1, y=2)
6
>>> (lambda *args: sum(args))(1,2,3)
6
>>> (lambda **kwargs: sum(kwargs.values()))(one=1, two=2, three=3)
6
>>> (lambda x, *, y=0, z=0: x + y + z)(1, y=2, z=3)
6

Dekorateure

In Python ist eindecorator die Implementierung eines Musters, mit dem einer Funktion oder Klasse ein Verhalten hinzugefügt werden kann. Sie wird normalerweise mit der Syntax@decoratorvor einer Funktion ausgedrückt. Hier ist ein erfundenes Beispiel:

def some_decorator(f):
    def wraps(*args):
        print(f"Calling function '{f.__name__}'")
        return f(args)
    return wraps

@some_decorator
def decorated_function(x):
    print(f"With argument '{x}'")

Im obigen Beispiel istsome_decorator() eine Funktion, diedecorated_function() ein Verhalten hinzufügt, sodass das Aufrufen vondecorated_function("Python") zu der folgenden Ausgabe führt:

Calling function 'decorated_function'
With argument 'Python'

decorated_function() druckt nurWith argument 'Python', aber der Dekorateur fügt ein zusätzliches Verhalten hinzu, das auchCalling function 'decorated_function' druckt.

Ein Dekorateur kann auf ein Lambda angewendet werden. Obwohl es nicht möglich ist, ein Lambda mit der Syntax@decoratorzu dekorieren, ist ein Dekorator nur eine Funktion, sodass er die Lambda-Funktion aufrufen kann:

 1 # Defining a decorator
 2 def trace(f):
 3     def wrap(*args, **kwargs):
 4         print(f"[TRACE] func: {f.__name__}, args: {args}, kwargs: {kwargs}")
 5         return f(*args, **kwargs)
 6
 7     return wrap
 8
 9 # Applying decorator to a function
10 @trace
11 def add_two(x):
12     return x + 2
13
14 # Calling the decorated function
15 add_two(3)
16
17 # Applying decorator to a lambda
18 print((trace(lambda x: x ** 2))(3))

add_two(), dekoriert mit@trace in Zeile 11, wird mit dem Argument3 in Zeile 15 aufgerufen. Im Gegensatz dazu ist in Zeile 18 sofort eine Lambda-Funktion beteiligt und in einen Aufruf vontrace(), dem Dekorateur, eingebettet. Wenn Sie den obigen Code ausführen, erhalten Sie Folgendes:

[TRACE] func: add_two, args: (3,), kwargs: {}
[TRACE] func: , args: (3,), kwargs: {}
9

Sehen Sie, wie, wie Sie bereits gesehen haben, der Name der Lambda-Funktion als<lambda> angezeigt wird, währendadd_two für die normale Funktion eindeutig identifiziert wird.

Das Dekorieren der Lambda-Funktion auf diese Weise kann für Debugging-Zwecke nützlich sein, möglicherweise um das Verhalten einer Lambda-Funktion zu debuggen, die im Kontext einer Funktion höherer Ordnung oder einer Schlüsselfunktion verwendet wird. Sehen wir uns ein Beispiel mitmap() an:

list(map(trace(lambda x: x*2), range(3)))

Das erste Argument vonmap() ist ein Lambda, das sein Argument mit2 multipliziert. Dieses Lambda ist mittrace() dekoriert. Bei der Ausführung gibt das obige Beispiel Folgendes aus:

[TRACE] Calling  with args (0,) and kwargs {}
[TRACE] Calling  with args (1,) and kwargs {}
[TRACE] Calling  with args (2,) and kwargs {}
[0, 2, 4]

Das Ergebnis[0, 2, 4] ist eine Liste, die durch Multiplizieren jedes Elements vonrange(3) erhalten wird. Betrachten Sie vorerstrange(3) als äquivalent zur Liste[0, 1, 2].

InMap sind Siemap() ausführlicher ausgesetzt.

Ein Lambda kann auch ein Dekorateur sein, wird aber nicht empfohlen. Wenn Sie dies benötigen, konsultieren SiePEP 8, Programming Recommendations.

Weitere Informationen zu Python-Dekoratoren finden Sie unterPrimer on Python Decorators.

Schließung

Aclosure ist eine Funktion, bei der jede freie Variable, mit Ausnahme der Parameter, die in dieser Funktion verwendet wird, an einen bestimmten Wert gebunden ist, der im umschließenden Bereich dieser Funktion definiert ist. In der Tat definieren Schließungen die Umgebung, in der sie ausgeführt werden, und können daher von überall aufgerufen werden.

Die Konzepte von Lambdas und Verschlüssen sind nicht unbedingt miteinander verbunden, obwohl Lambda-Funktionen Verschlüsse sein können, genauso wie normale Funktionen auch Verschlüsse sein können. Einige Sprachen haben spezielle Konstrukte für Closure oder Lambda (z. B. Groovy mit einem anonymen Codeblock als Closure-Objekt) oder einen Lambda-Ausdruck (z. B. Java Lambda-Ausdruck mit eingeschränkter Option für Closure).

Hier ist ein Verschluss, der mit einer normalen Python-Funktion erstellt wurde:

 1 def outer_func(x):
 2     y = 4
 3     def inner_func(z):
 4         print(f"x = {x}, y = {y}, z = {z}")
 5         return x + y + z
 6     return inner_func
 7
 8 for i in range(3):
 9     closure = outer_func(i)
10     print(f"closure({i+5}) = {closure(i+5)}")

outer_func() gibtinner_func() zurück, eine verschachtelte Funktion, die die Summe von drei Argumenten berechnet:

  • x wird als Argument anouter_func() übergeben.

  • y ist eine lokale Variable fürouter_func().

  • z ist ein Argument, das aninner_func() übergeben wird.

Um das Verhalten vonouter_func() undinner_func() zu testen, wirdouter_func() dreimal in einerfor-Schleife aufgerufen, die Folgendes ausgibt:

x = 0, y = 4, z = 5
closure(5) = 9
x = 1, y = 4, z = 6
closure(6) = 11
x = 2, y = 4, z = 7
closure(7) = 13

In Zeile 9 des Codes istinner_func(), das durch den Aufruf vonouter_func() zurückgegeben wird, an den Namenclosure gebunden. In Zeile 5 erfasstinner_func()x undy, da es Zugriff auf seine Einbettungsumgebung hat, sodass es beim Aufrufen des Abschlusses in der Lage ist, die beiden freien Variablenx undy.

In ähnlicher Weise kann einlambda auch ein Abschluss sein. Hier ist das gleiche Beispiel mit einer Python-Lambda-Funktion:

 1 def outer_func(x):
 2     y = 4
 3     return lambda z: x + y + z
 4
 5 for i in range(3):
 6     closure = outer_func(i)
 7     print(f"closure({i+5}) = {closure(i+5)}")

Wenn Sie den obigen Code ausführen, erhalten Sie die folgende Ausgabe:

closure(5) = 9
closure(6) = 11
closure(7) = 13

In Zeile 6 gibtouter_func() ein Lambda zurück und weist es der Variablenclosure zu. In Zeile 3 verweist der Körper der Lambda-Funktion aufx undy. Die Variabley ist zur Definitionszeit verfügbar, währendx zur Laufzeit definiert wird, wennouter_func() aufgerufen wird.

In dieser Situation verhalten sich sowohl die normale Funktion als auch das Lambda ähnlich. Im nächsten Abschnitt sehen Sie eine Situation, in der das Verhalten eines Lambda aufgrund seiner Auswertungszeit (Definitionszeit vs. Laufzeit) irreführend sein kann.

Auswertungszeit

In einigen Situationen mitloops kann das Verhalten einer Python-Lambda-Funktion als Abschluss möglicherweise nicht intuitiv sein. Es erfordert Verständnis, wann freie Variablen im Kontext eines Lambda gebunden werden. Die folgenden Beispiele zeigen den Unterschied zwischen der Verwendung einer regulären Funktion und der Verwendung eines Python-Lambda.

Testen Sie das Szenario zuerst mit einer regulären Funktion:

>>>

 1 >>> def wrap(n):
 2 ...     def f():
 3 ...         print(n)
 4 ...     return f
 5 ...
 6 >>> numbers = 'one', 'two', 'three'
 7 >>> funcs = []
 8 >>> for n in numbers:
 9 ...     funcs.append(wrap(n))
10 ...
11 >>> for f in funcs:
12 ...     f()
13 ...
14 one
15 two
16 three

In einer normalen Funktion wirdn zum Definitionszeitpunkt in Zeile 9 ausgewertet, wenn die Funktion zur Liste hinzugefügt wird:funcs.append(wrap(n)).

Beobachten Sie nun bei der Implementierung derselben Logik mit einer Lambda-Funktion das unerwartete Verhalten:

>>>

 1 >>> numbers = 'one', 'two', 'three'
 2 >>> funcs = []
 3 >>> for n in numbers:
 4 ...     funcs.append(lambda: print(n))
 5 ...
 6 >>> for f in funcs:
 7 ...     f()
 8 ...
 9 three
10 three
11 three

Das unerwartete Ergebnis tritt auf, weil die freie Variablen, wie implementiert, zur Ausführungszeit des Lambda-Ausdrucks gebunden ist. Die Python-Lambda-Funktion in Zeile 4 ist ein Abschluss, dern erfasst, eine freie Variable, die zur Laufzeit gebunden ist. Während der Laufzeit beim Aufrufen der Funktionf in Zeile 7 beträgt der Wert vonnthree.

Um dieses Problem zu beheben, können Sie die freie Variable zur Definitionszeit wie folgt zuweisen:

>>>

 1 >>> numbers = 'one', 'two', 'three'
 2 >>> funcs = []
 3 >>> for n in numbers:
 4 ...     funcs.append(lambda n=n: print(n))
 5 ...
 6 >>> for f in funcs:
 7 ...     f()
 8 ...
 9 one
10 two
11 three

Eine Python-Lambda-Funktion verhält sich in Bezug auf Argumente wie eine normale Funktion. Daher kann ein Lambda-Parameter mit einem Standardwert initialisiert werden: Der Parametern verwendet das äußeren als Standardwert. Die Python-Lambda-Funktion könnte alslambda x=n: print(x) geschrieben worden sein und das gleiche Ergebnis haben.

Die Python-Lambda-Funktion wird in Zeile 7 ohne Argument aufgerufen und verwendet den zur Definitionszeit festgelegten Standardwertn.

Lambdas testen

Python-Lambdas können ähnlich wie reguläre Funktionen getestet werden. Es ist möglich, sowohlunittest als auchdoctest zu verwenden.

unittest

Das Modulunittest behandelt Python-Lambda-Funktionen ähnlich wie reguläre Funktionen:

import unittest

addtwo = lambda x: x + 2

class LambdaTest(unittest.TestCase):
    def test_add_two(self):
        self.assertEqual(addtwo(2), 4)

    def test_add_two_point_two(self):
        self.assertEqual(addtwo(2.2), 4.2)

    def test_add_three(self):
        # Should fail
        self.assertEqual(addtwo(3), 6)

if __name__ == '__main__':
    unittest.main(verbosity=2)

LambdaTest definiert einen Testfall mit drei Testmethoden, von denen jede ein Testszenario füraddtwo() ausführt, die als Lambda-Funktion implementiert sind. Die Ausführung der Python-Dateilambda_unittest.py, dieLambdaTest enthält, führt zu folgenden Ergebnissen:

$ python lambda_unittest.py
test_add_three (__main__.LambdaTest) ... FAIL
test_add_two (__main__.LambdaTest) ... ok
test_add_two_point_two (__main__.LambdaTest) ... ok

======================================================================
FAIL: test_add_three (__main__.LambdaTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "lambda_unittest.py", line 18, in test_add_three
    self.assertEqual(addtwo(3), 6)
AssertionError: 5 != 6

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

Wie erwartet haben wir zwei erfolgreiche Testfälle und einen Fehler fürtest_add_three: Das Ergebnis ist5, aber das erwartete Ergebnis war6. Dieser Fehler ist auf einen absichtlichen Fehler im Testfall zurückzuführen. Wenn Sie das erwartete Ergebnis von6 auf5 ändern, werden alle Tests fürLambdaTest erfüllt.

doctest

Das Moduldoctest extrahiert interaktiven Python-Code ausdocstring, um Tests auszuführen. Obwohl die Syntax der Python-Lambda-Funktionen kein typischesdocstring unterstützt, ist es möglich, dem__doc__-Element eines benannten Lambda eine Zeichenfolge zuzuweisen:

addtwo = lambda x: x + 2
addtwo.__doc__ = """Add 2 to a number.
    >>> addtwo(2)
    4
    >>> addtwo(2.2)
    4.2
    >>> addtwo(3) # Should fail
    6
    """

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)

Diedoctest im Dokumentkommentar von Lambdaaddtwo() beschreiben dieselben Testfälle wie im vorherigen Abschnitt.

Wenn Sie die Tests überdoctest.testmod() ausführen, erhalten Sie Folgendes:

$ python lambda_doctest.py
Trying:
    addtwo(2)
Expecting:
    4
ok
Trying:
    addtwo(2.2)
Expecting:
    4.2
ok
Trying:
    addtwo(3) # Should fail
Expecting:
    6
**********************************************************************
File "lambda_doctest.py", line 16, in __main__.addtwo
Failed example:
    addtwo(3) # Should fail
Expected:
    6
Got:
    5
1 items had no tests:
    __main__
**********************************************************************
1 items had failures:
   1 of   3 in __main__.addtwo
3 tests in 2 items.
2 passed and 1 failed.
***Test Failed*** 1 failures.

Die fehlgeschlagenen Testergebnisse resultieren aus demselben Fehler, der in der Ausführung der Komponententests im vorherigen Abschnitt erläutert wurde.

Sie können einem Python-Lambdadocstring hinzufügen, indem Sie__doc__ zuweisen, um eine Lambda-Funktion zu dokumentieren. Obwohl dies möglich ist, berücksichtigt die Python-Syntaxdocstring für normale Funktionen besser als Lambda-Funktionen.

Eine umfassende Übersicht über Unit-Tests in Python finden Sie unterGetting Started With Testing in Python.

Missbrauch von Lambda-Ausdrücken

Einige Beispiele in diesem Artikel würden als Missbrauch gelten, wenn sie im Kontext von professionellem Python-Code geschrieben würden.

Wenn Sie versuchen, etwas zu überwinden, das ein Lambda-Ausdruck nicht unterstützt, ist dies wahrscheinlich ein Zeichen dafür, dass eine normale Funktion besser geeignet wäre. Dasdocstring für einen Lambda-Ausdruck im vorherigen Abschnitt ist ein gutes Beispiel. Der Versuch, die Tatsache zu überwinden, dass eine Python-Lambda-Funktion keine Anweisungen unterstützt, ist eine weitere rote Fahne.

In den nächsten Abschnitten werden einige Beispiele für Lambda-Verwendungen aufgeführt, die vermieden werden sollten. Diese Beispiele können Situationen sein, in denen der Code im Kontext von Python Lambda das folgende Muster aufweist:

  • Es folgt nicht dem Python-Styleguide (PEP 8).

  • Es ist umständlich und schwer zu lesen.

  • Es ist unnötig clever auf Kosten der schwierigen Lesbarkeit.

Eine Ausnahme auslösen

Der Versuch, eine Ausnahme in einem Python-Lambda auszulösen, sollte Sie zweimal überlegen lassen. Es gibt einige clevere Möglichkeiten, dies zu tun, aber selbst so etwas ist besser zu vermeiden:

>>>

>>> def throw(ex): raise ex
>>> (lambda: throw(Exception('Something bad happened')))()
Traceback (most recent call last):
    File "", line 1, in 
    File "", line 1, in 
    File "", line 1, in throw
Exception: Something bad happened

Da eine Anweisung in einem Python-Lambda-Body syntaktisch nicht korrekt ist, besteht die Problemumgehung im obigen Beispiel darin, den Anweisungsaufruf mit einer dedizierten Funktionthrow() zu abstrahieren. Die Verwendung dieser Art von Problemumgehung sollte vermieden werden. Wenn Sie auf diese Art von Code stoßen, sollten Sie in Betracht ziehen, den Code umzugestalten, um eine reguläre Funktion zu verwenden.

Kryptischer Stil

Wie in allen Programmiersprachen finden Sie Python-Code, der aufgrund des verwendeten Stils schwer zu lesen sein kann. Lambda-Funktionen können aufgrund ihrer Prägnanz dazu beitragen, schwer lesbaren Code zu schreiben.

Das folgende Lambda-Beispiel enthält mehrere schlechte Stiloptionen:

>>>

>>> (lambda _: list(map(lambda _: _ // 2, _)))([1,2,3,4,5,6,7,8,9,10])
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

Der Unterstrich (_) bezieht sich auf eine Variable, auf die Sie nicht explizit verweisen müssen. In diesem Beispiel beziehen sich drei_auf verschiedene Variablen. Ein erstes Upgrade auf diesen Lambda-Code könnte darin bestehen, die Variablen zu benennen:

>>>

>>> (lambda some_list: list(map(lambda n: n // 2,
                                some_list)))([1,2,3,4,5,6,7,8,9,10])
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

Zugegeben, es ist immer noch schwer zu lesen. Wenn Sie immer noch einlambda nutzen, würde eine reguläre Funktion diesen Code besser lesbar machen und die Logik auf einige Zeilen und Funktionsaufrufe verteilen:

>>>

>>> def div_items(some_list):
      div_by_two = lambda n: n // 2
      return map(div_by_two, some_list)
>>> list(div_items([1,2,3,4,5,6,7,8,9,10])))
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

Dies ist immer noch nicht optimal, zeigt Ihnen jedoch einen möglichen Pfad, um Code und insbesondere Python-Lambda-Funktionen besser lesbar zu machen. InAlternatives to Lambdas lernen Sie,map() undlambda durch Listenverständnisse oder Generatorausdrücke zu ersetzen. Dadurch wird die Lesbarkeit des Codes drastisch verbessert.

Python-Klassen

Sie können, sollten aber keine Klassenmethoden als Python-Lambda-Funktionen schreiben. Das folgende Beispiel ist vollkommen legaler Python-Code, zeigt jedoch unkonventionellen Python-Code, der auflambda basiert. Anstatt beispielsweise__str__ als reguläre Funktion zu implementieren, wirdlambda verwendet. In ähnlicher Weise sindbrand undyearproperties, die anstelle regulärer Funktionen oder Dekoratoren auch mit Lambda-Funktionen implementiert sind:

class Car:
    """Car with methods as lambda functions."""
    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

    brand = property(lambda self: getattr(self, '_brand'),
                     lambda self, value: setattr(self, '_brand', value))

    year = property(lambda self: getattr(self, '_year'),
                    lambda self, value: setattr(self, '_year', value))

    __str__ = lambda self: f'{self.brand} {self.year}'  # 1: error E731

    honk = lambda self: print('Honk!')     # 2: error E731

Wenn Sie ein Tool wieflake8 ausführen, ein Tool zur Durchsetzung von Stilrichtlinien, werden die folgenden Fehler für__str__ undhonk angezeigt:

E731 do not assign a lambda expression, use a def

Obwohlflake8 kein Problem für die Verwendung der Python-Lambda-Funktionen in den Eigenschaften aufzeigt, sind sie schwer zu lesen und fehleranfällig, da mehrere Zeichenfolgen wie'_brand' und'_year'.

Die ordnungsgemäße Implementierung von__str__ wird wie folgt erwartet:

def __str__(self):
    return f'{self.brand} {self.year}'

brand würde wie folgt geschrieben:

@property
def brand(self):
    return self._brand

@brand.setter
def brand(self, value):
    self._brand = value

In der Regel bevorzugen Sie im Kontext von in Python geschriebenem Code reguläre Funktionen gegenüber Lambda-Ausdrücken. Es gibt jedoch Fälle, die von der Lambda-Syntax profitieren, wie Sie im nächsten Abschnitt sehen werden.

Angemessene Verwendung von Lambda-Ausdrücken

Lambdas in Python sind häufig Gegenstand von Kontroversen. Einige der Argumente gegen Lambdas in Python sind:

  • Probleme mit der Lesbarkeit

  • Die Auferlegung einer funktionalen Denkweise

  • Schwere Syntax mit dem Schlüsselwortlambda

Trotz der hitzigen Debatten, die die bloße Existenz dieser Funktion in Python in Frage stellen, haben Lambda-Funktionen Eigenschaften, die manchmal der Python-Sprache und den Entwicklern einen Wert verleihen.

Die folgenden Beispiele veranschaulichen Szenarien, in denen die Verwendung von Lambda-Funktionen im Python-Code nicht nur geeignet, sondern auch empfohlen wird.

Klassische Funktionskonstrukte

Lambda-Funktionen werden regelmäßig mit den integrierten Funktionenmap() undfilter() sowiefunctools.reduce() verwendet, die im Modulfunctools verfügbar sind. Die folgenden drei Beispiele veranschaulichen die Verwendung dieser Funktionen mit Lambda-Ausdrücken als Begleiter:

>>>

>>> list(map(lambda x: x.upper(), ['cat', 'dog', 'cow']))
['CAT', 'DOG', 'COW']
>>> list(filter(lambda x: 'o' in x, ['cat', 'dog', 'cow']))
['dog', 'cow']
>>> from functools import reduce
>>> reduce(lambda acc, x: f'{acc} | {x}', ['cat', 'dog', 'cow'])
'cat | dog | cow'

Möglicherweise müssen Sie Code lesen, der den obigen Beispielen ähnelt, wenn auch mit relevanteren Daten. Aus diesem Grund ist es wichtig, diese Konstrukte zu erkennen. Trotzdem haben diese Konstrukte äquivalente Alternativen, die als pythonischer angesehen werden. InAlternatives to Lambdas lernen Sie, wie Sie Funktionen höherer Ordnung und die dazugehörigen Lambdas in andere idiomatischere Formen konvertieren.

Schlüsselfunktionen

Schlüsselfunktionen in Python sind Funktionen höherer Ordnung, die einen Parameterkey als benanntes Argument verwenden. key empfängt eine Funktion, dielambda sein kann. Diese Funktion beeinflusst direkt den Algorithmus, der von der Tastenfunktion selbst gesteuert wird. Hier sind einige Schlüsselfunktionen:

  • sort(): Listenmethode

  • sorted(), min(), max(): eingebaute Funktionen

  • nlargest() and nsmallest(): im Heap-Warteschlangenalgorithmus-Modulheapq

Stellen Sie sich vor, Sie möchten eine Liste von IDs sortieren, die als Zeichenfolgen dargestellt werden. Jede ID istconcatenation der Zeichenfolgeid und eine Zahl. Beim Sortieren dieser Liste mit der integrierten Funktionsorted() wird standardmäßig eine lexikografische Reihenfolge verwendet, da die Elemente in der Liste Zeichenfolgen sind.

Um die Sortierausführung zu beeinflussen, können Sie dem benannten Argumentkey ein Lambda zuweisen, sodass bei der Sortierung die der ID zugeordnete Nummer verwendet wird:

>>>

>>> ids = ['id1', 'id2', 'id30', 'id3', 'id22', 'id100']
>>> print(sorted(ids)) # Lexicographic sort
['id1', 'id2', 'id30', 'id3', 'id22', 'id100']
>>> sorted_ids = sorted(ids, key=lambda x: int(x[2:])) # Integer sort
>>> print(sorted_ids)
['id1', 'id2', 'id3', 'id22', 'id30', 'id100']

UI-Frameworks

UI-Frameworks wieTkinter,wxPython oder .NET Windows Forms mitIronPython nutzen Lambda-Funktionen zum Zuordnen von Aktionen als Reaktion auf UI-Ereignisse.

Das folgende naive Tkinter-Programm demonstriert die Verwendung vonlambda, die dem Befehl der SchaltflächeReverse zugewiesen sind:

import tkinter as tk
import sys

window = tk.Tk()
window.grid_columnconfigure(0, weight=1)
window.title("Lambda")
window.geometry("300x100")
label = tk.Label(window, text="Lambda Calculus")
label.grid(column=0, row=0)
button = tk.Button(
    window,
    text="Reverse",
    command=lambda: label.configure(text=label.cget("text")[::-1]),
)
button.grid(column=0, row=1)
window.mainloop()

Durch Klicken auf die SchaltflächeReverse wird ein Ereignis ausgelöst, das die Lambda-Funktion auslöst und die Bezeichnung vonLambda Calculus insuluclaC adbmaL * ändert:

Animated TkInter Windows demonstrating the action of the button to the text

Sowohl wxPython als auch IronPython auf der .NET-Plattform verfolgen einen ähnlichen Ansatz für die Behandlung von Ereignissen. Beachten Sie, dasslambda eine Möglichkeit ist, Zündereignisse zu behandeln, aber eine Funktion kann für denselben Zweck verwendet werden. Es ist in sich geschlossen und weniger ausführlich, einlambda zu verwenden, wenn die benötigte Codemenge sehr kurz ist.

Um wxPython zu erkunden, überprüfen SieHow to Build a Python GUI Application With wxPython.

Python-Interpreter

Wenn Sie im interaktiven Interpreter mit Python-Code spielen, sind Python-Lambda-Funktionen oft ein Segen. Es ist einfach, eine schnelle Einzeilerfunktion zu erstellen, um einige Codeausschnitte zu untersuchen, die außerhalb des Interpreters niemals das Licht der Welt erblicken. Die im Dolmetscher geschriebenen Lambdas sind aus Gründen der schnellen Entdeckung wie Altpapier, das Sie nach Gebrauch wegwerfen können.

timeit

Im gleichen Sinne wie das Experimentieren im Python-Interpreter bietet das Modultimeit Funktionen zum Zeitmessung kleiner Codefragmente. Insbesonderetimeit.timeit() können direkt aufgerufen werden, indem Python-Code in einer Zeichenfolge übergeben wird. Hier ist ein Beispiel:

>>>

>>> from timeit import timeit
>>> timeit("factorial(999)", "from math import factorial", number=10)
0.0013087529951008037

Wenn die Anweisung als Zeichenfolge übergeben wird, benötigttimeit() den vollständigen Kontext. Im obigen Beispiel wird dies durch das zweite Argument bereitgestellt, das die Umgebung einrichtet, die für die zeitgesteuerte Hauptfunktion erforderlich ist. Andernfalls würde eine Ausnahme vonNameErrorausgelöst.

Ein anderer Ansatz ist die Verwendung vonlambda:

>>>

>>> from math import factorial
>>> timeit(lambda: factorial(999), number=10)
0.0012704220062005334

Diese Lösung ist sauberer, lesbarer und lässt sich schneller in den Interpreter eingeben. Obwohl die Ausführungszeit für die Versionlambdaetwas kürzer war, kann die erneute Ausführung der Funktionen für die Versionstringeinen leichten Vorteil darstellen. Die Ausführungszeit vonsetup ist von der Gesamtausführungszeit ausgeschlossen und sollte keinen Einfluss auf das Ergebnis haben.

Affen Patching

Zum Testen ist es manchmal erforderlich, sich auf wiederholbare Ergebnisse zu verlassen, selbst wenn während der normalen Ausführung einer bestimmten Software erwartet wird, dass die entsprechenden Ergebnisse abweichen oder sogar völlig zufällig sind.

Angenommen, Sie möchten eine Funktion testen, die zur Laufzeit zufällige Werte verarbeitet. Während der Testausführung müssen Sie sich jedoch auf wiederholbare Weise gegen vorhersehbare Werte behaupten. Das folgende Beispiel zeigt, wie Ihnen das Patchen von Affen mit der Funktionlambdahelfen kann:

from contextlib import contextmanager
import secrets

def gen_token():
    """Generate a random token."""
    return f'TOKEN_{secrets.token_hex(8)}'

@contextmanager
def mock_token():
    """Context manager to monkey patch the secrets.token_hex
    function during testing.
    """
    default_token_hex = secrets.token_hex
    secrets.token_hex = lambda _: 'feedfacecafebeef'
    yield
    secrets.token_hex = default_token_hex

def test_gen_key():
    """Test the random token."""
    with mock_token():
        assert gen_token() == f"TOKEN_{'feedfacecafebeef'}"

test_gen_key()

Ein Kontextmanager hilft beim Isolieren des Betriebs des Affen-Patchens einer Funktion aus der Standardbibliothek (in diesem Beispielsecrets). Die Lambda-Funktion, diesecrets.token_hex() zugewiesen ist, ersetzt das Standardverhalten durch Rückgabe eines statischen Werts.

Dies ermöglicht es, jede Funktion in Abhängigkeit vontoken_hex() auf vorhersagbare Weise zu testen. Vor dem Verlassen des Kontextmanagers wird das Standardverhalten vontoken_hex() wiederhergestellt, um alle unerwarteten Nebenwirkungen zu beseitigen, die andere Bereiche des Tests betreffen würden, die möglicherweise vom Standardverhalten vontoken_hex() abhängen.

Unit-Test-Frameworks wieunittest undpytest bringen dieses Konzept auf ein höheres Niveau.

Mitpytest, das immer noch einelambda-Funktion verwendet, wird dasselbe Beispiel eleganter und prägnanter:

import secrets

def gen_token():
    return f'TOKEN_{secrets.token_hex(8)}'

def test_gen_key(monkeypatch):
    monkeypatch.setattr('secrets.token_hex', lambda _: 'feedfacecafebeef')
    assert gen_token() == f"TOKEN_{'feedfacecafebeef'}"

Mitpytest monkeypatch fixture wirdsecrets.token_hex() mit einem Lambda überschrieben, das einen deterministischen Wert,feedfacecafebeef, zurückgibt, wodurch der Test validiert werden kann. Mit dem Gerät pytestmonkeypatchkönnen Sie den Umfang der Überschreibung steuern. Im obigen Beispiel würde das Aufrufen vonsecrets.token_hex() in nachfolgenden Tests ohne Verwendung von Affen-Patches die normale Implementierung dieser Funktion ausführen.

Das Ausführen despytest-Tests ergibt das folgende Ergebnis:

$ pytest test_token.py -v
============================= test session starts ==============================
platform linux -- Python 3.7.2, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
cachedir: .pytest_cache
rootdir: /home/andre/AB/tools/bpython, inifile:
collected 1 item

test_token.py::test_gen_key PASSED                                       [100%]

=========================== 1 passed in 0.01 seconds ===========================

Der Test besteht, als wir bestätigten, dassgen_token() ausgeübt wurde, und die Ergebnisse waren die erwarteten im Rahmen des Tests.

Alternativen zu Lambdas

Während es gute Gründe gibt,lambda zu verwenden, gibt es Fälle, in denen seine Verwendung verpönt ist. Was sind also die Alternativen?

Funktionen höherer Ordnung wiemap(),filter() undfunctools.reduce() können mit leichten kreativen Wendungen in elegantere Formen umgewandelt werden, insbesondere mit Listenverständnissen oder Generatorausdrücken.

Sehen Sie sichUsing List Comprehensions Effectively an, um mehr über das Listenverständnis zu erfahren.

Map

Die eingebaute Funktionmap() nimmt eine Funktion als erstes Argument und wendet sie auf jedes der Elemente ihres zweiten Arguments an, einiterable. Beispiele für iterable Dateien sind Zeichenfolgen, Listen und Tupel. Weitere Informationen zu Iterables und Iteratoren finden Sie unterIterables and Iterators.

map() gibt einen Iterator zurück, der der transformierten Sammlung entspricht. Wenn Sie beispielsweise eine Liste von Zeichenfolgen mit jeder Großschreibung in eine neue Liste umwandeln möchten, können Siemap() wie folgt verwenden:

>>>

>>> list(map(lambda x: x.capitalize(), ['cat', 'dog', 'cow']))
['Cat', 'Dog', 'Cow']

Sie müssenlist() aufrufen, um den vonmap() zurückgegebenen Iterator in eine erweiterte Liste zu konvertieren, die im Python-Shell-Interpreter angezeigt werden kann.

Durch die Verwendung eines Listenverständnisses muss die Lambda-Funktion nicht mehr definiert und aufgerufen werden:

>>>

>>> [x.capitalize() for x in ['cat', 'dog', 'cow']]
['Cat', 'Dog', 'Cow']

Filter

Die eingebaute Funktionfilter(), ein weiteres klassisches Funktionskonstrukt, kann in ein Listenverständnis umgewandelt werden. Es wird einpredicate als erstes Argument und ein iterables als zweites Argument verwendet. Es wird ein Iterator erstellt, der alle Elemente der ursprünglichen Sammlung enthält, die die Prädikatfunktion erfüllen. Hier ist ein Beispiel, das alle geraden Zahlen in einer bestimmten Liste von Ganzzahlen filtert:

>>>

>>> even = lambda x: x%2 == 0
>>> list(filter(even, range(11)))
[0, 2, 4, 6, 8, 10]

Beachten Sie, dassfilter() einen Iterator zurückgibt, daher muss der integrierte Typlist aufgerufen werden, der eine Liste mit einem Iterator erstellt.

Die Implementierung, die das Listenverständniskonstrukt nutzt, bietet Folgendes:

>>>

>>> [x for x in range(11) if x%2 == 0]
[0, 2, 4, 6, 8, 10]

Reduzieren

Seit Python 3 istreduce() von einer integrierten Funktion zu einerfunctools-Modulfunktion übergegangen. Alsmap() undfilter() sind die ersten beiden Argumente jeweils eine Funktion und eine Iterierbarkeit. Es kann auch ein Initialisierer als drittes Argument verwendet werden, das als Anfangswert des resultierenden Akkumulators verwendet wird. Für jedes Element der Iterable wendetreduce() die Funktion an und akkumuliert das Ergebnis, das zurückgegeben wird, wenn die Iterable erschöpft ist.

Umreduce() auf eine Liste von Paaren anzuwenden und die Summe des ersten Elements jedes Paares zu berechnen, können Sie Folgendes schreiben:

>>>

>>> import functools
>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> functools.reduce(lambda acc, pair: acc + pair[0], pairs, 0)
6

Ein idiomatischerer Ansatz, bei demgenerator expression als Argument fürsum() im Beispiel verwendet wird, ist folgender:

>>>

>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> sum(x[0] for x in pairs)
6

Eine etwas andere und möglicherweise sauberere Lösung macht es überflüssig, explizit auf das erste Element des Paares zuzugreifen und stattdessen das Auspacken zu verwenden:

>>>

>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> sum(x for x, _ in pairs)
6

Die Verwendung von Unterstrichen (_) ist eine Python-Konvention, die angibt, dass Sie den zweiten Wert des Paares ignorieren können.

sum() verwendet ein eindeutiges Argument, sodass der Generatorausdruck nicht in Klammern stehen muss.

Sind Lambdas Pythonic oder nicht?

PEP 8, der Styleguide für Python-Code, lautet:

Verwenden Sie immer eine def-Anweisung anstelle einer Zuweisungsanweisung, die einen Lambda-Ausdruck direkt an einen Bezeichner bindet. (Source)

Dies rät dringend davon ab, an einen Bezeichner gebundenes Lambda zu verwenden, vor allem dort, wo Funktionen verwendet werden sollten und mehr Vorteile bieten. In PEP 8 werden keine anderen Verwendungen vonlambda erwähnt. Wie Sie in den vorherigen Abschnitten gesehen haben, können Lambda-Funktionen sicherlich gute Verwendungszwecke haben, obwohl sie begrenzt sind.

Eine Möglichkeit, die Frage zu beantworten, besteht darin, dass Lambda-Funktionen perfekt pythonisch sind, wenn nichts mehr pythonisch verfügbar ist. Ich halte mich von der Definition von "Pythonic" fern und überlasse Ihnen die Definition, die am besten zu Ihrer Denkweise sowie zu Ihrem persönlichen oder dem Codierungsstil Ihres Teams passt.

Über den engen Bereich von Pythonlambda hinaus istHow to Write Beautiful Python Code With PEP 8 eine großartige Ressource, die Sie möglicherweise in Bezug auf den Codestil in Python überprüfen möchten.

Fazit

Sie wissen jetzt, wie Sie die Funktionen von Pythonlambdaverwenden und können:

  • Schreiben Sie Python-Lambdas und verwenden Sie anonyme Funktionen

  • Wählen Sie mit Bedacht zwischen Lambdas oder normalen Python-Funktionen

  • Vermeiden Sie übermäßigen Gebrauch von Lambdas

  • Verwenden Sie Lambdas mit Funktionen höherer Ordnung oder Python-Tastenfunktionen

Wenn Sie eine Vorliebe für Mathematik haben, können Sie Spaß daran haben, die faszinierende Welt vonlambda calculus zu erkunden.

Python Lambdas sind wie Salz. Eine Prise Spam, Schinken und Eier verbessert die Aromen, aber zu viel verdirbt das Gericht.

__ Take the Quiz: Testen Sie Ihr Wissen mit unserem interaktiven Quiz „Python Lambda-Funktionen“. Nach Abschluss erhalten Sie eine Punktzahl, mit der Sie Ihren Lernfortschritt im Laufe der Zeit verfolgen können:

Note: Die nach Monty Python benannte Python-Programmiersprache verwendet lieberspam,ham undeggs als metasyntaktische Variablen anstelle der traditionellenfoo. bar undbaz.