Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 17 - 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) - 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á 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 kterých se píší názvy jednotlivých argumentů. Argument funkce v programovacím jazyce Python je hodnota nebo proměnná, kterou předáváme funkci, aby ji mohla použít během svého vykonávání. Když definujeme funkci, specifikujeme, jaké argumenty může přijmout, a 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á takto:

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ů samozřejmě může být 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í dánu 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 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í, v jaké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 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), ale to už 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 ta, ž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šechny čtyři čísla jsou sbaleny 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á sama sebe. Rekurzi můžeme použít například na 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 upadne na 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. Zde by to bylo stejně poznat podle hodnoty "Ahoj světe!", takže je tu takové označení trochu zbytečné. V programech v praxi ale 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:

while True:
    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":
            break

Kód si vysvětlíme. V tomto kódu se uživatel zeptá, jaká čísla chce dělit, a pokud 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 nekonečný cyklus a ukončit program.

Vylepšení kalkulačky

Teď máme dost znalostí na to, abychom znovu vylepšili 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")
    nezadano = True
    while nezadano:
        odpoved = input("\nPřejete si zadat další příklad? ano / ne: ").lower()
        if odpoved == "ano":
            nezadano = False
        elif odpoved == "ne":
            nezadano = False
            pokracovat = False
        else:
            pass
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):
    spatne = True
    while spatne:
        try:
            cislo = float(input(text_zadani))
            spatne = False
        except ValueError:
            print(text_chyba)
        else:
            return cislo

Program se tak dlouho opakuje v cyklu, dokud od uživatele nezískáme 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():
    while True:
        odpoved = input("\nPřejete si zadat další příklad? ano / ne: ").lower()
        if odpoved == "ano":
            return True
        elif odpoved == "ne":
            return False
        else:
            print("Prosím, odpovězte 'ano' nebo 'ne'.")

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):
    while True:
        print("1 - sčítání")
        print("2 - odčítání")
        print("3 - násobení")
        print("4 - dělení")

        try:
            vstup = int(input("Zadejte číslo pro vybranou operaci: "))

            match vstup:
                case 1:
                    return a + b
                case 2:
                    return a - b
                case 3:
                    return a * b
                case 4:
                    if b != 0:
                        return a / b
                    else:
                        print("Nulou nelze dělit!")
                        continue  # Pokračování ve smyčce a nový vstup od uživatele
                                  # Použití continue zde už není nutné, jen dáváme najevo, že pokračujeme další iterací smyčky
                case _:
                    print("Neplatná volba. Zadejte číslo odpovídající vybrané operaci.")
                    continue  # Pokračování ve smyčce a nový vstup od uživatele
                              # Použití continue zde už není nutné, jen dáváme najevo, že pokračujeme další iterací smyčky

        except ValueError:
            print("Neplatný vstup. Zadejte prosím číslo.")
            continue  # Pokračování ve smyčce a nový vstup od uživatele
                      # Použití continue zde už není nutné, jen dáváme najevo, že pokračujeme další iterací smyčky

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 lépe čitelný. Toto je podstata funkcionálního paradigmatu!

V příští lekci, Matematické funkce v Pythonu a knihovna math, se podíváme na matematické funkce a základní kurz úplně zakončíme.


 

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 1485x (1.19 kB)
Aplikace je včetně zdrojových kódů v jazyce Python

 

Předchozí článek
Cykly v Pythonu podruhé - Výraz pass, break a continue
Všechny články v sekci
Základní konstrukce jazyka Python
Přeskočit článek
(nedoporučujeme)
Matematické funkce v Pythonu a knihovna math
Článek pro vás napsal gcx11
Avatar
Uživatelské hodnocení:
561 hlasů
(^_^)
Aktivity