V minulém díle jste viděli...¶
Generátory¶
Generátory jsou zvláštním druhem iterátoru, který iteruje přes průběžně generované hodnoty, které generuje. generátorové funkce (generator function) nebo generátorového výrazu (generator expression).
Generátorová funkce je taková funkce, která hodnoty nevrací (pomocí příkazu return
), ale generuje (pomocí příkazu yield
).
def generate_squares(n):
for x in range(n):
yield x ** 2
Kdykoliv v programu zavoláme generátorovou funkci, funkce vrátí nový generátor.
m = generate_squares(5)
print(m)
for x in m:
print(x, end = " ")
print("")
m = tuple(generate_squares(5))
print(m)
<generator object generate_squares at 0x7f6990184110> 0 1 4 9 16 (0, 1, 4, 9, 16)
Generátorové výrazy umožňují vytvářet generátory bez nutnosti vytváření generátorových funkcí. Generátorová notace (comprehension) u modifikovatelných (mutable) kontejnerů nám pak výrazně zjednodušuje vytváření a zpracování těchto objektů.
Základní syntaxe je následující:
new_generator = (expression for member in iterable)
new_list = [expression for member in iterable]
new_set = {expression for member in iterable}
new_dict = {key:expression for member in iterable}
Například pro seznam můžeme příkaz rozepsat jako:
new_list = []
for element in iterable:
my_list.append(expression(element))
Generové výrazy můžeme rozšířit o podmínky a můžeme pomocí nich kombinovat hodnoty z více zdrojů:
- Filtrování (obdoba funkce
filter
):
new_generator = (expression for member in iterable if condition)
- Kombinace hodnot z více zdrojů:
new_generator = (expression for member1 in iterable1 (if condition1)
for member2 in iterable2 (if condition2)
...
for memberN in iterableN (if conditionN))
Zpracování chyb, výjimky, aserce¶
Neoddělitelnou součástí programování je také práce s chybami. Praxe ukazuje, že téměř každý alespoň trošku složitý program obsahuje nějakou chybu. Chyby obvykle dělíme přinejmenším do dvou skupin: syntaktické chyby a běhové chyby.
Syntaktické chyby¶
Syntaktické chyby (anglicky syntax errors) představují porušení pravidel zápisu jazyka v daném programovacím jazyce. Na syntaktickou chybu upozorní interpret (případně překladač) a označí i řádek ve zdrojovém kódu, kde se chyba nachází. Dokud programátor neopraví všechny syntaktické chyby, není možné program interpretovat (případně přeložit a spustit). Výhodou tedy je, že se o syntaktické chybě nikdo kromě samotného programátora nedozví. Moderní vývojová prostředí umí syntaktické chyby zvýrazňovat přímo při psaní zdrojového kódu.
Následující ukázka obsahuje syntaktickou chybu:
print "Hello world"
Cell In[5], line 1 print "Hello world" ^ SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
Interpret označí, že na řádce 1
našel chybu Missing parentheses in call to 'print'
, která značí, že ve volání funkce print
chybí závorky. V tomto případě interpret také nabídne možnost opravy.
Po opravě už bude možné program spustit:
print("Hello world")
Hello world
Běhové chyby¶
Běhové chyby (anglicky runtime errors) se projeví až při běhu programu a většinou vedou k jeho havárii (pádu). Mezi běhové chyby patří dělení nulou, použití ještě nevytvořeného objektu, použití objektu špatného datového typu, a podobně. V následující ukázce dojde k běhové chybě:
def faktorial(n):
# f = 1
while n > 0:
f = f * n
n = n -1
return f
print(faktorial(5))
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) Cell In[8], line 8 5 n = n -1 6 return f ----> 8 print(faktorial(5)) Cell In[8], line 4, in faktorial(n) 1 def faktorial(n): 2 # f = 1 3 while n > 0: ----> 4 f = f * n 5 n = n -1 6 return f UnboundLocalError: cannot access local variable 'f' where it is not associated with a value
Po spuštění skriptu dojde k pádu programu a zobrazí se hlášení, že k lokální proměnné f
bylo přistoupeno před její inicializací. Řešením tohoto problému je definovat proměnnou f
na začátku funkce faktorial
a přiřadit jí hodnotu 1
.
Poznámka: Některé prameny uvádějí ještě třetí typ chyb: logické (nebo sémantické). Tyto chyby bývá největší problém odhalit, protože navenek program běží, ale počítá něco špatně.
Výjimky jako reakce na vznik běhových chyb¶
Protože je programovací jazyk Python silně zaměřen na objektově orientované programování, nepřekvapí, že se v něm s chybami zachází jako s objekty. Protože by k chybám v dobře napsaném programu mělo docházet pouze výjimečně, zažil se pro tyto objekty pojem výjimky (anglicky exceptions). Takový objekt pak nese informace o chybě:
- na jakém místě v programu k chybě došlo
- jak se program na místo vzniku chyby dostal
- jaký typ chyby nastal
V situaci, kdy je v programu splněna podmínka vedoucí k běhové chybě, existuje možnost vyvolat výjimku (anglicky raise exception). Ve chvíli vyvolání výjimky je provádění aktuální funkce přerušeno, a v hierarchii volání se hledá kód, který by mohl danou výjimku zachytit (anglicky catch) a ošetřit (anglicky handle). V případě, že není nalezen žádný takový kód (často označovaný jako handler), program zhavaruje (spadne, crashne) a zobrazí chybové hlášení.
Na jednotlivé kroky se nyní podíváme podrobněji.
Vyvolání výjimky¶
Pro vyvolání výjimky se v jazyce Python používá příkaz raise
, ke kterému je možné přidat informaci o typu chyby a její textový popis:
def hustota(m, v):
if v == 0:
raise ZeroDivisionError("Nepodělíš nulou!")
return m/v
hustota(5.0, 0.0)
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[11], line 6 3 raise ZeroDivisionError("Nepodělíš nulou!") 4 return m/v ----> 6 hustota(5.0, 0.0) Cell In[11], line 3, in hustota(m, v) 1 def hustota(m, v): 2 if v == 0: ----> 3 raise ZeroDivisionError("Nepodělíš nulou!") 4 return m/v ZeroDivisionError: Nepodělíš nulou!
Vzhledem k tomu, že vyvolanou výjimku v kódu nikde nezachytáváme, dojde k vypsání informací o vzniklé chybě a následně k předčasnému ukončení programu. Součástí chybové zprávy budou i informace o vzniklé chybě získané z chybového objektu.
Poznámka: V této ukázce používáme třídu Exception, která je předkem všech standardních výjimek jazyka Python. Poznamenejme, že jednou z odvozených tříd je i výjimka ZeroDivisionError, která by pro tento příklad byla vhodnější. Přehled nejčastějších standardních výjimek bude uveden později.
Výjimka typu AssertionError
¶
Při ladění a testování zdrojového kódu můžeme v jazyce Python použít klíčové slovo assert
, které slouží k ověřování platnosti podmínek:
def faktorial(n):
assert n >= 0, "Nelze počítat faktorial ze záporného čísla"
f = 1
for x in range(2, n + 1):
f = f * x
return f
print(faktorial(5))
120
V případě, že je uvedená podmínka splněna, volání assert
nic nezpůsobí. Naopak, pokud podmínka splněna nebude, vyvolá se standardní výjimka typu AssertionError
a program se ukončí.
print(faktorial(-1))
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) Cell In[18], line 1 ----> 1 print(faktorial(-1)) Cell In[17], line 2, in faktorial(n) 1 def faktorial(n): ----> 2 assert n >= 0, "Nelze počítat faktorial ze záporného čísla" 3 f = 1 4 for x in range(2, n + 1): AssertionError: Nelze počítat faktorial ze záporného čísla
Použití klíčového slova assert
nám tedy umožní program odladit tak, aby byly všechny požadované podmínky splněny.
Poznámka:
Dodejme, že klíčové slovo assert
byste již měli znát z domácích úkolů, kde se používá ve veřejných i skrytých testech.
Nyní se podíváme na způsob, jakým se výjimky zachytávají a ošetřují.
Ošetřování výjimek¶
K zachycení a ošetření výjimek v jazyce Python nám slouží pokusný blok uvozený klíčovým slovem try
doplněný o jeden nebo více bloků except
. V případě, že některý příkaz v pokusném bloku vyvolá výjimku, přeruší se vykonávání tohoto bloku a řízení se přesune do bloku except
. Bloku except
se také říká handler, a má na starost ošetření výjimek vzniklých v předchozím pokusném bloku. Pokud v pokusném bloku žádný příkaz výjimku nevyvolá, blok except
se vynechá.
try:
print(faktorial(5))
print(faktorial(-5))
print(faktorial(20))
except:
pass
print("Toto je první příkaz za handlerem")
120 Toto je první příkaz za handlerem
Při výpočtu faktoriálu hodnoty -5
nebyla ve funkci faktorial
splněna podmínka kladnosti parametru, což způsobilo vyvolání standardní výjimky (typu AssertionError
) a dále předčasné ukončení fukce faktorial
a také zbytku pokusného bloku. Řízení pak pokračovalo v bloku except
, který by měl zachycenou výjimku ošetřit.
Poznámka:
V bloku except
jsme použili prázdný příkaz pass
, který nic nedělá, ale je možné ho z hlediska syntaxe použít na jakémkoliv místě, kde je očekáván nějaký příkaz (např. tělo funkce, tělo cyklů a podmínek). Používá se při psaní programu na vyplnění ještě neimplementovaných pasáží zdrojového kódu.
Upravme předchozí ukázku tak, aby handler vykonal nějakou činnost. K tomu, abychom zjistili informaci o chybě, musíme zachytit vyvolanou výjimku (v našem případě typu AssertionError
):
try:
print(faktorial(5))
print(faktorial(-5))
print(faktorial(20))
except AssertionError as error:
print("Zachytil jsem výjimku typu AssertionError. Došlo k následující chybě:")
print(error)
print("Toto je první příkaz za handlerem")
120 Zachytil jsem výjimku typu AssertionError. Došlo k následující chybě: Nelze počítat faktorial ze záporného čísla Toto je první příkaz za handlerem
Všimněte si, že v tomto případě je za klíčovým slovem except
uveden typ výjimky, kterou daný handler obslouží. Za klíčovým slovem as
se píše název, pod kterým budeme se zachycenou výjimkou pracovat. Můžeme ji třeba předat funkci print
, která vypíše textový popis chyby.
Za blokem try
může být handlerů více, každý z nich pak obslouží jiný typ výjimky:
try:
# 1/0
print(faktorial(5))
print(faktorial("10"))
print(faktorial(20))
except AssertionError as error:
print("Zachytil jsem výjimku typu AssertionError. Došlo k následující chybě:")
print(error)
except TypeError as type_error:
print("Zachytil jsem výjimku typu TypeError. Došlo k následující chybě:")
print(type_error)
print("Toto je první příkaz za handlerem")
120 Zachytil jsem výjimku typu TypeError. Došlo k následující chybě: '>=' not supported between instances of 'str' and 'int' Toto je první příkaz za handlerem
V tomto případě tedy první handler obsluhuje výjimky typu AssertionError
a druhý pak TypeError
. Při práci s výjimkami je potřeba doplnit handler pro každý typ výjimky, který v pokusném bloku může vzniknout.
Univerzální handler umí obsloužit všechny typy výjimek odvozené od třídy Exception
. Je uvozen pomocí except:
nebo except Exception ... :
try:
1/0
print(faktorial(5))
print(faktorial("10"))
print(faktorial(20))
except AssertionError as error:
print("Zachytil jsem výjimku typu AssertionError. Došlo k následující chybě:")
print(error)
except TypeError as type_error:
print("Zachytil jsem výjimku typu TypeError. Došlo k následující chybě:")
print(type_error)
except Exception as error:
print("Zachytil jsem nějakou nečekanou chybu:")
print(error)
print("Toto je první příkaz za handlerem")
Zachytil jsem nějakou nečekanou chybu: division by zero Toto je první příkaz za handlerem
Varování: Použití univerzálního handleru je jeden z nejnebezpečnějších zlozvyků (antipattern). Kód
try: něco_udělej() except: passsice zachytí všechny výjimky a tím zabrání okamžitému pádu programu, ale na druhou stranu všechny chyby, i ty neočekávané, v tichosti zamaskuje! Tímto způsobem pak často projdou chyby nepozorovaně do produkční verze programu.
Poznámka: V jazyce Python se používá styl psaní kódu označovaný zkratkou EAFP - easier to ask for forgiveness than permission neboli raději prosit o odpuštění než o povolení. To znamená, že se při práci se slovníky a objekty předpokládá existence klíčů a atributů a v případě jejich neexistence se zachytí výjimka. Součástí kódu tak bude řada pokusných bloků doplněných o příslušné handlery.
Na druhou stranu v jiných programovacích jazycích, jako třeba C, dáváme přednost principu LBYL - look before you leap neboli rozhlédni se před tím, než skočíš. V tomto případě existenci klíčů a atributů ověřuje před tím, než k nim přistoupíme. Součástí kódu tak bude řada podmínek. Tento přístup může působit problémy ve vícevláknovém programování. Například, následující ukázka:
if key in dictionary: return dictionary[key]selže, pokud jiné vlákno odstraní klíč ze slovníku po testu, ale před přístupem k hodnotě. Řešením je použít zamykání nebo EAFP přístup.
# chyby nejsou ošetřené:
s = [1,2,3]
i = int(input("zadej index"))
print(s[i])
2
# princip EAFP:
s = [1,2,3]
try:
i = int(input("zadej index"))
print(s[i])
except IndexError as e:
print("chyba: Zadal jsi index mimo rozsah,", e)
except ValueError as e:
print("chyba: Nezadal jsi číslo,", e)
Zadal jsi index špatně
# princip LBYL
s = [1,2,3]
i = int(input("zadej index"))
if 0 < i < len(s):
print(s[i])
else:
print("chyba: Zadal jsi index mimo rozsah")
chyba: Zadal jsi index mimo rozsah
# princip LBYL
s = [1,2,3]
x = input("zadej index")
if x.isdigit():
i = int(x)
if 0 <= i < len(s):
print(s[i])
else:
print("chyba: Zadal jsi index mimo rozsah")
else:
print("chyba: Nezadal jsi číslo")
chyba: Nezadal jsi číslo
# řízení cyklu pomocí výjimek:
s = [1,2,3]
i = None
while True:
try:
i = int(input("zadej index"))
print(s[i])
except IndexError as e:
print(f"Zadal jsi moc velký index. Musí být mezi {-len(s)} a {len(s)-1}. Zkus to znovu.")
except ValueError as e:
print("Nezadal jsi celé číslo. Zkus to znovu.")
else:
break
Zadal jsi moc velký index. Musí být mezi -3 a 2. Zkus to znovu.
Nezadal jsi celé číslo. Zkus to znovu.
1
V následující tabulce jsou vypsány časté výjimky, se kterými se můžete při programování potkat:
Výjimka | Krátký popis |
---|---|
ValueError |
vzniká při pokusu předat do funkce neplatnou hodnotu argumentu |
TypeError |
vzniká při pokusu o provedení operace s objektem nevhodného typu (např. operátor dělení pro typ str ) |
NameError |
vzniká při pokusu o přístup k funkci nebo proměnné, jejichž jméno není nalezeno v aktuální oblasti viditelnosti |
IndentationError |
vzniká v případě chybně odsazeného kódu |
RecursionError |
vzniká v případě překročení maximální hloubky rekurze |
ZeroDivisionError |
vzniká při pokusu o dělení nulou |
AssertionError |
vzniká, pokud není splněna podmínka v příkazu assert |
IndexError |
vzniká při indexování posloupností, pokud je index mimo meze |
KeyError |
vzniká, pokud přistupujeme k neexistujícímu klíči slovníku |
StopIteration |
vzniká, pokud se pokusíme použít iterátor, který už je na konci |
IOError |
vzniká v případě chyby při vstupně-výstupních operacích (práce se soubory) |
ImportError |
vzniká, pokud příkaz import nenalezne požadovaný modul |
Podrobnější popis a kompletní tabulku standardních výjimek je možné nalézt v referenční dokumentaci jazyka Python.
V jednom handleru můžeme zachytit i více typů výjimek, pokud je zapíšeme jako n-tici. V ukázce je použití funkce type(var)
, která vrací objekt obsahující informaci o typu proměnné var
. Jeho atribut __name__
pak obsahuje název typu.
typeE = None
try:
print(faktorial(-5))
print(faktorial("10"))
print(faktorial(20))
except (AssertionError, TypeError) as error:
error_name = type(error).__name__
print(f"Zachytil jsem výjimku typu {error_name}. Došlo k následující chybě:")
print(error)
print("Toto je první příkaz za handlerem")
Zachytil jsem výjimku typu AssertionError. Došlo k následující chybě: Nelze počítat faktorial ze záporného čísla Toto je první příkaz za handlerem
Klauzule else
a finally
¶
Za handlery může volitelně následovat blok else:
s příkazy, které mají být vykonány v případě, že v pokusném bloku nenastala chyba. Na úplný závěr může být blok finally:
, který se vykoná vždy, bez ohledu na to, zda v pokusném bloku byla vyvolána výjimka, či nikoliv:
try:
f = faktorial(5)
except (AssertionError, TypeError) as error:
error_name = type(error).__name__
raise ZeroDivisionError("Test")
print(f"Zachytil jsem výjimku typu {error_name}. Došlo k následující chybě:")
print(error)
else:
print("Pokusný blok proběhl bez chyb, výsledek je", f)
finally:
print("Tento blok se provede vždy")
Pokusný blok proběhl bez chyb, výsledek je 120 Tento blok se provede vždy
Poznánka: Při obsluze výjimky může v rámci handleru vzniknout další výjimka. Tuto sekundární výjimku ovšem neobslouží ostatní handlery na této úrovni, protože nejsou uvnitř pokusného bloku. Nicméně blok finally se provede i v tomto případě. V následující ukázce si všimněte hlášení During handling of the above exception, another exception occurred (při obsluze předchozí výjimky nastala další výjimka) a dále výpisu z finally bloku:
try:
f = faktorial("5")
except (AssertionError, TypeError) as error:
error_name = type(error).__name__
raise ZeroDivisionError("Test")
print(f"Zachytil jsem výjimku typu {error_name}. Došlo k následující chybě:")
print(error)
else:
print("Pokusný blok proběhl bez chyb, výsledek je", f)
finally:
print("Tento blok se provede vždy")
Tento blok se provede vždy
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[38], line 2 1 try: ----> 2 f = faktorial("5") 3 except (AssertionError, TypeError) as error: Cell In[17], line 2, in faktorial(n) 1 def faktorial(n): ----> 2 assert n >= 0, "Nelze počítat faktorial ze záporného čísla" 3 f = 1 TypeError: '>=' not supported between instances of 'str' and 'int' During handling of the above exception, another exception occurred: ZeroDivisionError Traceback (most recent call last) Cell In[38], line 5 3 except (AssertionError, TypeError) as error: 4 error_name = type(error).__name__ ----> 5 raise ZeroDivisionError("Test") 6 print(f"Zachytil jsem výjimku typu {error_name}. Došlo k následující chybě:") 7 print(error) ZeroDivisionError: Test
Vyvolání a šíření výjimky¶
Nyní už byste měli vědět, jak výjimku zachytit a ošetřit, zbývá ukázat, jak výjimku vyvolat a jakým způsobem se v programu šíří. K vyvolání výjimky slouží příkaz raise
doplněný o typ výjimky, která se má vyvolat. Typem výjimky se rozumí konkrétní objekt - třída odvozená od předka Exception
.
V následující ukázce funkce fukce_volana()
vyvolá výjimku typu ValueError
, což způsobí začátek šíření výjimky. Během něho se hledá handler odpovídajícího typu, který by výjimku zvládl ošetřit. Pokud se handler nenajde ve volané funkci, dojde k jejímu předčasnému ukončení a handler se dále hledá ve volající funkci. V našem případě se výjimka po jednotlivých patrech stromu volání rozšířila až na globální úroveň, na které se konečně našel příslušný handler. Pokud by se nenašel ani na globální úrovni, program by se ukončil (neošetřená výjimka). Všimněte si, že v tomto handleru voláme znovu příkaz raise
- tím umožníme další šíření výjimky.
def funkce_volana():
raise ValueError("Ahoj, já jsem výjimka typu ValueError")
print("volana")
def funkce_volajici():
funkce_volana()
print("volajici")
try:
funkce_volajici()
except ValueError as error:
print(error)
raise
Ahoj, já jsem výjimka typu ValueError
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[7], line 10 7 print("volajici") 9 try: ---> 10 funkce_volajici() 11 except ValueError as error: 12 print(error) Cell In[7], line 6, in funkce_volajici() 5 def funkce_volajici(): ----> 6 funkce_volana() 7 print("volajici") Cell In[7], line 2, in funkce_volana() 1 def funkce_volana(): ----> 2 raise ValueError("Ahoj, já jsem výjimka typu ValueError") 3 print("volana") ValueError: Ahoj, já jsem výjimka typu ValueError
Program nakonec opravdu zhavaruje, protože druhé vyvolání výjimky už není nikde zachyceno. Všimněte si, že v chybovém výpisu je zobrazen graf volání (anglicky traceback nebo taky stack trace)
Výhody a nevýhody práce s výjimkami¶
Použití výjimek v programu přináší řadu výhod:
- vyšší spolehlivost: použití výjimek umožní detekovat nečekané chyby
- čistější kód a jednodušší obsluha chyb: použití výjimek umožní oddělit kód pro ošetřování chyb od výpočetní logiky
- snadnější ladění: výpis informací, které výjimka nese usnadní identifikaci problému a výpis grafu volání umožní i vystopovat, kde k chybě došlo
Nicméně, s výjimkami jsou spjaty i některé nevýhody:
- dopad na výkon: s obsluhou výjimek jsem spojena režie, která snižuje výkon programu
- zvýšení složitosti kódu: zvláště pokud pracujeme s mnoha typy výjimek, může dojít ke zvýšení složitosti zdrojových souborů související s přidanou logikou pro obsluh chyb
- potenciální zvýšení zranitelnosti: špatně obsloužená výjimka může zveřejnit citlivé informace a vytvořit bezpečnostní zranitelnost
I přes tyto nedostatky zůstávají výjimky základním nástrojem pro řešení běhových chyb v jazyce Python.
Příklady¶
1. Základy práce s výjimkami¶
Zkuste si pro každý z následujících typů chyb napsat chybný kód, který ji vyvolá.
Výjimka | Krátký popis |
---|---|
ZeroDivisionError |
vzniká při pokusu o dělení nulou |
IndexError |
vzniká při indexování posloupností, pokud je index mimo meze |
KeyError |
vzniká u slovníku, pokud přistupujeme k neexistujícímu klíči |
StopIteration |
vzniká, pokud se pokusíme použít iterátor, který už je na koci |
NameError |
vzniká při pokusu o přístup k funkci nebo proměnné, jejichž jméno není nalezeno v aktuální oblasti viditelnosti |
ValueError |
vzniká při pokus předat do funkce neplatnou hodnotu argumentu |
TypeError |
vzniká, když operátor nebo funkci aplikujeme na objekt nesprávného typu |
# ZeroDivisionError:
5/0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[42], line 2 1 # ZeroDivisionError: ----> 2 5/0 ZeroDivisionError: division by zero
def f():
raise ZeroDivisionError("nedel nulou")
f()
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[43], line 3 1 def f(): 2 raise ZeroDivisionError("nedel nulou") ----> 3 f() Cell In[43], line 2, in f() 1 def f(): ----> 2 raise ZeroDivisionError("nedel nulou") ZeroDivisionError: nedel nulou
# IndexError:
s = [1,2]
s[-6]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) Cell In[44], line 3 1 # IndexError: 2 s = [1,2] ----> 3 s[-6] IndexError: list index out of range
# KeyError:
d = {}
d["a"]
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) Cell In[45], line 3 1 # KeyError: 2 d = {} ----> 3 d["a"] KeyError: 'a'
# StopIteration:
i = iter(s)
next(i)
next(i)
next(i)
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) Cell In[48], line 5 3 next(i) 4 next(i) ----> 5 next(i) StopIteration:
# NameError:
y = xx+ 6
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[49], line 2 1 # NameError: ----> 2 y = xx+ 6 NameError: name 'xx' is not defined
# ValueError:
int("fgddht")
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[50], line 2 1 # ValueError: ----> 2 int("fgddht") ValueError: invalid literal for int() with base 10: 'fgddht'
# TypeError:
"asfgsd" * 0.1
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[51], line 2 1 # TypeError: ----> 2 "asfgsd" * 0.1 TypeError: can't multiply sequence by non-int of type 'float'
Jednu z chyb vložte do pokusného bloku a odchytněte ji pomocí vhodného (dostatečně konkrétního) handleru. Vypiště textový popis chyby.
try:
6/0
except ZeroDivisionError as e:
print("chyba!! :", e)
chyba!! : division by zero
2. Dělitelnost¶
Napište funkci je_dělitelné(n, d)
, která vrací True
nebo False
podle toho, jestli je zadané přirozené číslo n
dělitelné číslem přirozeným číslem d
nebo ne. Pokud n
nebo d
není typu int
nebo se nejedná o kladné číslo, funkce vyvolá vhodný typ výjimky (TypeError
nebo ValueError
) s vhodným textovým popisem chyby.
def je_dělitelné(n, d):
if not isinstance(n, int) or not (isinstance(d, int)):
raise TypeError("Neplatné argumenty: 'n' a 'd' musí být typu 'int'")
if n <= 0 or d <= 0:
raise ValueError("Neplatné argumenty: 'n' a 'd' musí být kladná čísla")
return n % d == 0
print(je_dělitelné(10, 2))
print(je_dělitelné(10, 3.5))
True
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[57], line 9 6 return n % d == 0 8 print(je_dělitelné(10, 2)) ----> 9 print(je_dělitelné(10, 3.5)) Cell In[57], line 3, in je_dělitelné(n, d) 1 def je_dělitelné(n, d): 2 if not isinstance(n, int) or not (isinstance(d, int)): ----> 3 raise TypeError("Neplatné argumenty: 'n' a 'd' musí být typu 'int'") 4 if n <= 0 or d <= 0: 5 raise ValueError("Neplatné argumenty: 'n' a 'd' musí být kladná čísla") TypeError: Neplatné argumenty: 'n' a 'd' musí být typu 'int'
# takto by mohla vypadat testovací (logovací) funkce, která výjimky odchytne:
def test_and_print(n, d):
try:
result = je_dělitelné(n, d)
except (ValueError, TypeError) as e:
print(f"{n} {d}: Program nalezl chybu: {e}")
else:
print(f"{n} {d}: {result}")
# chybné argumenty:
test_and_print(1, 3.5)
test_and_print('8', 4)
test_and_print(0, 5)
test_and_print(5, 0)
# argumenty jsou v pořádku:
test_and_print(10, 2)
test_and_print(10, 3)
test_and_print(10, 5)
1 3.5: Program nalezl chybu: Neplatné argumenty: 'n' a 'd' musí být typu 'int' 8 4: Program nalezl chybu: Neplatné argumenty: 'n' a 'd' musí být typu 'int' 0 5: Program nalezl chybu: Neplatné argumenty: 'n' a 'd' musí být kladná čísla 5 0: Program nalezl chybu: Neplatné argumenty: 'n' a 'd' musí být kladná čísla 10 2: 0 10 3: 1 10 5: 0
3. Indexy¶
Napište funkci find_letter_indexes(string, letter)
, která vrátí seznam všech indexů, kde se v řetězci string
vyskytuje znak char
pomocí generátorové notace a funkce enumerate
. Rozšiřte řešení této úlohy o kontrolu argumentů funkce: string
by měl být neprázdný string a letter
by měl být buď string délky jedna nebo se může jednat o číselný kód znaku (typ int
). Pokud argumenty nejsou správných typů, funkce by měla vyvolat vhodný typ výjimky (TypeError
nebo ValueError
) s vhodným textovým popisem chyby. Pokud string
znak letter
neobsahuje, nechte funkci také vyvolat vhodnou výjimku (ValueError
).
def find_letter_indexes(string, letter):
if not isinstance(string, str):
raise TypeError("Neplatné argumenty: 'string' musí být typu 'str'.")
if not ( isinstance(letter, str) or isinstance(letter, int)):
raise TypeError("Neplatné argumenty: 'letter' musí být typu 'str' nebo 'int'.")
if isinstance(letter, str) and not len(letter) == 1:
raise ValueError("String 'letter' musí být délky jedna nebo číselný kód znaku.")
if isinstance(letter, int):
letter = chr(letter)
if letter not in string:
raise ValueError(f"Znak {letter} není obsažen ve stringu.")
return [index for index, char in enumerate(string) if char == letter]
assert find_letter_indexes("hello world", "o") == [4, 7]
assert find_letter_indexes("hello world", 108) == [2, 3, 9]
Vložte volání funkce do pokusného bloku (můžete se inspirovat testovací funkcí u předchozího příkladu) a otestujte funkci na vhodných příkladech:
s = "hello world"
l = "ll"
# result = find_letter_indexes(s, l)
def test(s,l):
try:
find_letter_indexes(s, l)
except (TypeError, ValueError) as e:
name = type(e).__name__
print(f"Nastala chyba typu {name}:", e)
else:
print("Nenastala chyba")
test("hello world","ll")
test("hello world",-1)
test("hello world","x")
test(['a','b'],"a")
test("",1.5)
Nastala chyba typu ValueError: String 'letter' musí být délky jedna nebo číselný kód znaku. Nastala chyba typu ValueError: chr() arg not in range(0x110000) Nastala chyba typu ValueError: Znak x není obsažen ve stringu. Nastala chyba typu TypeError: Neplatné argumenty: 'string' musí být typu 'str'. Nastala chyba typu TypeError: Neplatné argumenty: 'letter' musí být typu 'str' nebo 'int'.
4. Známky¶
Seznam studentů je reprezentovaný jako seznam slovníků, kde pro každého studenta je jeho jméno a seznam získaných známek. Příklad správně definovaného seznamu:
grades = [
{'name': 'Adam', 'grades': [1, 2, 3, 1, 2]},
{'name': 'Martin', 'grades': [1, 2, 2]},
{'name': 'Filip', 'grades': [1, 3]},
{'name': 'David', 'grades': [1, 2, 2, 3]},
{'name': 'Jakub', 'grades': [1, 2, 2, 1, 1]},
]
Napište funkci
check_format(grades)
, která pomocíassert
-ů otestuje, zda proměnnágrades
má správný formát:- grades je typu seznam (
list
) - prvky tohoto seznamu jsou pouze slovníky (
dict
) - každý slovník obsahuje právě klíče
name
agrades
- datový typ hodnoty
name
jestr
a datový typgrades
jelist
- seznam se známkami obsahuje pouze čísla od
1
do5
- žádné jméno studenta se v seznamu neopakuje
- grades je typu seznam (
Doplňte assert
-y o vhodné chybové hlášky.
Zkuste pro funkci vymyslet vhodné testovací příklady.
def check_format(grades):
assert isinstance(grades,list), "grades must be a list"
for student in grades:
assert isinstance(student,dict), "grades must contain dicts"
assert set(student.keys()) == {'name','grades'}, "wrong keys"
assert isinstance(student["name"], str), "value of name must be a string"
assert isinstance(student["grades"], list), "value of grades must be a list"
assert all(1 <= grade <= 5 for grade in student['grades']), "Grades must be a list of numbers 1-5"
names = [student['name'] for student in grades]
assert len(names) == len(set(names)), "there are duplicate student names"
check_format(grades)
def test(qwe):
try:
check_format(grades_with_error)
except AssertionError as e:
print("Kontrola funguje: ", end = " ")
print(e)
else:
assert False, "Kontrola nefunguje."
grades_with_error = (1, 2)
test(grades_with_error)
grades_with_error = [1,2,3]
test(grades_with_error)
grades_with_error = [
{'name': 'Adam', 'grades': [1, 2, 3, 1, 2]},
{'name': 'Martin', 'grades': [1, 2, 2]},
{'name': 'Filip', 'grades': [1, 3]},
{'name': 'David', 'grades': [1, 2, 2, 3]},
{'name': 'Pepa', 'igrades': [1, 2, 2, 1, 1]},
]
test(grades_with_error)
grades_with_error = [
{'name': 'Adam', 'grades': [1, 2, 3, 1, 2]},
{'name': 'Martin', 'grades': [1, 2, 2]},
{'name': 'Filip', 'grades': [1, 3]},
{'name': 'David', 'grades': [1, 2, 2, 3]},
{'name': 78, 'grades': [1, 2, 2, 1, 1]},
]
test(grades_with_error)
...
grades_with_error = [
{'name': 'Adam', 'grades': [1, 2, 3, 1, 2]},
{'name': 'Martin', 'grades': [1, 2, 2]},
{'name': 'Filip', 'grades': [1, 3]},
{'name': 'David', 'grades': [1, 2, 2, 3]},
{'name': 'Pepa', 'grades': [1,2,0]},
]
test(grades_with_error)
Kontrola funguje: grades must be a list Kontrola funguje: grades must contain dicts Kontrola funguje: wrong keys Kontrola funguje: value of name must be a string Kontrola funguje: Grades must be a list of numbers 1-5
- Napište funkci, která vrátí seznam jmen studentů, kteří nemají dostatek známek (alespoň
n
). Funkce pomocí funkcecheck_format()
zkontroluje, zda seznamstudents
je správného formátu. Také zkontroluje, zdan
je (nezáporné) celé číslo. Jinak vyvolá vhodný typ výjimky.
def students_with_not_enough_grades(students, n):
...
assert students_with_not_enough_grades(grades,4) == ['Martin', 'Filip']
- Napište funkci, která vrátí seznam, kde bude pro každé jméno studenta jeho průměr známek. Seznam bude seřazený v pořadí od studenta s nejlepším průměrem známek po studenta s nejhorším průměrem. Pokud seznam
students
nemá správný formát, funkce nevyvolá výjimku, ale vypíše textový popis chyby a vrátí hodnotuNone
.
def sort_students_by_average_grade(students):
...
print(sort_students_by_average_grade(grades))
print(sort_students_by_average_grade(grades_with_error)) # None
None None
5. Načti a sečti čísla¶
Napište funkci, která postupně načítá čísla ze vstupu, dokud nepřijde něco jiného než číslo. Pak vrátí počet zadaných čísel a jejich součet. Zkuste úlohu řešit s využitím odchytávání výjimky nebo bez něho.
def sum_numbers():
...
c, s = sum_numbers()
print(f"Počet čísel je {c} a jejich součet je {s}.")
Počet čísel je 0 a jejich součet je 0.