Python ni imel izpeljanih seznamov od vedno. Dobil jih je, ko je imel okrog 9 let, večine študentov pa še ni bilo med nami. Z verzijo 2.0, leta 2000.
Predtem sta tej sceni vladali dve funkciji: map
in
filter
.
Funkcija map
kot argument prejme funkcijo in nekaj, prek
česar je možno nagnati zanko. Vsak element tega, nečesa, "premapira" čez
funkcijo. Če imamo
from math import sqrt
= [9, 25, 16, 81] k
bo map(sqrt, k)
vrnil korene vseh števil v
k
:
for x in map(sqrt, k):
print(x)
Funkcija map
dela, približno tole:
def map(func, s):
= []
t for x in s:
t.append(func(x))return t
Do Pythona 3.0 je funkcija map
v resnici vračala seznam,
od različica 3.0 naprej pa vrne iterator. Za tiste, ki ne
veste, kaj je to: vede se kot seznam, samo da ni; čezenj lahko gremo z
zanko for
. Za tiste, ki ne veste, pa bi radi izvedeli:
preberite zapiske o generatorjih in iteratorjih. Za tiste, ki veste: ja,
takšen:
def map(func, s):
for x in s:
yield func(s)
Funkcijo map
od Pythona 2.0 naprej uporabljamo zelo
redko. map(func, s)
je isto kot
(func(x) for x in s)
. Prednost novejše različice je v tem,
da
(x ** 2 for x in s)
ne moremo prepisati v
map(**2, s)
, temveč potrebujemo lambdo:
map(lambda x: x ** 2, s)
;map
počasnejši, ker vedno kliče funkcijo, medtem ko
je novejši zapis, generator, ne (vsak, dokler lahko vse opravimo z
izrazom).Osebno map
rad uporabim, kadar imam funkcijo ravno pri
roki in kadar izgleda sintaktično lepše.
Se pravi: redko.
Funkcija filter
je druga funkcija, ki so jo izpeljani
seznami spravili ob delo. filter(func, s)
vrne vse tiste
elemente s
, pri katerih func
vrne
True
.
def vsebuje_i(s):
return "i" in s
= ["Ana", "Berta", "Cilka", "Dani", "Ema"]
imena
for x in filter(vsebuje_i, imena):
print(x)
To je seveda isto kot
(x for x in imena if vsebuje_i(x))
, kar je tako ali tako le
bolj zapletena različica (x for x in imena if "i" in x)
.
Resnici na ljubo tudi filter
ne potrebuje poprej definirane
funkcije, saj bi lahko pisali
filter(lambda x: "i" in x, imena)
. Vendar je očitno, zakaj
filtra ne vidimo več velikokrat.
Izpeljani seznami, slovarji, množice in generatorji v enem zamahu naredijo oboje, mapirajo in filtrirajo.
Funkcija reduce
je edina iz te družbe, ki ni ostala
brezposelna. No, hkrati pa tudi najmanj uporabna od njih, saj Python ni
ravno jezik za te hece. Mogoče je tudi to razlog, da jo dobimo v modulu
functools
in ne kar tako, na prostem.
reduce(func, s)
je nekako ekvivalenten temu
func(func(func(func(s[0], s[1])), s[2]), s[3]), s[4])
- če
je s
seznam s petimi elementi. Ali, v kodi (ki sicer ne zna
vsega, kar zna reduce
):
def reduce(func, s):
= s[0]
acc for x in s[1:]:
= func(acc, x)
acc return acc
Po domače: reduce
pokliče funkcijo na prvih dveh
elementih, nato na rezultatu tega klica in tretjem elementu, nato na
rezultatu tega klica in četrtem elementu... Spremenljivko
acc
pa smo poimenovali po njeni vlogi: akumulator.
Če vemo, kaj so iteratorji in kaj počne next
, znamo bolj
natančno (če ne, pa nič narobe, tudi gornje je dovolj dobro za
razumevanje, ki ga potrebujemo za uporabo funkcije):
def reduce(func, s, acc=None):
= iter(s)
t if acc is None:
= next(t)
acc
for x in t:
= func(acc, x)
acc return acc
Z reduce
se da početi zanimive stvari. Pripravimo si
nekaj funkcij (ki bi lahko bile tudi lambde, ampak recimo, da jih ne
znamo pisati).
def sestej(a, b):
return a + b
def zmnozi(a, b):
return a * b
def vrni_vecjega(a, b):
if a > b:
return a
else:
return b
def oba_resnicna(a, b):
return a and b
Pripravimo si še priložnostni seznam števil.
= [4, 2, 6, 3] s
Z reduce
lahko zdaj izračunamo vsoto elementov
seznama
reduce(sestej, s)
15
produkt
reduce(zmnozi, s)
144
in poiščemo največji element
reduce(vrni_vecjega, s)
6
mimogrede pa še 10!, se pravi produkt števil do 10
reduce(zmnozi, range(1, 11))
3628800
Če imamo seznam True
-jev in False
-ov, lahko
z reduce
izračunamo njegovo konjunkcijo (and
prek vseh elementov`).
reduce(oba_resnicna, [True, True, True, True, True])
True
reduce(oba_resnicna, [True, True, True, False, True])
False
Imenitna reč, problem je le, da se nam teh funkcij ne da definirati
vnaprej, Pythonove lambde, s katerimi lahko funkcijo definiramo kar
sproti, znotraj klica reduce
, pa so zelo kilave in tudi
nikoli ne bodo drugačne kot kilave.
Funkcije map
, filter
in reduce
- pri čemer se slednja v drugih jezikih bolj pogosto kot ne imenuje
fold
- so osnovni elementi funkcijskega
programiranja.