Skip to content

Alt om WordPress, webutvikling — og mer til

🐍 Funksjonell programmering i Python

🐍 Funksjonell programmering i Python

Enhver Python-utvikler hører det før eller siden: «prøv å skrive i en funksjonell stil, så blir koden renere.» Men når du åpner dokumentasjonen, ser du map, filter og reduce med lambdaer og forstår ikke hvor du skal begynne.

Problemet er ikke at funksjonell programmering er vanskelig. Problemet er at de fleste guider enten dykker ned i akademiske Haskell-kratt eller begrenser seg til syntaks uten å forklare «hvorfor». Python, som er et multi-paradigme-språk, gir deg akkurat så mange funksjonelle verktøy som du trenger til hverdagsoppgaver, uten ytterligheter.

I dette materialet får du en praktisk gjennomgang av Pythons funksjonelle muligheter: fra listekomprehensjoner til lambda-funksjoner, med ekte eksempler og scenarioer der hvert verktøy genuint forenkler koden.

💡 Rask oversikt:

  • Nedbryting av listekomprehensjoner, hvorfor de er bedre enn løkker og når du bør bruke dem
  • Mestring av generatorer: yield, minnebesparelser og generatoruttrykk
  • Gjennomgang av map + filter + reduce: praktiske eksempler på å erstatte løkker
  • Lære å skrive lambda-funksjoner og forstå hvor de passer og hvor de skader lesbarheten

Funksjonell programmering, hva snakker vi om

Funksjonell programmering er en tilnærming der et program bygges av matematiske funksjoner: de tar uforanderlige inndata og returnerer et resultat uten å endre noe utenfor. Ingen sideeffekter, ingen modifisering av global tilstand.

I rene funksjonelle språk som Haskell er «rene» og «urene» (de som samhandler med omverdenen) deler av programmet strengt atskilt. Denne tilnærmingen muliggjør formell bevising av kodekorrekthet: kompilatoren garanterer fravær av uventede sideeffekter.

Python ble ikke designet som et funksjonelt språk. Det er multi-paradigme: objektorientert, prosedyreorientert og, ja, funksjonelt også. Å skrive i Python i en rent funksjonell stil er som å hamre spiker med et mikroskop: teknisk mulig, men ubehagelig og unaturlig. Å ta enkeltstående funksjonelle verktøy og bygge dem inn i velkjent kode er derimot et fungerende og nyttig mønster.

Listekomprehensjoner, Pythons viktigste funksjonelle verktøy

Selve grunnmuren i funksjonell programmering er arbeid med lister. Velg elementer etter betingelse, transformer hvert element, bygg en ny liste fra en eksisterende, alt dette dekkes i Python av listekomprehensjoner.

Før, prosedyreløkke:

1def filter_odd(li):
2 result = []
3 for i in li:
4 if i % 2 == 1:
5 result.append(i)
6 return result
7
8print(filter_odd([2, 4, 6, 7, 8, 1, 19, 200, 42, 31]))

Etter, listekomprehensjon:

1li = [2, 4, 6, 7, 8, 1, 19, 200, 42, 31]
2odd_numbers = [x for x in li if x % 2 == 1]
3print(odd_numbers) # [7, 1, 19, 31]

Fire linjer med prosedyrekode komprimert til én. Lesbarheten ble ikke dårligere: syntaksen [expression for element in iterator if condition] er intuitivt forståelig, «ta x fra li hvis x er oddetall».

Og dette er ikke bare syntaktisk sukker. Listekomprehensjon kjører raskere enn en tilsvarende løkke med .append() fordi den utføres på C-nivå, ikke på Python-tolker-nivå.

Hva er en iterator

Listekomprehensjon bygger på konseptet om en iterator, et objekt som returnerer neste element i en sekvens når next() kalles. Ethvert objekt som implementerer metoden __iter__ kalles itererbart. Liste, streng, tuppel, ordbok, sett, de er alle itererbare.

