Lekce 18 - Funkce a výjimky v Pythonu
V minulé lekci, Cykly v Pythonu podruhé - Výraz pass, break a continue, jsme ucelili naše znalosti cyklů dalšími
konstrukcemi a klíčovými slovy pass
, break
a
continue
.
V dnešním Python tutoriálu se naučíme funkcionální styl programování. Dále se podíváme na jednoduché ošetřování chyb programu a vylepšíme si kalkulačku.
Funkce
Doposud jsme programy psali imperativně – proveď tento příkaz, následně tamten a tak dále. Tímto způsobem lze psát jednoduché a krátké programy. Větší programy by však byly velmi nepřehledné. Proto přejdeme na další programovací paradigma (způsob, jak něco naprogramovat) – na funkcionální programování. Náš program si rozdělíme na menší části (podproblémy), které vyřešíme samostatně. Jednotlivé podproblémy řeší funkce. Pro funkcionální styl programování můžeme použít vše, co jsme se doposud naučili.
Funkce obvykle přijímá argumenty (data, která funkce
zpracuje) a něco vrací, například výslednou hodnotu.
Nemusí ale také vracet nic – jako např. funkce print()
.
Základní syntaxe funkce
Funkce se definuje pomocí klíčového slova def
a výslednou
hodnotu vrací klíčovým slovem return
. Za def
se
píše mezera a poté název funkce. Za název funkce se dávají jednoduché
závorky, do nichž se píšou názvy jednotlivých argumentů. Argumentem
funkce v programovacím jazyce Python je hodnota nebo proměnná, kterou
předáváme funkci, aby hodnotu nebo proměnnou mohla použít během svého
vykonávání. Když funkci definujeme, specifikujeme, jaké argumenty může
přijmout. Poté, když funkci voláme, dosazujeme hodnoty pro tyto argumenty.
Funkce může být i bez argumentů. Na konci prvního řádku
se píše dvojtečka. Tělo funkce se standardně odsazuje:
def mocnina(cislo): cislo = cislo ** 2 return cislo
Takto vytvořená funkce mocnina()
vrací číslo umocněné na
druhou. Například při zavolání mocnina(1)
vrátí
1
, při zavolání mocnina(2)
vrátí 4
atd. V praxi to vypadá následovně:
def mocnina(cislo): # Cislo je název argumentu
cislo = cislo ** 2
return cislo
prvni_cislo = mocnina(2) # Dvojka v závorce je námi dosazovaná hodnota argumentu
print(prvni_cislo)
Argumentů může být samozřejmě i více:
def soucin(prvni_cislo, druhe_cislo, treti_cislo):
cislo = prvni_cislo * druhe_cislo * treti_cislo
return cislo
priklad = soucin(2, 3, 4)
print(priklad)
Funkci můžeme také použít jako argument jiné funkce:
def soucin(prvni_cislo, druhe_cislo, treti_cislo):
cislo = prvni_cislo * druhe_cislo * treti_cislo
return cislo
print(soucin(2, 3, 4)) # Naši funkci používáme jako argument funkce print()
Druhy argumentů funkce
V Pythonu máme dva druhy argumentů:
- poziční,
- klíčové.
Poziční argumenty
Poziční argumenty jsme viděli výše. Na jejich pozici se dosadí argument
na stejné pozici při volání funkce. Tyto argumenty nemají danou základní
hodnotu. Jejich syntaxe je jednoduchá: nazev_argumentu
.
Klíčové argumenty
Klíčové argumenty mají již předem nastavenou hodnotu, kterou lze
změnit. Na rozdíl od argumentů pozičních jsou ty klíčové
označeny klíčovým slovem (mají svůj název). Tyto
argumenty nemusí být při volání funkce zadány v pořadí, ve kterém jsou
deklarovány. Navíc je nemusíme při volání funkce inicializovat (určit
jejich hodnotu). V takovém případě se použije jejich výchozí
hodnota. Syntaxe vypadá takto:
nazev_argumentu=hodnota
.
Všechny poziční argumenty musí být deklarovány před těmi klíčovými!
Jako příklad si ukažme vylepšenou verzi funkce mocnina:
def mocnina(cislo, exponent=2): # Cislo je poziční argument, exponent je klíčový argument
cislo = cislo ** exponent
return cislo
print(mocnina(2))
První argument je poziční, druhý je klíčový. Nyní můžeme volat
mocnina(1)
, mocnina(1, exponent=2)
, případně
mocnina(1, 2)
a dostaneme stejný výsledek.
Navíc můžeme umocnit 3
na 4
–
mocnina(3, exponent=4)
. To již však musíme hodnotu exponentu
specifikovat:
def mocnina(cislo, exponent=2):
cislo = cislo ** exponent
return cislo
print(mocnina(3, exponent=4))
Obecně se poziční argumenty zapisují jako args
a klíčové jako kwargs
.
Zvláštní vlastností Pythonu je to, že klíčový argument (kwarg) můžeme zadávat i jako obyčejný argument (arg), pokud jeho pořadí odpovídá deklaraci funkce.
Jako příklad si ještě jednou ukažme naši funkci
mocnina()
:
def mocnina(cislo, exponent=2): # Cislo je poziční argument, exponent je klíčový argument
cislo = cislo ** exponent
return cislo
print(mocnina(3, 4)) # Zde jsme zavolali klíčový argument 'exponent' jako běžný poziční argument
Operátor *
V Pythonu operátor *
neslouží jen pro násobení. Když jej
použijeme v definici funkce, umožňuje nám zachytávat proměnný počet
pozičních argumentů. Tímto způsobem můžeme flexibilně pracovat s
různým množstvím vstupních hodnot. Uveďme si příklad:
def nejaka_funkce(*pozicni_argumenty): pass
V tomto případě se všechny zadané poziční argumenty předané funkci
shromáždí do n-tice (datový typ podobný seznamu) s názvem
pozicni_argumenty
. S touto n-ticí můžeme dále pracovat,
například ji procházet pomocí cyklu for
:
def soucin(*cisla):
vysledek = 1
for cislo in cisla:
vysledek = vysledek * cislo
return vysledek
print(soucin(2, 3, 4, 5))
Když tedy zavoláme funkci soucin(2, 3, 4, 5)
, všechna čtyři
čísla jsou sbalena do n-tice cisla
a v těle funkce jsou
následně přístupná jako cisla[0]
, cisla[1]
,
cisla[2]
a cisla[3]
. N-tici pak jednoduše projdeme
pomocí cyklu for
. Díky této vlastnosti můžeme funkci
soucin()
volat s libovolným počtem čísel.
Když použijeme *
v definici funkce bez
následujícího jména proměnné, říkáme tím, že funkce smí od
tohoto místa dál přijímat pouze klíčové argumenty.
Jinými slovy, všechny argumenty za *
musí být při volání
funkce specifikovány jako klíčové.
Ukažme si to na konkrétním příkladu:
def moje_funkce(*, prvni_klicovy_arg=1, druhy_klicovy_arg=1): print(prvni_klicovy_arg, druhy_klicovy_arg)
V tomto případě nám funkce nedovolí použít poziční argumenty. Musíme je specifikovat jako klíčové argumenty:
moje_funkce(prvni_klicovy_arg=5, druhy_klicovy_arg=10) # výstup: 5 10 # Následující volání by vyvolalo chybu: # moje_funkce(5, 10) # TypeError: moje_funkce() takes 0 positional arguments but 2 were given
Chceme-li použít zápis s operátorem *
pro
klíčové argumenty, napíšeme dvě hvězdičky.
Rekurze
Pojem rekurze označuje zápis kódu, kdy funkce volá samu sebe. Rekurzi můžeme použít například pro výpočet faktoriálu. Uveďme si příklad:
def faktorial(cislo): if cislo > 0: return faktorial(cislo - 1) * cislo else: return 1
Při rekurzi si musíme dát pozor, aby se funkce někdy ukončila. Jinak program spadne kvůli přetečení zásobníku. Rekurze podrobně vysvětlujeme u algoritmu faktoriálu.
Typování funkcí
Pokud chceme explicitně deklarovat typ parametrů funkcí a jejich
návratové hodnoty, použijeme k tomu operátor ->
. Podívejme
se na příklad:
def generuj_ahoj(jmeno: str) -> str: # Návratová hodnota funkce bude str return "Ahoj, " + jmeno + "!"
V rozsáhlejších projektech je typování obzvláště užitečné pro udržení kvality a konzistence kódu. Usnadňuje spolupráci více vývojářů a pomáhá při zachování dobré architektury.
Mimochodem, podobně postupujeme i u proměnných. Jen operátor je jiný.
Pokud chceme explicitně deklarovat typ proměnné, použijeme k tomu
dvojtečkový operátor :
:
text: str = "Ahoj světe!"
Nyní je jasně řečeno, že text
je typu str
. V
tomto případě to lze poznat podle hodnoty "Ahoj světe!"
, takže
je tu takové označení trochu nadbytečné. Avšak v programech v praxi bývá
spousta míst, kde to již tak jasné není. Potom má deklarování typu
smysl.
Úprava výstupu funkce
print()
Nyní se naučíme upravit si funkci print()
. Už jsme se s tím
částečně setkali v předchozích lekcích, když jsme upravovali, čím
funkce print()
ukončí řádek. Nyní se na ni podíváme
podrobněji. Nejprve se zaměříme na její dva klíčové argumenty:
sep
– Argument udává mezery mezi jednotlivými prvky (pozičními argumenty). Normálně je nastavený na mezeru (" "
).end
– Tento argument definuje, čím se zápis ukončí. Normálně se tak děje znakem nového řádku ("\n"
).
Pojďme si to vyzkoušet na příkladu:
print(1, 2, 3, "a", sep="-")
print("Žádná nová řádka", end=" ")
print("nebude.", end=" ")
Ošetření chyb
Ve většině námi dosud vytvořených programů jsme uživateli umožnili spáchat chybu při číselném vstupu. Pokud by tedy uživatel zadal namísto čísla např. písmeno, náš program by spadl.
Nyní si tedy ukážeme, jak takovéto chyby ošetřit. Ošetření se
provádí pomocí bloku try
–except
:
try: # Blok příkazů except jmeno_prvni_vyjimky: # Blok příkazů except jmeno_dalsi_vyjimky: # Blok příkazů else: # Tento blok příkazů se provede, pokud nenastane žádná chyba. finally: # Blok příkazů, který se provede vždy.
Pokud chceme zachytit i chybovou zprávu, musíme kód upravit takto:
except jmeno_vyjimky as chyba: # Text výjimky se uloží do proměnné chyba.
Chybám se v Pythonu (a v objektových jazycích obecně) říká výjimky. Ty základní jsou následující:
SyntaxError
– chyba je ve zdrojovém kódu,ZeroDivisionError
– pokus o dělení nulou,TypeError
– nesprávné použití datových typů, např. sčítání řetězce a čísla,ValueError
– nesprávná hodnota.
Všechny výjimky najdeme v dokumentaci Pythonu.
Ukažme si jednoduchý příklad, který využije všechna klíčová slova
bloku try
–except
:
pokracovat = True while pokracovat: try: delenec = float(input("Zadejte číslo, které chcete dělit: ")) delitel = float(input("Zadejte číslo, kterým chcete dělit: ")) vysledek = delenec / delitel except ZeroDivisionError: print("Dělení nulou není možné!") except ValueError: print("Byla zadána neplatná hodnota!") else: print(f"Výsledek dělení je: {vysledek}") finally: opustit = input("Chcete ukončit program? (ano/ne): ").lower() if opustit == "ano": pokracovat = False
Kód si vysvětlíme. V tomto kódu se program uživatele zeptá, která
čísla chce dělit. Pokud uživatel zkusí dělit nulou, program zachytí a
vyhodí chybovou zprávu. Stejně tak dojde k zachycení chyby v případě, že
uživatel vloží něco jiného než číslo. Blok else
jsme
použili pro výpis výsledku dělení v případě, že nedojde k výjimce.
Blok finally
pak umožňuje uživateli opustit cyklus a ukončit
program.
Vylepšení kalkulačky
Nyní máme dost znalostí k tomu, abychom mohli znovu vylepšit naši kalkulačku. Nejprve si připomeňme její dosavadní kód:
print("Vítejte v kalkulačce") pokracovat = "ano" while pokracovat == "ano": a = float(input("Zadejte první číslo: ")) b = float(input("Zadejte druhé číslo: ")) print("Zvolte si operaci:") print("1 - sčítání") print("2 - odčítání") print("3 - násobení") print("4 - dělení") volba = int(input()) vysledek = 0.0 match volba: case 1: vysledek = a + b case 2: vysledek = a - b case 3: vysledek = a * b case 4: if b != 0: vysledek = a / b else: print("Nulou nelze dělit!") vysledek = "N/A" if 0 < volba < 5: print(f"Výsledek: {vysledek}") else: print("Neplatná volba") pokracovat = input("Přejete si zadat další příklad? [ano/ne]: ") print("Děkuji za použití kalkulačky, aplikaci ukončíte libovolnou klávesou.")
Pusťme se do vylepšování kódu. Nejprve naprogramujeme "uživateli vzdornou" funkci na získání čísla ze vstupu:
def nacti_cislo(text_zadani, text_chyba): cislo_nezadano = True while cislo_nezadano: try: cislo = float(input(text_zadani)) cislo_nezadano = False except ValueError: print(text_chyba) return cislo
Program se tak dlouho opakuje v cyklu, dokud od uživatele nezíská
správný vstup. Řádek s float()
převede řetězec na desetinné
číslo.
Dále si naprogramujeme funkci, která umožní zadat pouze ano
a ne
:
def dalsi_priklad(): odpoved_nevalidni = True while odpoved_nevalidni: odpoved = input("\nPřejete si zadat další příklad? ano / ne: ").lower() if odpoved == "ano" or odpoved == "ne": odpoved_nevalidni = False else: print("Prosím, odpovězte 'ano' nebo 'ne'.") return odpoved == "ano" # Když bude v proměnné odpoved uložen řetězec "ano", funkce vrátí hodnotu True. Jinak vrátí False.
Dále musíme ošetřit, co se stane po volbě operace. Uživatel by mohl
zadat například číslo mimo interval <1;4>
a tím zvolit
neexistující operaci. Případně zadat čísla 5
a
0
a poté zvolit dělení. Náš program by pak skončil s
výjimkou ZeroDivisionError
. Musíme se tedy o tyto potenciální
chyby postarat. Je také nevhodné, aby funkce tiskla výsledek, funkce má
vždy vracet hodnotu:
def proved_operaci(a, b): operace = 0 while operace not in [1, 2, 3, 4]: print("1 - sčítání") print("2 - odčítání") print("3 - násobení") print("4 - dělení") # Abychom nemuseli znovu při vybírání operace ošetřovat uživatelský vstup, využijeme zde naši již vytvořenou funkci # Její výstup (typu float) přetypujeme na int operace = int(nacti_cislo("Zadejte číslo pro vybranou operaci: ", "Neplatný vstup. Zadejte prosím číslo.")) if operace == 4 and b == 0: print("Nulou nelze dělit! Zkuste jinou operaci.") operace = 0 # Reset operace kvůli dělení nulou elif operace < 1 or operace > 4: print("Neplatná volba. Zadejte číslo odpovídající vybrané operaci.") # Ošetření neplatného vstupu match operace: case 1: return a + b case 2: return a - b case 3: return a * b case 4: return a / b
Hlavní cyklus programu se s našimi novými funkcemi značně zjednoduší:
print("Kalkulačka\n") pokracovat = True while pokracovat: prvni_cislo = nacti_cislo("Zadej číslo: ", "Neplatné číslo!\n") druhe_cislo = nacti_cislo("Zadej číslo: ", "Neplatné číslo!\n") vysledek_vypoctu = proved_operaci(prvni_cislo, druhe_cislo) print(f"Výsledek: {vysledek_vypoctu}") pokracovat = dalsi_priklad() print("Děkuji za použití kalkulačky, aplikaci ukončíte klávesou Enter.") input()
Celý kód jsme rozdělili do přehledných a jednoduchých bloků. Případné úpravy v kódu pak budou výrazně jednodušší. Celý kód je také daleko čitelnější. Toto je podstata funkcionálního paradigmatu!
V příští lekci, Matematické funkce v Pythonu a knihovna math, se podíváme na užitečné matematické funkce.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 1720x (1.39 kB)
Aplikace je včetně zdrojových kódů v jazyce Python