Lambda-funk­tio­ner har været en metode til funk­tio­nel pro­gram­me­ring siden Python 1.0. I de senere år har andre teknikker imid­ler­tid vundet større po­pu­la­ri­tet og i vid ud­stræk­ning erstattet lambdaer. Ikke desto mindre er der stadig nogle spe­ci­a­li­se­re­de an­ven­del­ser for lambdaer, som kyndige Python-pro­gram­mø­rer bør kende til.

Hvad er lambda-funk­tio­ner i Python?

I Python refererer lambda-funk­tio­nen til en anonym funktion. Python bruger nøg­le­or­det lambda til at oprette en lambda-funktion. Et lambda-udtryk består af nøg­le­or­det lambda ef­ter­fulgt af en liste over ar­gu­men­ter, et kolon og et enkelt udtryk. Udtrykket forsynes med ar­gu­men­ter­ne og evalueres, når lambda-funk­tio­nen kaldes:

lambda argument: expression

Funk­tio­ner er en grund­læg­gen­de sprog­kon­struk­tion i næsten alle pro­gram­me­rings­sprog, og de re­præ­sen­te­rer den mindste gen­an­ven­de­li­ge enhed af kode. Typisk defineres funk­tio­ner i Python med nøg­le­or­det def. Vi viser dig kva­drat­funk­tio­nen, der mul­ti­pli­ce­rer et tal med sig selv, som et eksempel:

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

Nøg­le­or­det def er en velkendt måde at definere funk­tio­ner på i Python, men sproget har også lambdas. Dette er anonyme funk­tio­ner, der definerer et udtryk med parametre. Lambdas kan bruges overalt, hvor en funktion forventes eller kan tildeles et navn. Du kan se lambda-udtrykket, der svarer til kva­drat­funk­tio­nen, her:

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

Hvad er for­skel­len mellem lambda og def?

Det kan virke underligt, at Python lader dig oprette funk­tio­ner med både lambda og def. Men Lambda er ikke en egen funktion, men blot en anden måde at oprette korte funk­tio­ner lokalt på. Alle funk­tio­ner, der oprettes med lambda, kan også oprettes med def. Det gælder dog ikke omvendt.

På det syn­tak­ti­ske niveau er lambda og def begge nøgleord. En vigtig forskel mellem dem er Pythons strenge ad­skil­lel­se af sætninger og udtryk. Sætninger er trin i ud­fø­rel­sen af kode, mens udtryk evalueres til en værdi.

Def indleder en sætning, eller mere specifikt en sammensat sætning, som in­de­hol­der yder­li­ge­re sætninger. Der må kun forekomme return sætninger inden for en def sætning. En return sætning re­tur­ne­rer en værdi, når den funktion, der er defineret med def, kaldes.

I mod­sæt­ning til def starterlambda et udtryk, der ikke kan indeholde nogen sætninger. Lambda-udtrykket tager et eller flere ar­gu­men­ter og re­tur­ne­rer en anonym funktion. Når lambda-funk­tio­nen kaldes, evalueres det udtryk, den in­de­hol­der, med de overførte ar­gu­men­ter, og det re­tur­ne­res.

Hvad er be­græns­nin­ger­ne ved Pythons lambda-udtryk?

Python har bevidst begrænset nytten af lambda-funk­tio­ner, da det normalt er bedre at navngive funk­tio­ner. Dette tvinger pro­gram­mø­rer til at tænke over funk­tio­nens betydning og skelne klart mellem de for­skel­li­ge dele.

Lambdaer kan ikke indeholde in­struk­tio­ner, i mod­sæt­ning til kroppen af en funktion defineret med nøg­le­or­det def. Det er derfor ikke muligt at bruge if, for osv. i en lambda-funktion. Det er heller ikke muligt at udløse en und­ta­gel­se, da dette kræver en raise.

Lambda-funk­tio­ner i Python kan indeholde et enkelt udtryk, der evalueres, når det kaldes. Ty­pe­an­no­ta­tio­ner kan ikke bruges i lambda-udtrykket. I dag bruger de fleste an­ven­del­ses­til­fæl­de af lambda-funk­tio­ner i Python andre teknikker, såsom com­pre­hen­sions.