1spam_iter = iter("foobar")
2result = "".join([c.upper() for c in spam_iter])
3print(result) # FOOBAR

Her returnerer iter() en streng-iterator, og listekomprehensjonen itererer gjennom den og bruker .upper() på hvert tegn.

Generatorer, late sekvenser

En iterator kan opprettes ikke bare fra en eksisterende samling, men også ved hjelp av en generatorfunksjon. I stedet for return bruker den yield: funksjonen «sovner», husker tilstanden sin, og ved neste next()-kall fortsetter den fra samme sted.

1def gen(max_val):
2 i = 1
3 while i < max_val:
4 yield i
5 i += 1
6
7g = gen(1000)
8for _ in range(12):
9 print(next(g))

Denne koden vil skrive ut tall fra 1 til 12. Funksjonen gen oppretter ikke en liste med tusen elementer i minnet, den genererer verdier én etter én, på forespørsel. For sekvenser med millioner av poster (loggfillinger, databasestrøm) er forskjellen i minneforbruk størrelsesordener.

Generatoruttrykk

Kompakt syntaks: det samme som listekomprehensjoner, men med parenteser i stedet for hakeparenteser.

1g = (c.upper() for c in "foobar")

Dette er ikke en tuppel, dette er en generator. Regelen er enkel: hakeparenteser → liste (ivrig, alt i minnet), parenteser → generator (lat, ett element om gangen).

Generatorer er en bro mellom prosedyrestil og funksjonell stil i Python: de gir lat evaluering uten å dykke ned i monadeteori.

Map, filter og reduce: tre pilarer i funksjonell prosessering

Tre innebygde funksjoner som i funksjonelle språk er grunnlaget for alt. Python implementerer dem på sin egen måte, og det er viktig å kjenne nyansene i de Python-spesifikke versjonene.

Map, bruk en funksjon på hvert element

1def square(x):
2 return x * x
3
4result = map(square, [1, 2, 3])
5print(list(result)) # [1, 4, 9]

map tar en funksjon og en sekvens, og returnerer en iterator med resultatene av å bruke funksjonen på hvert element. I Python 3 returnerer map en iterator, ikke en liste, så du trenger list() for å se resultatet.

For enkle transformasjoner leses listekomprehensjon ofte bedre:

1[x * x for x in [1, 2, 3]] # same thing, but more familiar

Filter, velg elementer etter betingelse

1def is_upper(c):
2 return c == c.upper()
3
4result = filter(is_upper, "FreedominObscureandoutlandishcOde")
5print(list(result)) # ['F', 'O', 'O']

filter beholder bare de elementene der predikatfunksjonen returnerer True. Som map returnerer den en iterator i Python 3.

Reduce, fold en sekvens til én enkelt verdi

I motsetning til map og filter ligger ikke reduce i det innebygde navnerommet, men i modulen functools. Den bruker en funksjon sekvensielt på elementer og akkumulerer resultatet.

1from functools import reduce
2
3def add(a, b):
4 return a + b
5
6print(reduce(add, range(1, 6))) # 15

Under panseret: ((((1 + 2) + 3) + 4) + 5) = 15. For å summere tall i Python finnes den innebygde sum(), så reduce brukes oftere for ikke-standard folder: bygge et tre fra en flat liste, flette nestede ordbøker, beregne største felles divisor for en sekvens.

Lambda-funksjoner, anonyme hjelpere

Når en funksjon trengs nøyaktig én gang og kroppen får plass i ett uttrykk, bruk lambda:

1lambda arguments: expression

Det samme filter-eksempelet kan skrives om uten en separat def:

1result = list(filter(lambda c: c == c.upper(), "FreedominObscureandoutlandishcOde"))

