Obsah
1. Dekorátory funkcí a metod nabízené knihovnou funcy
3. Dekorátory zjednodušující reakce na výjimky
5. Příklady použití dekorátoru @silent
6. Zachycení výjimky, která je ve funkci vyhazována explicitně
8. Příklady použití dekorátoru @ignore
9. Vrácení zvolené hodnoty z funkce, ve které byla výjimka zachycena
10. Rozdílné chování pro různé typy výjimek
11. Transformace výjimky na jiný typ výjimky (s odlišnou zprávou) dekorátorem @reraise
12. Ukázky použití dekorátoru @reraise
13. Reakce na větší množství typů výjimek
14. Využití informací z původní výjimky v nové výjimce
15. Pokusy o opakované spuštění funkce při vyhození výjimky – dekorátor @retry
16. Základní způsob použití dekorátoru @retry
17. Specifikace typu zachytávaných výjimek
18. Exponenciálně vzrůstající hodnota timeout mezi jednotlivými voláními funkce
19. Repositář s demonstračními příklady
1. Dekorátory funkcí a metod nabízené knihovnou funcy
V již sedmé části seriálu, v němž se zabýváme funkcionálním programováním v Pythonu (ano, i to je do jisté míry možné) si ukážeme další mnohdy velmi užitečné dekorátory funkcí a metod nabízených knihovnou funcy. S některými dekorátory jsme se již v tomto seriálu setkali, takže si je jen ve stručnosti připomeňme. Posléze si popíšeme další (prozatím nezmíněné) dekorátory, jejichž využitím lze zdrojový kód zjednodušit popř. zpřehlednit:
Dekorátor | Kapitola | Stručný popis |
---|---|---|
@decorator | 2 | deklarace funkce, ze které se vytvoří dekorátor |
@silent | 4 | ve funkci s dekorátorem se budou ignorovat všechny výjimky |
@ignore | 7 | ve funkci s dekorátorem se budou ignorovat vybrané výjimky + možnost zadat návratovou hodnotu funkce |
@reraise | 11 | transformace vyhozené výjimky na jiný typ výjimky (s odlišnou zprávou) |
@retry | 15 | pokusy o opakované spuštění funkce při vyhození výjimky |
2. Dekorátor @decorator
V první řadě se jedná o dekorátor nazvaný přímočaře @decorator. Jedná se o dekorátor, který je možné s výhodou využít pro deklaraci vlastního dekorátoru (vytvořeného z funkce), což může do značné míry zjednodušit zápis výsledného programu. Použití tohoto dekorátoru je ve skutečnosti až triviálně snadné, protože vlastně namísto původního wrapperu píšeme jen deklaraci „obalovací“ funkce. Zkusme si tedy převést funkci nazvanou wrapper na skutečný dekorátor, který vzápětí použijeme:
from funcy import decorator @decorator def wrapper1(function): print("-" * 40) function() print("-" * 40) @wrapper1 def hello(): print("Hello!") hello()
Pro úplnost se ještě podívejme na další příklad, který jsme si taktéž již ukázali v předchozích článcích. Jedná se o několikanásobnou aplikaci více dekorátorů, což znamená, že původní funkce je transformována do jiné funkce a ta je dále transformována do další funkce (atd.). Připomeňme si, že zdrojový kód tohoto příkladu (bez použití @decorator) vypadal 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()
Opět se podívejme na způsob zjednodušení celé struktury tohoto příkladu do podoby založené na použití dekorátoru @decorator. Přepis je přímočarý:
from funcy import decorator @decorator def wrapper1(function): print("-" * 40) function() print("-" * 40) @decorator def wrapper2(function): print("=" * 40) function() print("=" * 40) @wrapper1 @wrapper2 def hello(): print("Hello!") hello()
Naposledy se podívejme na použití dekorátoru @decorator z balíčku funcy. Tentokráte upravíme příklad s dekorátorem, který dokáže změřit délku trvání nějaké operace v uživatelem specifikované funkci. Původní zápis (opět bez použití @decorator) vypadal 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)
Přepis do stručnější a čitelnější podoby s využitím dekorátoru @decorator:
from funcy import decorator import time @decorator def measure_time(func): t = time.time() res = func() print("Function took " + str(time.time() - t) + " seconds to run") return res @measure_time def tested_function(n): print(f"Sleeping for {n} seconds") time.sleep(n) tested_function(1) tested_function(2)
3. Dekorátory zjednodušující reakce na výjimky
V knihovně Funcy nalezneme i několik dekorátorů, které dokážou modifikovat způsob zpracování výjimek, které vzniknou (přesněji řečeno jsou vyhozeny) ve funkci obalené dekorátorem. To vlastně znamená, že u takto označených funkcí je způsob zpracování výjimek naznačen i bez toho, abychom museli studovat kód takové funkce (což nám stejně nemusí pomoci, protože výjimky mohou být vyhozeny z interně volaných funkcí a metod). V navazujících kapitolách si ukážeme dekorátory určené pro zachycení a zahození všech výjimek, dále dekorátor, který dokáže zachytit pouze určené výjimky a můžeme v něm specifikovat i návratovou hodnotu z funkce (pokud výjimka nastala) a nakonec i dekorátor, který zachycené výjimky dokáže transformovat na jiný typ výjimky (takže například dokážeme všechny označené výjimky transformovat na výjimku typu InternalServerError, která je následně vrácena klientovi v HTTP odpovědi).
4. Dekorátor @silent
Prvním novým dekorátorem, se kterým se v dnešním článku seznámíme, je dekorátor nazvaný @silent. Použití tohoto dekorátoru je ve skutečnosti velmi jednoduché, protože slouží k zachycení a tichému zahození (odtud jeho jméno) jakékoli výjimky, která může v dekorované funkci nastat. Není tedy nutné ve funkci vytvářet řídicí strukturu try-except s prázdným blokem except.
5. Příklady použití dekorátoru @silent
Dekorátor @silent se používá velmi snadno. Podívejme se například na následující funkci, která se pokusí vydělit své dva operandy a vrátit výsledek podílu. Tato funkce může (pochopitelně) vyhodit výjimku, pokud se dělí nulou:
def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Výsledek je očekávatelný:
0.5 Traceback (most recent call last): File "silent_1.py", line 6, in <module> print(divide(1, 0)) File "silent_1.py", line 2, in divide return a/b ZeroDivisionError: division by zero
Nyní funkci pro výpočet podílu „odekorujeme“ s využitím @silent:
from funcy import silent @silent def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Výsledky budou odlišné – při dělení nulou se vrátí hodnota None:
0.5 None
Podobně se můžeme pokusit o deklaraci funkce, která vypíše obsah zvoleného souboru. I v tomto případě může dojít k vyhození výjimky (resp. dokonce několika typů výjimek):
def cat(filename): with open(filename) as fin: print(fin.read()) cat("silent_3.py") cat("this_does_not_exists")
Druhý soubor neexistuje, takže dostaneme:
def cat(filename): with open(filename) as fin: print(fin.read()) cat("silent_3.py") cat("this_does_not_exists") Traceback (most recent call last): File "silent_3.py", line 7, in <module> cat("this_does_not_exists") File "silent_3.py", line 2, in cat with open(filename) as fin: FileNotFoundError: [Errno 2] No such file or directory: 'this_does_not_exists'
Pokud před hlavičku funkce přidáme dekorátor @silent, budou všechny výjimky zachyceny a funkce pouze vrátí hodnotu None (kterou stejně ignorujeme):
from funcy import silent @silent def cat(filename): with open(filename) as fin: print(fin.read()) cat("silent_3.py") cat("this_does_not_exists")
Výsledky:
def cat(filename): with open(filename) as fin: print(fin.read()) cat("silent_3.py") cat("this_does_not_exists")
6. Zachycení výjimky, která je ve funkci vyhazována explicitně
Samozřejmě je možné, že výjimka je v uživatelské funkci vyhazována explicitně (tj. s využitím klíčového slova raise), což je ukázáno na tomto jednoduchém příkladu:
def raise_exception(): raise Exception("foo") raise_exception()
Po spuštění skriptu dostaneme podle očekávání tento výstup:
Traceback (most recent call last): File "silent_5.py", line 5, in <module> raise_exception() File "silent_5.py", line 2, in raise_exception raise Exception("foo") Exception: foo
I takovou výjimku je možné pomocí dekorátoru @silent zachytit (což se může hodit například při ladění programu atd. – není nutné zasahovat do kódu funkce):
from funcy import silent @silent def raise_exception(): raise Exception("foo") raise_exception()
Tento program po svém spuštění nic nevypíše, pouze se korektně ukončí.
A nakonec si, nyní již pouze pro úplnost, ukažme chování dekorátoru @silent u funkce, která volá jinou uživatelskou funkci, která vyhodí výjimku:
from funcy import silent @silent def call_function_to_raise_exception(): raise_exception() def raise_exception(): raise Exception("foo") call_function_to_raise_exception()
I tento program po svém spuštění nic nevypíše, pouze se korektně ukončí (s návratovým kódem nastaveným na nulu).
7. Dekorátor @ignore
Dekorátor @silent, který byl popsaný v předchozích dvou kapitolách, nelze žádným způsobem řídit ani upravit jeho chování. Mnohdy ovšem potřebujeme ignorovat jen určitý typ výjimky nebo typy výjimek. A současně mnohdy potřebujeme, aby při vzniku (a zachycení) výjimky funkce vrátila nějakou předem zadanou hodnotu, nikoli pouze výchozí hodnotu None (typicky nám to bude vadit při zpracování proudu dat). A právě v těchto případech lze s výhodou použít další dekorátor, který je nazvaný @ignore. Tomuto dekorátoru se předává jeden či dva parametry. Prvním parametrem jsou typy výjimek, které se mají zachytit (v tomto případě je typ shodný se jménem třídy s implementací výjimky) a parametrem druhým pak hodnota, která se z funkce vrátí v případě, že došlo k vyhození a zachycení výjimky (registrovaného typu).
8. Příklady použití dekorátoru @ignore
Podobně jako jsme si ukázali způsoby použití dekorátoru @silent si v této kapitole ukážeme, jak lze využít sofistikovanější dekorátor nazvaný @ignore.
Zachycení výjimky typu Exception a od ní odvozených výjimek:
from funcy import ignore @ignore(errors=Exception) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Výsledky:
0.5 None
Pokus o zachycení odlišné výjimky, než je ZeroDivisionError:
from funcy import ignore @ignore(errors=IOError) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
V tomto případě není výjimka zachycena a „probublá“ výše:
0.5 Traceback (most recent call last): File "ignore_2.py", line 9, in <module> print(divide(1, 0)) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 43, in wrapper return func(*args, **kwargs) File "ignore_2.py", line 5, in divide return a/b ZeroDivisionError: division by zero
Zachycení pouze jediné výjimky, která nás při výpočtu zajímá, tedy výjimky vyhození při dělení nulou:
from funcy import ignore @ignore(errors=ZeroDivisionError) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Výsledky budou totožné s prvním příkladem z této kapitoly, tedy:
0.5 None
9. Vrácení zvolené hodnoty z funkce, ve které byla výjimka zachycena
A konečně si ukažme velmi užitečnou vlastnost, tedy vrácení zvolené hodnoty v případě, že je výjimka zachycena:
from funcy import ignore @ignore(errors=ZeroDivisionError, default=-1) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
V tomto případě se při vzniku výjimky vrátí hodnota –1:
0.5 -1
10. Rozdílné chování pro různé typy výjimek
Připomeňme si, že v parametru errors je možné dekorátoru @ignore předat seznam výjimek, které se mají zachytávat. Prozatím jsme namísto seznamu předávali pouze jeden typ výjimky, ovšem bez problémů je možné realizovat i následující funkci, v níž se zachytí výjimky typu ZeroDivisionError a/nebo TypeError (pokud předáme parametr typu, pro který není dělení realizovatelné):
from funcy import ignore @ignore(errors=[ZeroDivisionError, TypeError], default=-1) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0)) print(divide(None, 1))
Výsledky:
0.5 -1 -1
Pokud ovšem zachytáváme pouze ZeroDivisionError a nikoli již TypeError, bude výsledek podle očekávání odlišný:
0.5 -1 Traceback (most recent call last): File "ignore_5.py", line 10, in <module> print(divide(None, 1)) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 43, in wrapper return func(*args, **kwargs) File "ignore_5.py", line 5, in divide return a/b TypeError: unsupported operand type(s) for /: 'NoneType' and 'int'
Ve skutečnosti je možné na každou výjimku reagovat odlišně, tedy vrácením jiné hodnoty. To je potenciálně velmi užitečná vlastnost, takže se podívejme na realizaci takového programu. Je to vlastně triviální – použijeme dvojici dekorátorů:
from funcy import ignore @ignore(errors=ZeroDivisionError, default=0) @ignore(errors=TypeError, default=-1) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0)) print(divide(None, 1))
Výsledky by měly být následující:
0.5 0 -1
11. Transformace výjimky na jiný typ výjimky (s odlišnou zprávou) dekorátorem @reraise
Dalším dekorátorem, který v knihovně Funcy nalezneme a který souvisí se zpracováním výjimek, je dekorátor nazvaný @reraise. Tento dekorátor dokáže zachytit výjimku či výjimky specifikovaného typu (typů) a namísto nich vyhodit jiný typ výjimky. Kde se ovšem tato funkcionalita uplatní? Například ve chvíli, kdy potřebujeme klientovi odeslat odpověď přes HTTP protokol s korektním stavem (200 OK, 404 Not Found atd.). Můžeme tedy zachytit ostatní typy výjimek (IOError atd.) a nechat si je přetransformovat právě do výjimky, která reprezentuje stav HTTP odpovědi – a to bez nutnosti psaní relativně složitého kódu založeného na sekvenci bloků try a except. Tuto funkcionalitu si ukážeme v navazujících dvou kapitolách.
12. Ukázky použití dekorátoru @reraise
Základní použití dekorátoru @reraise může vypadat následovně – libovolný typ výjimky (odvozené od třídy Exception) zachytíme a vyhodíme namísto ní výjimku typu MathError. Je tedy nutné dekorátoru @reraise předat parametry errors (jedna či více zachycovaných výjimek) a into (výjimka, která se má vyhodit):
from funcy import reraise class MathError(Exception): def __init__(self, message): self.message = message @reraise(errors=Exception, into=MathError("neděl nulou!")) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Z výsledku je patrné, že se skutečně vyhodila nová výjimka a ta původní zůstala v historii:
0.5 Traceback (most recent call last): File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 84, in reraise yield File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "reraise_1.py", line 10, in divide return a/b ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "reraise_1.py", line 14, in <module> print(divide(1, 0)) File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "/usr/lib/python3.8/contextlib.py", line 131, in __exit__ self.gen.throw(type, value, traceback) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 88, in reraise raise into from e __main__.MathError: neděl nulou!
Pokud ovšem budeme zachytávat odlišný typ výjimky (zde konkrétně IOError), nebude dekorátor @reraise provádět žádnou viditelnou činnost:
from funcy import reraise class MathException(Exception): def __init__(self, message): self.message = message @reraise(errors=IOError, into=MathException("neděl nulou!")) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Výsledky:
0.5 Traceback (most recent call last): File "reraise_2.py", line 14, in <module> print(divide(1, 0)) File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "reraise_2.py", line 10, in divide return a/b ZeroDivisionError: division by zero
13. Reakce na větší množství typů výjimek
Vzhledem k tomu, že dekorátoru @reraise můžeme v parametru errors předat nikoli pouze jednu, ale hned několik výjimek, lze při „dekoraci“ funkce použít i následující zápis:
from funcy import reraise class MathException(Exception): def __init__(self, message): self.message = message @reraise(errors=[ArithmeticError, IOError], into=MathException("neděl nulou!")) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
V praxi dostaneme v tomto konkrétním případě shodné výsledky, jako v předchozím příkladu, protože výjimka IOError nebude nikdy vyhozena a tudíž ani zachycena:
0.5 Traceback (most recent call last): File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 84, in reraise yield File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "reraise_3.py", line 10, in divide return a/b ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "reraise_3.py", line 14, in <module> print(divide(1, 0)) File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "/usr/lib/python3.8/contextlib.py", line 131, in __exit__ self.gen.throw(type, value, traceback) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 88, in reraise raise into from e __main__.MathException: neděl nulou!
Ovšem současně to znamená, že sice lze použít dekorátor @reraise a současně nezachytit žádnou výjimku. Jak to bude vypadat v praxi?
from funcy import reraise class MathException(Exception): def __init__(self, message): self.message = message @reraise(errors=[], into=MathException("neděl nulou!")) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Výsledky:
0.5 Traceback (most recent call last): File "reraise_4.py", line 14, in <module> print(divide(1, 0)) File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "reraise_4.py", line 10, in divide return a/b ZeroDivisionError: division by zero
14. Využití informací z původní výjimky v nové výjimce
Ještě si ukažme jeden příklad použití dekorátoru @reraise. Tentokrát se pokusíme o využití informací z původní výjimky. Příkladem může být zjištění (resp. přesněji řečeno přečtení) zprávy obsažené v původní výjimce (nemusí být ve všech typech výjimek!) a znovupoužití této zprávy při konstrukci nové výjimky, která se z funkce vyhodí. V tomto případě můžeme s výhodou použít anonymní funkci (lambda) tak, jak je to patrné ze zdrojového kódu následujícího demonstračního příkladu:
from funcy import reraise class MathException(Exception): def __init__(self, message): self.message = message @reraise(errors=Exception, into=lambda e: MathException("neděl nulou! " + str(e))) def divide(a, b): return a/b print(divide(1, 2)) print(divide(1, 0))
Výsledek bude v tomto případě vypadat následovně:
0.5 Traceback (most recent call last): File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 84, in reraise yield File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "reraise_5.py", line 10, in divide return a/b ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "reraise_5.py", line 14, in <module> print(divide(1, 0)) File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "/usr/lib/python3.8/contextlib.py", line 131, in __exit__ self.gen.throw(type, value, traceback) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 88, in reraise raise into from e __main__.MathException: neděl nulou! division by zero
15. Pokusy o opakované spuštění funkce při vyhození výjimky – dekorátor @retry
Posledním dekorátorem, s nímž se v dnešním článku seznámíme, je dekorátor nazvaný @retry. Tento dekorátor zajišťuje, že se nějaká funkce s tímto dekorátorem bude volat opakovaně až do chvíle, kdy buď skončí úspěšně (tedy bez vyhození výjimky) nebo dokud počet opakování nepřesáhne programátorem zadanou hranici (například deset pokusů). Dokonce je možné specifikovat i prodlevu mezi jednotlivými opakováními, což je výhodné například ve chvíli, kdy došlo k výpadku sítě a tedy nemá smysl se pokoušet o připojení bez větší časové prodlevy. Relativně snadno si lze vynutit exponenciálně se zvyšující hodnotu prodlevy, což ostatně uvidíme na demonstračním příkladu.
16. Základní způsob použití dekorátoru @retry
Základní způsob použití dekorátoru @retry je vlastně velmi jednoduchý, protože nám postačuje pouze zadat hraniční počet opakování funkce. V případě, že volaná funkce bude stále vyhazovat výjimku, pokusy o její opakované spuštění po dosažení určeného počtu budou ukončeny a výjimka bude skutečně vyhozena:
from funcy import retry @retry(3) def call_function_to_raise_exception(): print("Trying to call problematic code...") raise_exception() def raise_exception(): raise Exception("foo") while True: call_function_to_raise_exception()
Výsledky ukazují celkem čtyři pokusy o volání funkce:
Trying to call problematic code... Trying to call problematic code... Trying to call problematic code... Traceback (most recent call last): File "retry_1.py", line 15, in <module> call_function_to_raise_exception() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 47, in wrapper return deco(call, *dargs, **dkwargs) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 99, in retry return call() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 68, in __call__ return self._func(*self._args, **self._kwargs) File "retry_1.py", line 7, in call_function_to_raise_exception raise_exception() File "retry_1.py", line 11, in raise_exception raise Exception("foo") Exception: foo
Alternativně můžeme zadat prodlevu mezi jednotlivými voláními funkce, která je specifikovaná v sekundách:
from funcy import retry @retry(3, timeout=1) def call_function_to_raise_exception(): print("Trying to call problematic code...") raise_exception() def raise_exception(): raise Exception("foo") while True: call_function_to_raise_exception()
V našem konkrétním případě bude výsledek totožný, ovšem dosáhneme ho až za zhruba čtyři sekundy:
Trying to call problematic code... Trying to call problematic code... Trying to call problematic code... Traceback (most recent call last): File "retry_2.py", line 15, in <module> call_function_to_raise_exception() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 47, in wrapper return deco(call, *dargs, **dkwargs) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 99, in retry return call() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 68, in __call__ return self._func(*self._args, **self._kwargs) File "retry_2.py", line 7, in call_function_to_raise_exception raise_exception() File "retry_2.py", line 11, in raise_exception raise Exception("foo") Exception: foo
17. Specifikace typu zachytávaných výjimek
Podobně jako u některých výše zmíněných dekorátorů, i u dekorátoru @retry je možné specifikovat, které výjimky budou zpracovány a které přímo povedou k ukončení funkce. Například následující kód skončí ihned po prvním volání funkce, protože zpracováváme pouze výjimku typu IOError a nikoli Exception:
from funcy import retry @retry(3, timeout=1, errors=IOError) def call_function_to_raise_exception(): print("Trying to call problematic code...") raise_exception() def raise_exception(): raise Exception("foo") while True: call_function_to_raise_exception()
Z výsledků je patrné, že nyní skutečně nedošlo ke snaze o opětovné volání funkce:
Trying to call problematic code... Traceback (most recent call last): File "retry_3.py", line 15, in <module> call_function_to_raise_exception() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 47, in wrapper return deco(call, *dargs, **dkwargs) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 99, in retry return call() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 68, in __call__ return self._func(*self._args, **self._kwargs) File "retry_3.py", line 7, in call_function_to_raise_exception raise_exception() File "retry_3.py", line 11, in raise_exception raise Exception("foo") Exception: foo
Jinak tomu bude (logicky) tehdy, pokud budeme explicitně zpracovávat výjimku typu Exception:
from funcy import retry @retry(3, timeout=1, errors=Exception) def call_function_to_raise_exception(): print("Trying to call problematic code...") raise_exception() def raise_exception(): raise Exception("foo") while True: call_function_to_raise_exception()
Nyní je již vše v pořádku:
Trying to call problematic code... Trying to call problematic code... Trying to call problematic code... Traceback (most recent call last): File "retry_4.py", line 15, in <module> call_function_to_raise_exception() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 47, in wrapper return deco(call, *dargs, **dkwargs) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 99, in retry return call() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 68, in __call__ return self._func(*self._args, **self._kwargs) File "retry_4.py", line 7, in call_function_to_raise_exception raise_exception() File "retry_4.py", line 11, in raise_exception raise Exception("foo") Exception: foo
18. Exponenciálně vzrůstající hodnota timeout mezi jednotlivými voláními funkce
V dnešním posledním demonstračním příkladu je ukázáno, jak lze zajistit, aby se prodleva mezi jednotlivými voláními funkce exponenciálně zvyšovala. To je častý požadavek, zejména pokud se čeká na inicializaci nějaké služby, na obnovení činnosti sítě atd. – to poslední, čeho chceme dosáhnout, je přetížení prostředku (například oné sítě) v důsledku častých dotazů. A právě neustále se zvyšující prodleva mezi požadavky může být vhodným řešením. V praxi vypadá následovně:
from funcy import retry @retry(4, timeout=lambda delay: 2 ** delay, errors=Exception) def call_function_to_raise_exception(): print("Trying to call problematic code...") raise_exception() def raise_exception(): raise Exception("foo") while True: call_function_to_raise_exception()
Po spuštění tohoto příkladu je patrné, že se funkce zavolá celkem pětkrát a teprve poté se výjimka skutečně vyhodí (co již vidět není, jsou časy jednotlivých volání – přidejte si proto do kódu vámi oblíbenou logovací knihovnu, aby se časy zobrazily):
Trying to call problematic code... Trying to call problematic code... Trying to call problematic code... Trying to call problematic code... Traceback (most recent call last): File "retry_5.py", line 15, in <module> call_function_to_raise_exception() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 47, in wrapper return deco(call, *dargs, **dkwargs) File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/flow.py", line 99, in retry return call() File "/home/ptisnovs/.local/lib/python3.8/site-packages/funcy/decorators.py", line 68, in __call__ return self._func(*self._args, **self._kwargs) File "retry_5.py", line 7, in call_function_to_raise_exception raise_exception() File "retry_5.py", line 11, in raise_exception raise Exception("foo") Exception: foo shell returned 1
19. Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si prozatím v tomto seriálu ukázali, 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, a pro dnešní příklady i výše zmíněnou knihovnu funcy):
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 - Clojure aneb jazyk umožňující tvorbu bezpečných vícevláknových aplikací pro JVM (4.část – kolekce, sekvence a lazy sekvence)
https://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure a bezpečné aplikace pro JVM: sekvence, lazy sekvence a paralelní programy
https://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - 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 - Funcy na GitHubu
https://github.com/suor/funcy/ - Welcome to funcy documentation!
https://funcy.readthedocs.io/en/stable/ - Funcy cheatsheet
https://funcy.readthedocs.io/en/stable/cheatsheet.html - PyToolz API Documentation
https://toolz.readthedocs.io/en/latest/index.html - Toolz (PyToolz) na GitHubu
https://github.com/pytoolz/toolz - Fn.py: enjoy FP in Python
https://github.com/kachayev/fn.py - Funcy na PyPi
https://pypi.org/project/funcy/ - Underscore aneb další knihovna pro funkcionální programování v JavaScriptu
https://www.root.cz/clanky/underscore-aneb-dalsi-knihovna-pro-funkcionalni-programovani-v-javascriptu/ - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Awesome functional Python
https://github.com/sfermigier/awesome-functional-python - lispy
https://pypi.org/project/lispy/ - clojure_py na indexu PyPi
https://pypi.python.org/pypi/clojure_py - PyClojure
https://github.com/eigenhombre/PyClojure - Hy na GitHubu
https://github.com/hylang/hy - Hy: The survival guide
https://notes.pault.ag/hy-survival-guide/ - Hy běžící na monitoru terminálu společnosti Symbolics
http://try-hy.appspot.com/ - Welcome to Hy’s documentation!
http://docs.hylang.org/en/stable/ - Hy na PyPi
https://pypi.org/project/hy/#description - Getting Hy on Python
https://lwn.net/Articles/596626/