For­skel­li­ge an­ven­del­ser af lambda-funk­tio­ner i Python

Lambdaer stammer fra funk­tio­nel pro­gram­me­ring. I nogle sprog, såsom Ja­va­Script, bruges anonyme funk­tio­ner i vid ud­stræk­ning uden behov for et særligt nøgleord. I Python bruges lambda-udtryk til at oprette små funk­tio­ner lokalt. Vi gennemgår deres mest nyttige an­ven­del­ser nedenfor.

Sådan udfyldes højere or­dens­funk­tio­ner i Python med lambdas

Lambdaer bruges ofte sammen med højere or­dens­funk­tio­ner som map(), filter() og reduce(). Ele­men­ter­ne i en iterabel kan trans­for­me­res uden brug af sløjfer takket være lambdaer. Højere or­dens­funk­tio­ner er funk­tio­ner, der tager funk­tio­ner som parametre eller re­tur­ne­rer en funktion.

Funk­tio­nen map() tager en funktion og en iterabel som parametre. Den udfører funk­tio­nen for hvert element i den iterable. Lad os prøve at generere kva­drat­num­re. Vi bruger funk­tio­nen map() og sender et lambda-udtryk som argument, hvilket genererer kva­drat­funk­tio­nen. Kva­drat­funk­tio­nen anvendes på hvert element i listen med map():

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

Fra og med Python 3.0 re­tur­ne­rer funk­tio­ner­ne map() og filter() en iterabel i stedet for en liste. Et list() -kald bruges i assert -sæt­nin­ger­ne til at udpakke iterabler til en liste.

List com­pre­hen­sions tilbyder en mere moderne tilgang til be­hand­ling af iterables. I stedet for at ty til map() og generere en lambda-funktion, kan vi beskrive ope­ra­tio­nen direkte:

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

Funk­tio­nen filter() kan bruges til at filtrere ele­men­ter­ne i en iterabel. Vi kan udvide vores eksempel til kun at generere lige kva­drat­num­re:

# 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

Vi viser den fo­re­truk­ne tilgang, hvor man bruger liste­for­stå­el­se til at generere det samme resultat uden at bruge lambdas og højere or­dens­funk­tio­ner. Den if del af for­stå­el­sen bruges til at filtrere de lige tal fra de ge­ne­re­re­de kva­drat­num­re:

# 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
Note

Pythons reduce() -funktion er ikke blevet in­klu­de­ret i stan­dard­bi­bli­o­te­ket siden Python 3.0. Denne funktion kan findes i functools -modulet.

Sådan im­ple­men­te­res nøg­le­funk­tio­ner i Python med lambdas

Com­pre­hen­sions har i vid ud­stræk­ning erstattet de klassiske højere or­dens­funk­tio­ner map() og filter() i Python. Nøg­le­funk­tio­ner kan dog bruges til at de­mon­stre­re lambdas fulde styrker.

Python-sam­men­lig­nings­funk­tio­ner­ne sorted(), min() og max() fungerer på iterable. Hvert element i iterable un­der­ka­stes en sam­men­lig­ning, når det kaldes. De tre funk­tio­ner tager en nøg­le­funk­tion som en valgfri key parameter. Nøg­le­funk­tio­nen kaldes for hvert element og re­tur­ne­rer en nøg­le­vær­di til sam­men­lig­nings­o­pe­ra­tio­nen.

Lad os se på følgende problem. Vi har en mappe med bil­led­fi­ler, hvor navnene er knyttet til en Python-liste. Vi vil sortere listen. Alle filnavne starter med img ef­ter­fulgt af et tal:

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

Hvis vi bruger Pythons sorted(), anvendes den lek­si­ko­gra­fi­ske ræk­ke­føl­ge. Her behandles på hinanden følgende cifre som en­keltcif­re. Derfor placeres tallet ['1', '2', '100'] i ræk­ke­føl­gen ['1', '100', '2']. Re­sul­ta­tet er ikke som forventet:

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

Vi sender et lambda, der pro­du­ce­rer en nøg­le­funk­tion for at sikre, at sor­te­rin­gen er korrekt. Nøg­le­funk­tio­nen udtrækker den numeriske del af et filnavn, som bruges som nøgle af sorted():

