4. Výjimky a dekorátory
Výjimky
Více informací zde.
Během našeho programování jsme se již setkali s několika výjimkami (exceptions). Jednu ukázkovou můžeme vyvolat následovně:
1
2
3
4
5
6
7
8
Python 3.9.4 (default, Apr 5 2021, 01:50:46)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 2 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Nejdůležitější je řádek číslo 8, který obsahuje název výjimky ZeroDivisionError
a doplňující popis division by zero
.
Ošetření výjimek
V předchozím příkladu jsme viděli vyvolání výjimky, teď si ukážeme jak na vyvolanou výjimku reagovat.
1
2
3
4
5
6
try:
a = 2 / 0
except ZeroDivisionError as err:
print(f"Following exception was raised: {err}")
finally:
print("Finishing program")
Ošetření výjimek realizuje příkaz try, except, finally
. Blok začínající příkazem try
obsahuje kód ve kterém se výjimky odchytávají. Následují bloky except
s typy výjimek a reakcemi na ně. Volitelný blok finally
se provede v každém případě, ať už výjimka nastane nebo nikoli.
Vestavěné výjimky
Jazyk Python obsahuje sadu vestavěných výjimek, které můžete ve svých programech používat. Jejich podrobnější popis naleznete zde. Jejich krátký přehled je uveden níže.
1
2
3
4
5
6
7
8
9
AssertionError # podmínka příkazu assert není splněna
IndexError # přístup na neexistující index v kolekci
NameError # přístup k nedefinované proměnné
TypeError # operace s nekompatibilními datovými typy
ValueError # operace s kompatibilními datovými typy ale s chybnou hodnotou
OverflowError # výsledek operace je příliš velký a nelze reprezentovat v paměti
ZeroDivisionError # druhý operant dělení nebo modulo roven nule
RuntimeError # blíže nespecifikovaná chyba
Exception # obecná výjimka
Vyvolání výjimek
Výjimky můžeme nejen ošetřovat ale rovněž vyvolávat. V následujícím příkladu funkce vyvolá vyjimku ValueError
pokud není její vstup validní. Výjimka by vždy měla mít anglický dolpňující popis.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def subtract_lists(list1, list2):
"""Subtract two list piecewise."""
if len(list1) != len(list2):
raise ValueError(f"Lists are not same length, {len(list1)} and {len(list2)} was given.")
result = []
for a, b in zip(list1, list2):
result.append(a - b)
return result
subtract_lists([1, 2], [4, 3])
Existují dva přístupy k práci s výjimkami EAFP (it’s easier to ask for forgiveness than permission) a LBYL (look before you leap). Rozdíl je vidět v následujícím příkladě.
1
2
3
4
5
6
7
8
9
# LBYL
if "key" in dict_:
value += dict_["key"]
# EAFP
try:
value += dict_["key"]
except KeyError:
pass
V Pythonu s ohledem na jeho dynamičnost častěji upřednostnujeme EAFP. Je možné používat obojí, v případě LBYL však není dobré kontroly příliš přehánět.
Dekorátory
Funkce vyššího řádu
Funkce které vracejí funkce nebo přijímají funkce jako argument nazýváme funkce vyššího řádu.
1
2
3
4
5
6
7
8
9
10
11
12
13
# funkce vyššího řádu přijímající funkci jako argument
def apply_operation(list_, operation):
"""Applies operation on the given list"""
return operation(list_)
# běžná funkce
def list_sum_squared(list_):
"""Squared sum of all members of given list"""
return sum(list_) ** 2
apply_operation([1, 2, 3], list_sum_squared)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# funkce vyššího řádu vracející funkci
def parent_function():
print("Parent function is running.")
local_x = 10
def local_function():
print("Child function is running.")
return local_x
return local_function
# k vnitřní funkci nelze primo přistoupit
local_function()
# k vnitřní funkci se můžeme dostat
local_function = parent_function()
# a následně ji použít
local_function()
# připadně vše v jenom kroku
parent_function()()
Jednoduchý dekorátor
Co je dekorátor můžeme demonstrovat na jednoduchém příkladu. Představme si, že nami definovanou funkci chceme “obalit” další funkcionalitou (například vytisknout řetězec před a po volání).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# dekorátor
def my_decorator(func):
"""Decorator description."""
def wrapper():
print("Something before function call")
func()
print("Something after function call")
return wrapper
# námi definovaná funkce
def my_function():
print("Super function!")
# volání původní funkce
my_function()
my_function
# obalení funkce dekorátorem
my_function_decorated = my_decorator(my_function)
# volání modifikované funkce
my_function_decorated()
my_function_decorated
Všimněme si, že dekorátor splňuje definici funkce vyššího řádu. Pro jednodušší práci s dekorátory Python nabízí “syntaktický cukr” @nazev_dekoratoru
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# dekorátor
def do_twice(func):
"""Calls a function twice."""
def wrapper():
func()
func()
return wrapper
# dekorování funkce pomoci @
@do_twice
def my_function():
print("Super function!")
Dekorování funkce s argumenty
V případě, že námi dekorovaná funkce přijímá argumenty narazíme na následující problém.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# dekorátor
def do_twice(func):
"""Calls a function twice."""
def wrapper():
func()
func()
return wrapper
# dekorování funkce pomoci @
@do_twice
def my_function(arg):
print(f"Super function! {arg}")
# chyba - dekorovaná funkce nepočítá s argumentem
my_function("Ahoj!")
Tuto situaci opravíme následovně.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# dekorátor - podporuje předání argumentů vnitřní funkci
def do_twice(func):
"""Calls a function twice."""
def wrapper(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper
# dekorování funkce pomoci @
@do_twice
def my_function(arg):
print(f"Super function! {arg}")
my_function("Ahoj!")
Dekorování funkce vracející hodnotu
Podobný problém nastane pokud funkce vrací hodnotu. Její dekorovaná verze bude tuto hodnotu zahazovat.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# dekorátor
def do_twice(func):
"""Calls a function twice."""
def wrapper(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper
# dekorování funkce pomoci @
@do_twice
def multiply_list(list_, by):
"""Multiply items from list by given value.
Args:
list_: list to be multiplied
by: value by which items are multiplied
Returns:
multiplied list
"""
result = []
for item in list_:
result.append(item * by)
return result
# problém - výsledek nebude vrácen
multiply_list([1, 2, 3], 5)
Jednoduchou modifikací dekorátoru situaci vyřešíme.
1
2
3
4
5
6
7
8
# upravený dekorátor
def do_twice(func):
"""Calls a function twice."""
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper
Problém identity funkce
Při dekorování funkce dochází ke ztrátě identity funkce, což ovlivňuje i nedosažitelnost původního docstringu. Demonstrace z předchozího příkladu:
1
2
3
multiply_list
multiply_list.__name__
help(multiply_list)
Situaci vyřešíme použitím dekorátoru functools.wraps
, který zachová identitu dekorované funkce.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import functools
def do_twice(func):
"""Calls a function twice."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper
@do_twice
def multiply_list(list_, by):
"""Multiply items from list by given value.
Args:
list: list to be multiplied
by: value by which items are multiplied
Returns:
multiplied list
"""
result = []
for item in list_:
result.append(item * by)
return result
multiply_list
multiply_list.__name__
help(multiply_list)
Příklad z praxe
Základní šablona dekorátoru je následující:
1
2
3
4
5
6
7
8
9
10
11
12
import functools
def decorator(func):
"""Description."""
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# kód vykonaný před voláním funkce
value = func(*args, **kwargs)
# kód vykonaný po volání funkce
return value
return wrapper_decorator
Jedním z příkladu je takzvaný timing funkce (měření doby běhu).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import functools
import time
def timer(func):
"""Print the runtime of the decorated function"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter()
value = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Finished {func.__name__} in {run_time:.4f} secs")
return value
return wrapper_timer
@timer
def waste_some_time(num_times):
for _ in range(num_times):
1 + 1
waste_some_time(1000)
Zanořování dekorátorů
Dekorátory je možné zanořovat, pozor, záleží na pořadí!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@do_twice
@timer
def waste_some_time(num_times):
for _ in range(num_times):
1 + 1
waste_some_time(100)
@timer
@do_twice
def waste_some_time(num_times):
for _ in range(num_times):
1 + 1
waste_some_time(100)
Dekorátory s argumenty
Dekorátoru je možné předávat argumenty.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def repeat(num_times):
"""Repeats function n times."""
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
@repeat(num_times=4)
@timer
def waste_some_time(num_times):
for _ in range(num_times):
1 + 1
waste_some_time(100)
Úkoly
Nevíte si rady? Přečtěte si “Jak pracovat s Github Classroom?”.
Při řešení úloh nepoužívejte pokročilejší funkcionalitu jazyka která nebyla ještě představena! Takové úkoly budou vráceny na přepracování bez ohledu na jejich funkčnost.
Skupina Mikula
- L04E01: Matrix multiplication (exceptions) [Náhled], [Příjmout úkol]
- L04E02: Delay decorator [Náhled], [Příjmout úkol]
- L04E03: Debug decorator [Náhled], [Příjmout úkol]
Skupina Petržela
- L04E01: Matrix multiplication (exceptions) [Náhled], [Příjmout úkol]
- L04E02: Delay decorator [Náhled], [Příjmout úkol]
- L04E03: Debug decorator [Náhled], [Příjmout úkol]