Obsah
1. Podpora funkcionálního programování v Pythonu a knihovna functools (3. část)
2. Funkce vyššího řádu partialmethod
3. Třída s metodou s parametry
4. Metody enable a disable vzniklé transformací set_enabled
5. Další příklad použití funkce partialmethod – doplnění většího množství parametrů při transformaci
6. Cache pro výsledky čistých funkcí
7. Klasický výpočet Fibonacciho posloupnosti rekurzivní funkcí
8. LRU cache pro výsledky Fibonacciho posloupnosti pro nejčastěji použité vstupy
9. Přečtení informací o využití LRU cache
10. Programové vymazání LRU cache
11. Cache pro hodnotu vlastnosti objektu
12. Použití standardního dekorátoru @property
13. Použití dekorátoru @cached_property
14. Vygenerování metod s implementací relačních operátorů (total ordering)
16. Ukázka dekorátoru aplikovaného na funkci
17. Dvojice dekorátorů aplikovaných na jednu funkci
18. Praktické použití dekorátoru – měření doby trvání funkce označené dekorátorem
19. Repositář s demonstračními příklady
1. Podpora funkcionálního programování v Pythonu a knihovna functools (3. část)
Na předchozí dva články o podpoře funkcionálního programování v jazyce Python [1][2] dnes navážeme. Popíšeme si další funkce a dekorátory, které nalezneme ve standardním balíčku functools, zejména funkci partialmethod a dekorátory @lru_cache, @cached_property a @total_ordering. Společně s předchozím článkem jsme se tak věnovali prakticky celému obsahu tohoto potenciálně velmi užitečného balíčku:
Symbol | Verze Pythonu | Popsáno |
---|---|---|
wraps | 2.5 | |
update_wrapper | 2.5 | |
partial | 2.5 | druhý článek, kapitola 9 |
reduce | 3.0 | první článek, kapitola 17 |
lru_cache | 3.2 | dnešní část, kapitola 6 |
total_ordering | 3.2 | dnešní část, kapitola 14 |
cmp_to_key | 3.2 | |
partialmethod | 3.4 | dnešní část, kapitola 2 |
singledispatch | 3.4 | |
cached_property | 3.8 | dnešní část, kapitola 11 |
singledispatchmethod | 3.8 | |
cache | 3.9 |
2. Funkce vyššího řádu partialmethod
V předchozím článku jsme si poměrně dopodrobna popsali funkci vyššího řádu nazvanou partial, která nějakou funkci s obecně n parametry transformovala na jinou funkci s obecně n-m parametry tak, že zbývající parametry (je jich m, typicky však 1) již byly doplněny na nějakou hodnotu. Připomeňme si následující (poněkud umělý) příklad:
from functools import partial def mul(x, y): return x * y print(mul(6, 7)) print() doubler = partial(mul, 2) for i in range(11): print(i, doubler(i))
V balíčku functools kromě partial nalezneme i podobně koncipovanou funkci partialmethod, která ovšem – jak její název správně napovídá – bude použitelná pro transformaci metody, tedy takových funkcí, jejichž prvním argumentem je self (a na něž se aplikují určitá pravidla viditelnosti atd.). Funkce partialmethod je potenciálně velmi užitečná, jak ostatně uvidíme v dalším textu.
3. Třída s metodou s parametry
Ještě předtím, než si ukážeme způsob použití funkce partialmethod si ukažme jednoduchou třídu, jejíž instance obsahují atribut _enabled. Tento atribut se nastavuje metodou pojmenovanou set_enabled a jak je z názvu atributu zřejmé (i bez uvedení typové deklarace), jsou hodnotami tohoto atributu pravdivostní hodnoty:
class Foo: def __init__(self): self._enabled = False def set_enabled(self, state): self._enabled = state def __str__(self): return "Foo that is " + ("enabled" if self._enabled else "disabled") foo = Foo() print(foo)
Příklad použití:
Foo that is disabled
Samozřejmě si můžeme bez problémů otestovat i vliv volání metody set_enabled (tedy tolik zatracovaného setteru) na stav objektu:
class Foo: def __init__(self): self._enabled = False def set_enabled(self, state): self._enabled = state def __str__(self): return "Foo that is " + ("enabled" if self._enabled else "disabled") foo = Foo() print(foo) foo.set_enabled(True) print(foo) foo.set_enabled(False) print(foo)
Výsledkem spuštění tohoto skriptu budou následující zprávy vypsané na terminál:
Foo that is disabled Foo that is enabled Foo that is disabled
4. Metody enable a disable vzniklé transformací set_enabled
Volání:
foo.set_enabled(True) foo.set_enabled(False)
je ve skutečnosti poněkud neohrabané a hodí se jen ve chvíli, kdy se metoda volá s nějakým výrazem a nikoli s konstantou. Samozřejmě si můžeme vytvořit pomocné metody enable a disable, a to zcela klasickým způsobem – budeme z nich volat původní metodu set_enabled. Ovšem právě v tomto případě je mnohem elegantnější použití partialmethod, která za nás hodnotu parametru doplní automaticky. Dvě nové metody do třídy Foo lze tedy přidat i tak, jak je to naznačeno na obou podtržených řádcích:
from functools import partialmethod class Foo: def __init__(self): self._enabled = False def set_enabled(self, state): self._enabled = state enable = partialmethod(set_enabled, True) disable = partialmethod(set_enabled, False) def __str__(self): return "Foo that is " + ("enabled" if self._enabled else "disabled") foo = Foo() print(foo) foo.enable() print(foo) foo.disable() print(foo)
Výsledek bude naprosto stejný, jako tomu bylo v předchozím příkladu:
Foo that is disabled Foo that is enabled Foo that is disabled
5. Další příklad použití funkce partialmethod – doplnění většího množství parametrů při transformaci
V předchozím článku jsme si řekli, že funkci partial můžeme použít i pro doplnění a zapamatování většího množství parametrů. Totéž ovšem platí i pro partialmethod, což znamená, že například můžeme transformovat metodu move_to ze třídy Point se třemi parametry (self, x a y) na novou metodu pouze s parametrem self, která přesune příslušný bod do počátku souřadného systému. Výsledek může vypadat následovně:
from functools import partialmethod class Point: def __init__(self): self._x = 0 self._y = 0 def move_to(self, x, y): self._x = x self._y = y to_origin = partialmethod(move_to, 0, 0) def __str__(self): return f"Point[{self._x}, {self._y}]" point = Point() print(point) point.move_to(1, 2) print(point) point.to_origin() print(point)
Samozřejmě si můžeme otestovat, jak se bude výsledný skript chovat po svém spuštění:
Point[0, 0] Point[1, 2] Point[0, 0]
6. Cache pro výsledky čistých funkcí
Připomeňme si, že za čisté funkce, resp. čistě funkcionální funkce, považujeme takové funkce, jejichž výsledná hodnota záleží pouze na hodnotách parametrů a nikoli na nějakém vnitřním stavu, hodnotách externích proměnných či například výsledku přístupu k nějakým prostředkům se stavem (databáze, soubory, síťové rozhraní atd.). Jenže když se nad takovými funkcemi zamyslíme, zjistíme, že je vlastně (alespoň teoreticky) možné nahradit volání takové funkce nějakou formou mapování, resp. slovníku, kde klíči slovníku budou kombinace hodnot parametrů funkce a hodnotami budou návratové hodnoty funkce. To je pochopitelně pro většinu funkcí nepraktické, zejména s ohledem na obrovské množství kombinace hodnot parametrů, s nimiž se může funkce volat.
Nicméně mnohé funkce jsou často volány se stejnými kombinacemi parametrů. A takové funkce je možné opatřit (zabalit) vhodnou formou cache pro nejčastěji vyžadované výsledky. Opět se jedná o funkcionalitu dostupnou díky standardnímu balíčku functools.
7. Klasický výpočet Fibonacciho posloupnosti rekurzivní funkcí
Jako ukázku vhodnosti či v některých případech nevhodnosti použití cache pro funkce si ukažme, jak lze implementovat výpočet Fibonacciho posloupnosti rekurzivním výpočtem. Jedná se o klasický „školní“ příklad, který pouze doplníme o změření času trvání výpočtu. Pro zajímavost budeme výpočet desetkrát opakovat pro stejný vstup:
from time import time def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) max_n = 40 for _ in range(10): start = time() result = fib(max_n) end = time() print(result, end - start)
I pro relativně nízkou vstupní hodnotu (40) bude výpočet trvat poměrně dlouho (minimálně při použití CPythonu):
102334155 27.4883930683136 102334155 27.809394598007202 102334155 27.91700768470764 102334155 28.687997817993164 102334155 30.422297954559326 102334155 28.643412351608276 102334155 28.83504009246826 102334155 28.57629656791687 102334155 28.623551607131958 102334155 28.74962282180786
8. LRU cache pro výsledky Fibonacciho posloupnosti pro nejčastěji použité vstupy
Nyní předchozí skript nepatrně upravíme tak, aby se použila cache pro nejčastěji požadované výsledky (resp. naopak – ty nejméně často vyžadované výsledky jsou z cache odstraňovány). Nejprve importujeme funkci lru_cache z balíčku functools a následně před definici funkce pro výpočet Fibonacciho posloupnosti přidáme dekorátor @lru_cache. Žádné další změny nebudou provedeny:
from time import time from functools import lru_cache @lru_cache def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) max_n = 40 for _ in range(10): start = time() result = fib(max_n) end = time() print(result, end - start)
Nyní ovšem budou výpočty probíhat mnohem rychleji! Pro další opakování výpočtu pro daný vstup maxn = 40 je to zřejmé – LRU cache již má uložen příslušný výsledek. Ovšem již první volání této funkce bude rychlejší, a to z toho důvodu, že se pamatují výsledky z jedné větve rekurzivního výpočtu:
102334155 0.0005862712860107422 102334155 4.76837158203125e-07 102334155 7.152557373046875e-07 102334155 4.76837158203125e-07 102334155 4.76837158203125e-07 102334155 4.76837158203125e-07 102334155 4.76837158203125e-07 102334155 4.76837158203125e-07 102334155 4.76837158203125e-07 102334155 2.384185791015625e-07
9. Přečtení informací o využití LRU cache
Samotná LRU cache (least recently used) nám může poskytnout několik informací o počtu zapamatovaných výsledků, využití cache atd. Tyto informace lze získat zavoláním fib.cache_info(), což je samo o sobě zajímavé, protože nám to prozrazuje, že se jedná a atribut funkce obalené dekorátorem (podrobnosti si řekneme za chvíli). Zkusme si tedy výpočet Fibonacciho posloupnosti upravit tak, abychom před každým výpočtem zjistili a vypsali informaci o LRU cache:
from time import time from functools import lru_cache @lru_cache def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) max_n = 40 for _ in range(10): print(fib.cache_info()) start = time() result = fib(max_n) end = time() print(result, end - start)
Získané výsledky prozrazují, jak je cache využita:
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) 102334155 0.0011126995086669922 CacheInfo(hits=38, misses=41, maxsize=128, currsize=41) 102334155 1.1920928955078125e-06 CacheInfo(hits=39, misses=41, maxsize=128, currsize=41) 102334155 9.5367431640625e-07 CacheInfo(hits=40, misses=41, maxsize=128, currsize=41) 102334155 9.5367431640625e-07 CacheInfo(hits=41, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07 CacheInfo(hits=42, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07 CacheInfo(hits=43, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07 CacheInfo(hits=44, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=45, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07 CacheInfo(hits=46, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07
10. Programové vymazání LRU cache
LRU cache je možné v případě potřeby explicitně vymazat. Příkladem by mohla být funkce používaná ve výpočtu na více místech, ovšem každý výpočet začíná s odlišnými vstupními parametry. V případě, že se nechceme spoléhat na „LRU algoritmus“, můžeme před takovým výpočtem zavolat metodu cache_clear(), která cache vymaže:
fib.cache_clear()
Podívejme se nyní na poněkud umělý příklad, v němž opět počítáme n-tý prvek Fibonacciho posloupnosti s využitím LRU cache, ovšem po proběhnutí určitého počtu volání funkce fib cache explicitně vymažeme:
from time import time from functools import lru_cache @lru_cache def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) max_n = 40 for i in range(20): if i % 5 == 0: fib.cache_clear() print(fib.cache_info()) start = time() result = fib(max_n) end = time() print(result, end - start)
Tento skript kromě výsledků výpočtů a času, který byl stráven výpočty, zobrazuje informaci o využití cache:
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) 102334155 0.0005662441253662109 CacheInfo(hits=38, misses=41, maxsize=128, currsize=41) 102334155 7.152557373046875e-07 CacheInfo(hits=39, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=40, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=41, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) 102334155 1.5497207641601562e-05 CacheInfo(hits=38, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07 CacheInfo(hits=39, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=40, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07 CacheInfo(hits=41, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) 102334155 1.430511474609375e-05 CacheInfo(hits=38, misses=41, maxsize=128, currsize=41) 102334155 4.76837158203125e-07 CacheInfo(hits=39, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=40, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=41, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) 102334155 1.4066696166992188e-05 CacheInfo(hits=38, misses=41, maxsize=128, currsize=41) 102334155 7.152557373046875e-07 CacheInfo(hits=39, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=40, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07 CacheInfo(hits=41, misses=41, maxsize=128, currsize=41) 102334155 2.384185791015625e-07
11. Cache pro hodnotu vlastnosti objektu
Výše uvedený dekorátor @lru_cache (což je, jak uvidíme dále, ve skutečnosti vhodně zapsaná funkce vyššího řádu, resp. uzávěr), se používá skutečně pro implementaci cache, jejíž obsah se může postupem času měnit na základě toho, s jakými parametry se „cachovaná“ funkce volá. Ve standardní knihovně functools ovšem nalezneme ještě jeden dekorátor s částečně podobným významem a taktéž s podobným jménem.
Tento dekorátor se jmenuje @cached_property a používá se v těch místech programového kódu, kde skutečně vyžadujeme vlastnost (property), která se ovšem (na rozdíl od běžné vlastnosti) vypočte jen jedenkrát a při každém dalším přístupu k ní (samozřejmě je myšleno čtení) se přímo vrátí zapamatovaná hodnota. Tato cache tedy slouží pro zapamatování jediné hodnoty a nemusí být založena na použití algoritmu „LRU“. Taktéž může sloužit pro odložení výpočtu, který by se jinak prováděl při konstrukci a inicializaci objektu.
12. Použití standardního dekorátoru @property
Ještě než si popíšeme výše zmíněný dekorátor @cached_property, podívejme se pro úplnost na použití standardního dekorátoru @property. Tento dekorátor se zapisuje před metodu, jejíž název později použijeme pro čtení vlastnosti objektu – a to bez toho, aby se metoda explicitně volala. Metoda je tedy dekorátorem transformována do odlišně se chovající hodnoty:
class Foo: @propety def bar(self): return something x = Foo() print(x.bar)
Opět se podívejme na příklad založený na výpočtu Fibonacciho posloupnosti. V konstruktoru si pouze zapamatujeme hodnotu n a následně vypočteme Fib(n) jen ve chvíli, kdy je to skutečně zapotřebí, tj. při čtení vlastnosti value (a pokud se tato vlastnost nebude číst, výpočet se neprovede vůbec):
from time import time class FibonacciNumber: def __init__(self, n): self._n = n @property def value(self): return FibonacciNumber.compute(self._n) @staticmethod def compute(n): if n < 2: return n return FibonacciNumber.compute(n-1) + FibonacciNumber.compute(n-2) f = FibonacciNumber(40) for _ in range(10): start = time() result = f.value end = time() print(result, end - start)
Jak pravděpodobně správně tušíte, bude každé čtení f.value znovu a znovu spouštět dlouhotrvající výpočet:
102334155 32.67074537277222 102334155 34.46800398826599 102334155 31.666145086288452 102334155 32.48106145858765 102334155 33.00236797332764 102334155 33.22874307632446 102334155 33.054988384246826 102334155 33.02854561805725 102334155 32.91584897041321 102334155 33.35956025123596
13. Použití dekorátoru @cached_property
V případě, že namísto standardního dekorátoru @property použijeme dekorátor @cached_property, bude se program chovat odlišně – výpočet se provede pouze při prvním přístupu k vlastnosti value:
from time import time from functools import cached_property class FibonacciNumber: def __init__(self, n): self._n = n @cached_property def value(self): return FibonacciNumber.compute(self._n) @staticmethod def compute(n): if n < 2: return n return FibonacciNumber.compute(n-1) + FibonacciNumber.compute(n-2) f = FibonacciNumber(40) for _ in range(10): start = time() result = f.value end = time() print(result, end - start)
O tom, že bude výsledek výpočtu skutečně uložen do cache, se přesvědčíme snadno pohledem na výsledky získané po spuštění skriptu:
102334155 32.47180533409119 102334155 9.5367431640625e-07 102334155 2.384185791015625e-07 102334155 2.384185791015625e-07 102334155 0.0 102334155 2.384185791015625e-07 102334155 0.0 102334155 2.384185791015625e-07 102334155 0.0 102334155 0.0
Pro další urychlení výpočtu pochopitelně můžeme využít jak @cached_property, tak i @lru_cache, a to konkrétně následujícím způsobem:
from time import time from functools import cached_property, lru_cache class FibonacciNumber: def __init__(self, n): self._n = n @cached_property def value(self): return FibonacciNumber.compute(self._n) @staticmethod @lru_cache def compute(n): if n < 2: return n return FibonacciNumber.compute(n-1) + FibonacciNumber.compute(n-2) f = FibonacciNumber(40) for _ in range(10): start = time() result = f.value end = time() print(result, end - start)
Výsledky nyní budou vypočteny jen jedenkrát a navíc velmi rychle:
102334155 0.0005643367767333984 102334155 4.76837158203125e-07 102334155 2.384185791015625e-07 102334155 2.384185791015625e-07 102334155 0.0 102334155 0.0 102334155 2.384185791015625e-07 102334155 0.0 102334155 2.384185791015625e-07 102334155 2.384185791015625e-07
14. Vygenerování metod s implementací relačních operátorů (total ordering)
Velmi zajímavým dekorátorem, který nalezneme v balíčku functools, je dekorátor nazvaný @total_ordering. Používá se, resp. může se použít v deklaracích tříd, jejichž instance (tedy hodnoty) je možné nějakým způsobem jednoznačně uspořádat ve smyslu relace „menší než“, „rovno“ atd. Příkladem může být třída reprezentující sémantickou verzi major.minor, kde major i minor jsou celá čísla. Všechny možné verze zapsané tímto způsobem lze zajisté uspořádat a vzniká tedy potřeba implementace všech šesti relačních operátorů ==, !=, <, <=, > i >=. A právě v takové situaci je možné použít dekorátor @total_ordering, který dokáže příslušné realizace operátorů vygenerovat na základě znalosti pouze relace rovnosti a (například) „menší než“. Ostatně se podívejme na implementaci, kde dekorátor aplikujeme nikoli na metodu, ale na celou třídu:
from functools import total_ordering @total_ordering class Version: def __init__(self, major, minor): self._major = major self._minor = minor def _is_valid_version(self, other): return (hasattr(other, "_major") and hasattr(other, "_major")) def __eq__(self, other): if not self._is_valid_version(other): return NotImplemented return (self._major, self._minor) == \ (other._major, other._minor) def __lt__(self, other): if not self._is_valid_version(other): return NotImplemented return (self._major, self._minor) < \ (other._major, other._minor) v1 = Version(1, 0) v2 = Version(1, 2) v3 = Version(1, 2) v4 = Version(2, 1) print(v1==v2) print(v2==v3) print() print(v1<v2) print(v1<v4) print(v2<v4) print(v1>v2) print(v1>v4) print(v2>v4)
15. Dekorátory
V závěrečné části dnešního článku si na trojici demonstračních příkladů ukážeme základní způsoby použití takzvaných dekorátorů. Jedná se opět o funkcionální technologii, která nám umožňuje snadno „obalit“ volání nějaké funkce dalším kódem a vrátit výsledek jako novou funkci s přidanými vlastnostmi. Může to vypadat následovně:
def wrapper1(původní_funkce): def nová_funkce(): # nějaký kód původní_funkce() # nějaký kód return nová_funkce
Důležité je, že ono vlastní „obalení“ původní funkce je realizováno snadno zapamatovatelnou syntaxí – před definici funkce se na samostatný řádek zapíše jméno dekorátoru a jeho případné parametry:
@wrapper def hello(): print("Hello!")
Musíme si ovšem uvědomit, že se ve skutečnosti jedná pouze o syntaktický cukr a podobnou techniku lze použít i v případě, že by dekorátory v Pythonu neexistovaly.
16. Ukázka dekorátoru aplikovaného na funkci
Podívejme se nyní na velmi jednoduchý dekorátor, který původní (libovolnou) funkci obaluje tak, že před voláním původní funkce zobrazí řádek se znaky „-“ a po návratu z původní funkce opět vypíše řádek se znaky „-“. Kód tohoto příkladu vypadá následovně:
def wrapper1(function): def inner_function(): print("-" * 40) function() print("-" * 40) return inner_function @wrapper1 def hello(): print("Hello!") hello()
Pokud nyní tento příkladu spustíme, vypíše se na terminál následující trojice zpráv:
---------------------------------------- Hello! ----------------------------------------
Proč tomu tak je? Ve skutečnosti totiž nevoláme původní funkci hello, protože ta již pod tímto jménem neexistuje – byla totiž transformována do nové funkce, kterou si sice nemůžeme jednoduše vypsat ve formě zdrojového kódu, ale měla by vypadat zhruba takto:
def hello(): print("-" * 40) původní_hello()() print("-" * 40)
17. Dvojice dekorátorů aplikovaných na jednu funkci
Nyní si předchozí příklad upravme do nové podoby. Nejprve si nadefinujeme dvojici wrapperů („obalovačů“), které před i po zavolání původní funkce vypíšou na terminál buď řadu znaků „-“ nebo řadu znaků „=“:
def wrapper1(function): def inner_function(): print("-" * 40) function() print("-" * 40) return inner_function def wrapper2(function): def inner_function(): print("=" * 40) function() print("=" * 40) return inner_function
Poté si necháme původní funkci hello ztransformovat (tedy „obalit“) dvakrát, a to v tomto zapsaném pořadí:
@wrapper1 @wrapper2 def hello(): print("Hello!")
Výsledek bude vypadat takto:
---------------------------------------- ======================================== Hello! ======================================== ----------------------------------------
Co to znamená? Původní funkce hello byla nejdříve obalena do přibližně této podoby:
def novější_hello(): print("=" * 40) původní_hello()() print("=" * 40)
a potom došlo k dalšímu obalení:
def nejnovější_hello(): print("-" * 40) novější_hello()() print("-" * 40)
Úplný zdrojový kód tohoto demonstračního příkladu bude vypadat následovně:
def wrapper1(function): def inner_function(): print("-" * 40) function() print("-" * 40) return inner_function def wrapper2(function): def inner_function(): print("=" * 40) function() print("=" * 40) return inner_function @wrapper1 @wrapper2 def hello(): print("Hello!") hello()
18. Praktické použití dekorátoru – měření doby trvání funkce označené dekorátorem
V dnešním posledním demonstračním příkladu je ukázáno jedno z možných praktických použití dekorátoru. Bude se jednat o dekorátor, který nám umožní obalit nějakou funkci příkazy sloužícími pro měření doby jejího trvání. Samotný algoritmus je přitom triviální: zapamatujeme si časové razítko před spuštěním měřené funkce, měřenou funkci spustíme, vypočteme na základě nového časového razítka dobu trvání měřené funkce, kterou vytiskneme a nakonec vrátíme původní návratovou hodnotu z měřené funkce. Jedna z nejjednodušších implementací tohoto algoritmu může vypadat následovně:
# Original code: # https://pythonbasics.org/decorators/#Real-world-examples import time def measure_time(func): def wrapper(*arg): t = time.time() res = func(*arg) print("Function took " + str(time.time() - t) + " seconds to run") return res return wrapper @measure_time def tested_function(n): time.sleep(n) tested_function(1) tested_function(2)
Z výsledků získaných po spuštění tohoto skriptu vyplývá, že vše pracuje podle předpokladů (naměřený čas bude pochopitelně poněkud delší než zvolená jedna či dvě sekundy):
Function took 1.00141787529 seconds to run Function took 2.00236320496 seconds to run
19. Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si ukázali předminule, minule i dnes, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalovánu některou z podporovaných verzí Pythonu 3, žádné další balíčky nejsou zapotřebí):
20. Odkazy na Internetu
- functools — Higher-order functions and operations on callable objects
https://docs.python.org/3/library/functools.html - Functional Programming HOWTO
https://docs.python.org/3/howto/functional.html - Functional Programming in Python: When and How to Use It
https://realpython.com/python-functional-programming/ - Functional Programming With Python
https://realpython.com/learning-paths/functional-programming/ - Awesome Functional Python
https://github.com/sfermigier/awesome-functional-python - Currying
https://en.wikipedia.org/wiki/Currying - Currying in Python – A Beginner’s Introduction
https://www.askpython.com/python/examples/currying-in-python - Fundamental Concepts in Programming Languages
https://en.wikipedia.org/wiki/Fundamental_Concepts_in_Programming_Languages - When should I use function currying?
https://stackoverflow.com/questions/24881604/when-should-i-use-function-currying - Toolz
https://github.com/pytoolz/toolz/tree/master - Coconut: funkcionální jazyk s pattern matchingem kompatibilní s Pythonem
https://www.root.cz/clanky/coconut-funkcionalni-jazyk-s-pattern-matchingem-kompatibilni-s-pythonem/ - A HITCHHIKER'S GUIDE TO functools
https://ep2021.europython.eu/media/conference/slides/a-hitchhikers-guide-to-functools.pdf - Coconut aneb funkcionální nadstavba nad Pythonem (2.část)
https://www.root.cz/clanky/coconut-aneb-funkcionalni-nadstavba-nad-pythonem-2-cast/ - Knihovny pro zpracování posloupností (sekvencí) v Pythonu
https://www.root.cz/clanky/knihovny-pro-zpracovani-posloupnosti-sekvenci-v-pythonu/ - clj – repositář s knihovnou
https://github.com/bfontaine/clj - clj 0.1.0 – stránka na PyPi
https://pypi.python.org/pypi/clj/0.1.0 - Coconut: Simple, elegant, Pythonic functional programming
http://coconut-lang.org/ - coconut (Python package index)
https://pypi.python.org/pypi/coconut/ - Coconut Tutorial
http://coconut.readthedocs.io/en/master/HELP.html - Coconut FAQ
http://coconut.readthedocs.io/en/master/FAQ.html - Coconut Documentation
http://coconut.readthedocs.io/en/master/DOCS.html - Coconut na Redditu
https://www.reddit.com/r/Python/comments/4owzu7/coconut_functional_programming_in_python/ - Repositář na GitHubu
https://github.com/evhub/coconut - Object-Oriented Programming — The Trillion Dollar Disaster
https://betterprogramming.pub/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7 - Goodbye, Object Oriented Programming
https://cscalfani.medium.com/goodbye-object-oriented-programming-a59cda4c0e53 - So You Want to be a Functional Programmer (Part 1)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-1–1f15e387e536 - So You Want to be a Functional Programmer (Part 2)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-2–7005682cec4a - So You Want to be a Functional Programmer (Part 3)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-3–1b0fd14eb1a7 - So You Want to be a Functional Programmer (Part 4)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-4–18fbe3ea9e49 - So You Want to be a Functional Programmer (Part 5)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-5-c70adc9cf56a - So You Want to be a Functional Programmer (Part 6)
https://cscalfani.medium.com/so-you-want-to-be-a-functional-programmer-part-6-db502830403 - Why Programmers Need Limits
https://cscalfani.medium.com/why-programmers-need-limits-3d96e1a0a6db - Infographic showing code complexity vs developer experience
https://twitter.com/rossipedia/status/1580639227313676288 - Python's reduce(): From Functional to Pythonic Style
https://realpython.com/python-reduce-function/ - What is the problem with reduce()?
https://stackoverflow.com/questions/181543/what-is-the-problem-with-reduce - The fate of reduce() in Python 3000
https://www.artima.com/weblogs/viewpost.jsp?thread=98196 - Reading 16: Map, Filter, Reduce
http://web.mit.edu/6.031/www/sp22/classes/16-map-filter-reduce/ - Currying
https://sw-samuraj.cz/2011/02/currying/ - Používání funkcí v F#
https://docs.microsoft.com/cs-cz/dotnet/fsharp/tutorials/using-functions - Funkce vyššího řádu
http://naucte-se.haskell.cz/funkce-vyssiho-radu - Currying (Wikipedia)
https://en.wikipedia.org/wiki/Currying - Currying (Haskell wiki)
https://wiki.haskell.org/Currying - Haskell Curry
https://en.wikipedia.org/wiki/Haskell_Curry - Moses Schönfinkel
https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel - ML – funkcionální jazyk s revolučním typovým systémem
https://www.root.cz/clanky/ml-funkcionalni-jazyk-s-revolucnim-typovym-systemem/ - Funkce a typový systém programovacího jazyka ML
https://www.root.cz/clanky/funkce-a-typovy-system-programovaciho-jazyka-ml/ - Curryfikace (currying), výjimky a vlastní operátory v jazyku ML
https://www.root.cz/clanky/curryfikace-currying-vyjimky-a-vlastni-operatory-v-jazyku-ml/ - Primer on Python Decorators
https://realpython.com/primer-on-python-decorators/ - Python Decorators
https://www.programiz.com/python-programming/decorator - PythonDecorators (Python Wiki)
https://wiki.python.org/moin/PythonDecorators