Lambda er et kompromiss. Pluss: du slipper å deklarere en separat funksjon for én triviell sjekk. Minus: lesbarheten synker hvis uttrykket blir mer komplekst enn et par operasjoner. Tommelfingerregel: hvis lambdaen ikke får plass på én linje eller du begynner å nøste lambdaer inni hverandre, trekk ut en vanlig def.

Hvor lambdaer faktisk er nyttige

Det vanligste scenarioet er sortering etter en ikke-standard nøkkel:

1users = [{"name": "Alice", "age": 31}, {"name": "Bob", "age": 25}]
2users.sort(key=lambda u: u["age"])

Eller rask transformasjon i map/filter når kroppen er virkelig triviell:

1squares = list(map(lambda x: x * x, range(10)))

Men hvis du strekker deg etter en lambda for logikk med fire eller fem operasjoner, stopp. Et funksjonsnavn fungerer som dokumentasjon, og def gir plass til en docstring.

⁉️🤔 Vanlige spørsmål

Når er en listekomprehensjon bedre enn map?

En listekomprehensjon som [x*2 for x in data] leses mer naturlig enn list(map(lambda x: x*2, data)). En innebygd operasjon med en lambda har to nivåer av indireksjon, mens en listekomprehensjon har ett. Men hvis funksjonen allerede finnes som en separat def, er map med den konsis. Listekomprehensjon vinner i lesbarhet for enkle transformasjoner, spesielt med filtrering gjennom [... if ...]. For kompleks logikk med mellomliggende variabler er en vanlig for-løkke uslåelig.

Generator eller liste, hva skal man velge?

Hvis resultatet trengs flere ganger (iterering, indeksering, lengde), ta en liste. Hvis du itererer én gang og datamengden er stor, vil en generator spare minne. En liste med en million heltall tar omtrent 8 MB, en generator for samme intervall tar under en kilobyte. Men en generator kan ikke «spoles tilbake» eller aksesseres med indeks. Praktisk regel: data fra en fil eller database, generator; resultat for gjentatt bruk, list().

Skader lambdaer kodens lesbarhet?

I små doser og for triviell logikk, nei. key=lambda x: x["price"] leses umiddelbart. Men så snart en lambda slutter å være «én tanke», trekk den ut i en vanlig funksjon. Team med kodegjennomgang begrenser lambdaer til ett uttrykk uten nøsting. Hvis en kollega trenger mer enn fem sekunder på å forstå den, skriv den om som en def. Navnet fungerer som dokumentasjon: def by_price(item): return item["price"] er tydeligere enn en anonym lambda.

Hvor mye tregere er funksjonell Python enn prosedyrebasert?

For de fleste hverdagsoppgaver er forskjellen ubetydelig, brøkdeler av et mikrosekund. Listekomprehensjon er ofte raskere enn en tilsvarende løkke fordi den er implementert i C. Flaskehalsen i Python-programmer er ikke valget mellom map og en løkke, men input/output, nettverksforespørsler og arkitektur generelt. Skriv tydelig, profiler varme punkter med cProfile, og optimaliser bare det som faktisk bremser ting.

Hva du kan bruke i koden din i dag

Pythons funksjonelle verktøy er ikke dogmer, men spaker. De erstatter ikke prosedyreorientert og objektorientert kode, men utfyller den på spesifikke punkter.

Start med listekomprehensjoner, dette er den vanligste og tryggeste anvendelsen. Mestre generatorer: så snart du ser en midlertidig liste som brukes én gang, spør deg selv, burde ikke dette erstattes med yield? Bruk map og filter der de genuint forenkler en transformasjonskjede. Spar lambdaer til key= i sortering og trivielle tilbakekall.

Funksjonell stil i Python handler ikke om «renhet», men om uttrykkskraft. Hvis koden ble tydeligere etter at du erstattet en løkke med en listekomprehensjon, gjorde du alt riktig. Prøv å skrive om én problematisk metode i en funksjonell stil og sammenlign diffen: ofte taler resultatet for seg selv.