# 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

Nøg­le­funk­tio­nen bruges lokalt og kun én gang. Det er ikke nød­ven­digt at definere en ekstra navngiven funktion til den. Lambdas er den korrekte måde at oprette nøg­le­funk­tio­ner på. Lad os se på to flere eksempler.

Ligesom sorted() tager de ind­byg­ge­de Python-funk­tio­ner min() og max() en valgfri nøg­le­funk­tion. Funk­tio­ner­ne finder det mindste og største element i en liste eller anden iterabel. Det mindste eller største element er et spørgsmål om de­fi­ni­tion og kan spe­ci­fi­ce­res ved hjælp af nøg­le­funk­tio­nen.

Det er klart, hvad der menes med det mindste eller største element for lister med enkle værdier, såsom en liste med tal. I dette tilfælde har vi ikke brug for en særlig nøg­le­funk­tion:

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

Hvis der ikke overføres nogen nøg­le­funk­tion, bruges iden­ti­tets­funk­tio­nen f(x) = x som standard. Dette kan nemt defineres som en Python-lambda med lambda x: x.

Men hvad nu hvis hvert element i en iterabel in­de­hol­der flere datoer? Lad os fo­re­stil­le os en liste med ordbøger, der re­præ­sen­te­rer personer med deres navne og alder. Hvad er kriteriet for min() og max(), når man skal beslutte, hvilket element der er det mindste og det største? Det er her, en nøg­le­funk­tion er nyttig.

Vi har brug for ek­sem­pel­da­ta for at il­lu­stre­re, hvordan nøg­le­funk­tio­ner fungerer. Lad os oprette en funktion Person(), der fungerer som en kon­struk­tør:

# 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

Vi opretter en liste over personer ved hjælp af vores kon­struk­tor­funk­tion:

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

Vi finder den ældste person ved hjælp af max(). Dette genererer en nøg­le­funk­tion ved hjælp af lambda-udtrykket, som tager en person-dict og udtrækker alderen fra den som et sam­men­lig­nings­e­le­ment:

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

Tilgangen fungerer nøjagtigt på samme måde for funk­tio­nen min(). I dette tilfælde definerer vi nøg­le­funk­tio­nen uden for min() kaldet og bruger igen et lambda-udtryk. Dette forbedrer læs­bar­he­den og er vær­di­fuldt, hvis nøg­le­funk­tio­nen har flere lokale an­ven­del­ser:

# 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

Sådan oprettes lukninger med Python-lambdaer

Python-lambdaer bruges også til at definere lukninger. Det er funk­tio­ner, der oprettes af andre funk­tio­ner og gemmer en værdi. Lukninger kan bruges til at oprette familier af lignende funk­tio­ner. Vi viser et al­min­de­ligt eksempel, hvor der oprettes po­tens­funk­tio­ner.

Po­tens­funk­tio­ner tager et argument og eks­po­ne­rer det. Kva­drat­funk­tio­nen f(x) = x ^ 2 og ku­bik­funk­tio­nen f(x) = x ^ 3 er velkendte eksempler. Vil­kår­li­ge po­tens­funk­tio­ner kan genereres som lukninger ved hjælp af en kon­struk­tor­funk­tion. Vi bruger et lambda-udtryk, hvilket betyder, at vi ikke behøver at definere en indre navngivet 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

Sådan bruges IIFE (Im­me­di­a­te­ly Invoked Function Expres­sion) med Python-lambdaer

IIFE, udtalt ‘iffy’, er et kendt mønster i Ja­va­Script. Det indebærer at definere en anonym funktion og udføre den med det samme.

Lambdaer kan bruges som IIFE’er, selvom de ikke er særlig nyttige på grund af be­græns­nin­ger i Python. Vi behøver kun at sætte pa­ren­te­ser omkring lambda-udtrykket:

(lambda num: num * num)
python

Og endnu et par pa­ren­te­ser, der in­de­hol­der ar­gu­men­tet/ar­gu­men­ter­ne:

assert (lambda num: num * num)(3) == 9
python
Gå til ho­ved­me­nu­en