Funkcionální programování v Pythonu
Jedna z (volnějších) definic funkcionálního programování říká, že pokud nahradíme v kódu volání funkce výsledky tohoto volání, je program (jazyk) funkcionální. Jinými slovy, funkcionální programování znamená, že volaní funkce pouze produkuje výstup, nemá žádné vedlejší účinky a ani nemění její interní stav. Funkcionální program je v podstatě rekurzivním vyhodnocením výrazů. Mezi funkcionální jazyky patří např. Haskell, Clojure, Lisp nebo Erlang. Funkcionální programování se řadí mezi deklarativní programovací techniky (kam patří např. i SQL) a je téměř opakem objektově orientovaného programování, kde volání metod mění stav objektů. Python je spíše objektové orientovaný jazyk, nicméně je v něm možné používat technik funkcionálního programování a jednodušše tyto dva odlišné způsoby programování kombinovat.
A proč vlastně programovat funkcionálně? Jednoduchá, či spíše zjednodušující, odvěď je vyhnout se vedlejším efektům. To má své výhody, předvším jednoduchochou paralelizaci a vyhýbání se chybám typu přiřazení špatné hodnoty do proměnné. Zatím to ovšem vypadá, že čistě funkcionální programování nepřináší až tak velký užitek, neboť mezi programátory není příliš rozšířené. Vhodně použité funkcionální prvky v jinak procedurálním, objektově orientovaném jazyku mohou být velice užitečné. A toto nám Python poměrně dobře umožňuje.
Více např. na http://docs.python.org/3/howto/functional.html
Základní kameny funkcionálního programování¶
- First-class functions, anonymní (lambda) funkce
- Nemění se stav (programu)
- Immutable data
- Closure (uzávěr)
- Rekurze
- Řady
- ...
Podpora v Pythonu¶
- Funkce
map
,reduce
,filter
- Zkrácené logické výrazy
- Generátorová notace (pro tuple, list, set, dict)
- Iterátory
- Moduly
operator
,itertools
,functools
Pojďme se na to podívat prakticky.
Vyhýbáme se blokům if - else blokům a smyčkám¶
Tento kód:
if <cond1>:
func1()
elif <cond2>:
func2()
else:
func3()
je ekvivalentní
(<cond1> and func1()) or (<cond2> and func2()) or (func3())
Příklad:
def pr(s):
return s
x = 3
res = (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))
print(res)
x = 2
res = (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))
print(res)
Pozor: v tomto příkladu může dojít k nesprávnému vyhodnocení -- přijdete na to v jakém případě? (Nápověda: souvisí to s funkcí pr, resp. její návratovou hodnotou.) Lepší je proto použít podmíněné výrazy (conditional expression) (ternární logické operátory).
While smyčka může být nahrazena rekurzí:
n = 1
while n < 5:
n += 1
print("n = {}".format(n))
print("výsledek je {}".format(n))
# to samé rekurzivně
def get_n(n=1):
n += 1
print(n)
# použijeme podmíněný výraz
return n if n >= 5 else get_n(n)
print("výsledek je {}".format(get_n()))
First-class funkce, uzávěry, lambda¶
Pokud je možné funkce používat ekvivalentně jako proměnné, např. předávat je jako parametry funkcím nebo vracet funkce jako výsledek jiné funkce, mluvíme o first-class funkcích. S tímto konceptem se vážou i anonymní neboli lambda funkce. Opět to ukážeme na příkladech.
# definujeme funkci foo
def foo(x):
return bool(x)
# a přiřadíme do proměnné boo
boo = foo
def simply_apply(func, val):
# v proměnné func předpokládáme funkci
# simply_apply je "higher-order function"
print("the result is: %s" % str(func(val)))
# nyní použijeme funkci simply_apply pro vyhodnocení jiné funkce
# (konkrétně funkce foo, kterou máme v proměnné boo
simply_apply(boo, ()) # všiměte si i druhého argumentu
Funkci lze vytvořit v rámci jiné funkce a vrátit jako její výsledek. Tato funkce může obsahovat i uzávěr (closure), který zachová aktuální kontext.
def calculations(a, b):
# tady se vytvoří uzávěr, aktuální hodnoty a, b se uchovávají ve funkci add
def add():
return a + b
return add
f = calculations(1, 2)
print(f())
f = calculations(3, 2)
print(f())
Vytvořená funkce add
může mít i parametr.
def calculations(a, b):
def add(x):
return x * (a + b)
return add
f12 = calculations(1, 2)
f34 = calculations(3, 4)
print(f12(2))
print(f34(2))
Pro vyvoření nové funkce můžeme také použít anonymní (lambda) funkci.
def calculations(a, b):
return a, b, lambda x: x * (a + b)
a, b, f = calculations(1, 2)
aa, bb, ff = calculations(3, 4)
print(f(2))
print(ff(2))
Higher ordered functions (neboli funkcionály) přijímají jako parametr funkci.
def calc(f):
return lambda x: f(x + 1)
foo = calc(lambda x: x**2)
print(foo)
# výsledná funkce je (x + 1)**2
print(foo(2))
Konec hraní, používáme závorky, map, reduce, filter¶
Jak už jsme ukázali dříve, Python obsahuje koncept iterátorů, který je uplatněn pro vestavěné kontejnery, jako jsou list
, tuple
, dict
, set
, a objevuje se i u datových toků (stream), tedy např. souborů. Dále Python obsahuje generátory a generátorovou notaci. Iterátory, jakožto metody objektů, mohou ovšem měnit vnitřní stav, proto úplně nezapadají do funkcionálního stylu programování.
Generátorová notace¶
Pomocí závorek a několika klíčových slov for
a in
, případně také if
, můžeme jednoduše vytvářet kontejnery.
# list pomocí [ ... ]
[i+1 for i in [0, 1, 2, 3]]
# generator (nikoli tuple) pomocí ( ... )
(i+1 for i in [0, 1, 2, 3])
# generátor je iterable
for i in (i for i in range(4) if i % 2):
print(i)
# dict se tvoří pomocí { ... }
{i: i+1 for i in range(5)}
# a set také pomocí { ... }
{k for k in {i: i+1 for i in range(5)}.keys()}
map -- reduce (-- filter)¶
Map -- reduce princip je velice rozšířený ve funkcionálním programování a hojně se také využívá v (paralelním) zpracování dat, především pokud je jich mnoho. V Pythonu máme funkce map
a reduce
, které moho být aplikovány na jakékoli iterable obejkty.
-
map(function, object)
- aplikuje funkci function postupně na prvky object (pomocí iterací). function musím mít jeden argument (další mohou být nepovinné). Vrátí iterátor výsledků --map
objekt. -
functools.reduce(function, object)
- aplikuje funkci kumulativně na dva prvky obejktu, výsledkem je jedna hodnota podledního volání funkce. Reduce jde zapsat rekurzivně jako f[1] = function(object[0], object[1]), f[n+1] = function[f[n], object[n+1]). -
filter(function, object)
vrátí elementy, pro které je funkce True.
Ukážeme si to na příkladech.
from operator import ifloordiv
# aplikujeme funkci str
print(map(str, range(10)))
# aplikujee lambda funkci, která vrací celočíselné dělení 3
print(map(lambda x: ifloordiv(x, 3), range(10)))
from operator import add
from functools import reduce
# teď "zredukujeme" předchozí výsledky pomocí add (sčítání)
print(reduce(add, map(str, range(10))))
print(reduce(add, map(lambda x: ifloordiv(x, 3), range(10))))
# čísla dělitelná třemi
print(filter(lambda x: (x % 3) == 0, range(10)))
Většinou se v Python komunitě doporučuje používat závorky neboli generátorovou notaci:
- místo
map
použijeme(function(x) for x in object)
- místo
filter
máme(x for x in object if function(x))
- místo reduce můžeme v mnoha případech použít vestavěné
sum
,all
,any
print([str(x) for x in range(10)])
print([x for x in range(10) if (x % 3) == 0])
Cvičení¶
- Naprogramujte generátorovou funkci
slowmap(func, iterable, speed=1)
, která bude dělat to samé jakomap(func, iterable)
, ale mezi každou iterací bude čekat pomocí funkcetime.sleep
1/speed
vteřin. - Vyzkoušejte pomocí
slowmap
aplikovat lambda funkci, která vrací třetí mocninu argumentu, narange(10)
. Výsledek nakonec uložte do proměnné typulist
. - Napište funkci
pown(n)
, která bude vracet funkci vracející n-tou mocninu argumentu. Tj. např.pown(3)(2)
vrátí výsledek8
. Tuto funkci použijte místo lambda funkce v části 2.
Komentáře
Comments powered by Disqus