comment up next how to end end

4. Podmínky a funkce

  1. Podmínky if.. elif.. else
  2. Vnořené podmínky
  3. Funkce s podmínkami
  4. Booleovské funkce
  5. Rozvoj programu
  6. Schema zásobníku
  7. Styl programu
  8. Testování s 'doctestem'
  9. Glosář
  10. Cvičení

4.1   Podmínky if.. elif.. else

K napsání užitečného programu potřebujeme mít možnost měnit chování programu v závislosti na jistých podmínkách. Tuto možnost nám dávají podmíněné příkazy

Podmínka if .. else

Touto podmínkou realizujeme tak zvané alternativní provedení, při němž existují dvě možnosti a podmínka určí, která z nich se provede:

x = int(input("Zadej číslo: "))

if x % 2 == 0:
    print (x, "je sudé") 
else: 
    print (x, "je liché") 

Je-li zbytek po dělení dvěma roven nule, potom víme, že x je sudé a program tuto zprávu zobrazí. Není-li tato podmínka pravdivá, provede se druhá sada příkazů. Protože podmínka může být pouze pravdivá nebo nepravdivá, provede se určitě jedna z obou možností.

Alternativám říkáme větve protože jsou rozvětvením toku programu.

Mimochodem, kdybychom potřebovali často posuzovat "lichost" či "sudost" čísel, mohli bychom si tento prográmek "zabalit" do funkce:
def print_parity(x):
    if x%2 == 0:
        print(x, "je sudé")
    else:
        print(x, "je liché")

Pro každou hodnotu x vrátí print_parity příslušnou zprávu. Při volání můžeme zadat jako argument jakýkoliv celočíselný výraz:

>>> print_parity(17)
17 je liché
>>> y = 41
>>> print_parity(y + 1)
42 je sudé

Podmínka if..

Realizace alternativního provedení má zkrácenou verzi, která spočívá v tom, že vypustíme část else:

x = int(input("Zadej číslo: "))

if x >= 0:
    print ("odmocnina",x,"=" x**0.5)
print ("Nevadí, jedeme dál.")	

Stejně jako funkce a jiné složené příkazy se příkaz if skládá ze záhlaví a těla. Záhlaví začíná klíčovým slovem if, pokračuje booleovským výrazem neboli podmínkou a končí dvojtečkou.

Poté, co se podmínka vyhodnotí jako pravdivá, provede se odsazená část složeného příkazu.

Není-li podmínka splněna, pokračuje tok výpočtu na prvním následujícím neodsazeném řádku.

Podmínka if .. elif .. else

Někdy je více než dvě možností a my potřebujeme více než dvě větve. Použijeme seriově uspořádané (zřetězené) podmínky:

if x < y:
    print(x, "is less than", y)
elif x > y:
    print(x, "is greater than", y)
else:
    print(x, " and ", y, " are equal")

elif je zkratka "else if". Opět je provedena pouze jedna větev. Počet elif není omezen, příkaz else však smí být pouze jeden a musí být uveden jako poslední.

Podmínky jsou zkoumány jedna za druhou. Je-li první nepravdivá, prověřuje se další. Je-li některá pravdivá, provede se bezprostředně následující větev programu. Jestliže je pravdivých podmínek více, provede se jen první z nich.

4.2   Vnořené podmínky

Jedna podmínka může být vnořena do druhé. Předchozí trojité větvení můžeme realizovat také takto:

if x == y:
    print(x, "and", y, "are equal")
else:
    if x < y:
        print(x, "is less than", y)
    else:
        print(x, "is greater than", y)

Vnější podmínka má dvě větve. První větev obsahuje prostý příkaz k tisku. Druhá větev obsahuje další podmínku if, která má své vlastní dvě větve. Obě tyto větve jsou prostým voláním funkce print.

I když odsazení příkazů činí strukturu zápisu zřejmou, vnořené podmínky se velmi rychle stávají méně přehledné. Pokud můžeme, tak se jim raději vyhneme.

Vnořené podmínky můžeme často zjednodušit logickými operátory. Následující kód bude v další ukázce přepsán pro použití jen jedné podmínky:

if 0 < x:
    if x < 10:
        print("x is a positive single digit.")

K volání funkce print dojde pouze tehdy, splníme-li obě podmínky, můžeme tedy použít operátor and.

if 0 < x and x < 10:
    print("x is a positive single digit.")

Pro toto docela běžné seskupení podmínek poskytuje Python alternativní syntaxi, která je podobná zápisu v matematice:

if 0 < x < 10:
    print("x is a positive single digit.")

Tato podmínka je významově stejná jako složený booleovský výraz s vnořenou podmínkou.


4.3 Funkce s podmínkami

Příkaz return s výhodou použijeme ve funkci s větvenými podmínkami, jako například:

def absolute_value(x):
    if x < 0:
        return -x
    else:
        return x

Protože jsou tyto příkazy v alternativních podmínkách, provedou se pouze jednou - jakmile se jeden z příkazů return provede, funkce končí bez provádění následných příkazů.

Naši funkci můžeme zapsat také stručněji. Místo příkazu else dáme přímo příkaz return.

def absoluteValue(x):
    if x < 0:
        return -x
    return x

Přesvědčme se, že tato fce pracuje stejně jako ta předchozí.

Kód, který je zapsán za příkazem return i kterékoliv další místo, kam se tok programu nedostane, se nazývá mrtvý kód.

Je dobré aby každá možná cesta funkcí narazila na příkaz return. V následující verzi absolute_value toto zajištěno není:

def absolute_value(x):
    if x < 0:
        return -x
    elif x > 0:
        return x

Tato verze není zcela správná, protože bude-li x rovno nule, žádná z podmínek není pravdivá a funkce končí, aniž by narazila na příkaz return; výslednou hodnotou je speciální hodnota zvaná None:

>>> print(absoluteValue(0))
None

None je jedinečná hodnota typu NoneType:

>>> type(None)
<class 'NoneType' >

4.4 Booleovské funkce

Vestavěná funkce bool vrací booleovskou hodnotu čísel a řetězců:

>>> bool(1)
True
>>> bool(0)
False
>>> bool("No !")
True
>>> bool("")
False
>>> bool(3.14159)
True
>>> bool(0.0)
False

U číselných typů generují nulové hodnoty výstup False, nenulové hodnoty výstup True. Prázdné řetězce dávají False, neprázdné řetězce dávají True.

Za booleovskou funkci lze považovat jakákokoliv funkci, která vrací hodnotu True nebo False:

def is_divisible(x, y):
    if x % y == 0:
        return True
    else:
        return False 

Jméno této funkce je is_divisible (je dělitelné). Je obvyklé dávat booleovským funkcím jména, která zní jako otázka "ano či ne". Funkce is_divisible vrací buď True nebo False podle toho, zda x je či není dělitelné y.

Funkci můžeme ještě zjednodušit využitím faktu, že podmínka příkazu if je sama booleovským výrazem. Výsledek získáme přímo i po vypuštění příkazu if:

def is_divisible(x, y):
    return x % y == 0

Následující seance předvádí novou funkci v akci:

>>> is_divisible(6, 4)
False
>>> is_divisible(6, 3)
True

Booelovské funkce jsou často používány v podmíněných příkazech:

if is_divisible(x, y):
    print("x is divisible by y")
else:
    print("x is not divisible by y")

4.5 Rozvoj programu

Nyní bychom bychom měli být schopni poznat pouhým pohledem na zápis funkce co má provádět. Pokud jsme prováděli cvičení, sami jsme několik menších funkcí napsali. Při psaní větších funkcí nám mohou nastat potíže kvůli významovým chybám a chybám při běhu programu.

Abychom se vyrovnali s rostoucí složitostí programů seznámíme se s technikou, zvanou přírůstkový rozvoj. Jeho cílem je vyloučení dlouhých seancí při odstraňování chyb tím, že se postupně přidávají a testují krátké úseky kódu.

Jako příklad předpokládejme, že chceme nalézt vzdálenost dvou bodů, daných souřadnicemi (x1, y1) a (x2, y2). Vzdálenost podle Pythagorovy věty je:

Nejprve musíme uvážit jak by měla funkce distance vypadat. Jinými slovy, jaké budou vstupy (parametry) a jaký bude výstup (hodnota return)?

V našem případě musíme na vstupu zadat čtyři souřadnice dvou bodů. Výstupní hodnotou bude jejich vzdálenost s hodnotou typu float.

Již můžeme psát obrys funkce:

def distance(x1, y1, x2, y2):
    return 0.0

Je zřejmé, že tato verze žádnou vzdálenost nepočítá, neboť vždycky vrátí nulu. Je ale skladebně (syntakticky) správná, poběží a můžeme jí testovat před tím než ji zkomplikujeme.

Abychom ji otestovali, zavoláme ji pro jednoduché hodnoty:

>>> distance(1, 2, 4, 6)
0.0

Hodnoty jsme vybrali tak, aby vodorovná vzdálenost bodů byla 3, svislá 4 a výsledná vzdálenost 5 (což je přepona pravoúhlého trojúhelníka). Při testování funkce je užitečné znát správný výsledek předem.

V této chvíli jsme se přesvědčili, že funkce je syntakticky správná a můžeme začít přidávat řádky kódu. Po každé malé změně funkci znovu otestujeme. Objeví-li se v kterémkoli místě chyba, budeme vědět kde musí být – v posledním přidaném řádku.

Prvním logickým krokem ve výpočtu bude nalézt rozdíly x2-x1 a y2-y1. Tyto hodnoty uložíme do dočasných proměnných dx, dy a vytiskneme je.

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    print("dx is", dx)
    print("dy is", dy)
    return 0.0

Je-li funkce v pořádku, měly by výsledky být 3 a 4. Jestliže ano, ověřili jsme si, že náš dílčí program pracuje správně. Pokud ne, potřebujeme prověřit jenom několi málo řádků.

Nyní sečteme součet čtverců pro dx a dy.

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    print("dsquared is: ", dsquared)
    return 0.0

Všimněme si, že jsme odstranili evokace funkce print, které jsme zapsali v předchozím kroku. Takovéto dočasné části kódu říkáme brlení (scaffolding), protože pomáhá při sestavení programu, ale není součástí finálního výsledku.

Opět necháme proběhnout program a zkontrolujeme výstup (což by mělo být 25).

Konečně, s použitím zlomkového exponentu 0.5 pro nalezení odmocniny, můžeme spočítat výsledek:

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    result = dsquared**0.5
    return result

Pokud nám to chodí správně, jsme hotovi.

Když začínáme, měli bychom přidávat jen po jednom či dvou řádcích. Po získání jisté zkušenosti můžeme přidávat po větších dávkách. Přírůstkový rozvoj nám v každém případě ušetří mnoho času při odstraňování chyb.

Klíčové aspekty tohoto postupu jsou:

  1. Začneme s fungujícím programem a děláme malé přírůstkové změny. V každém okamžiku víme přesně kde může být případná chyba.
  2. Použijeme dočasných proměnných k uložení mezivýsledků; tak je můžeme vytisknout a zkontrolovat.
  3. Když program pracuje bezchybně, můžeme odstranit nepotřebné lešení, případně uspořádat některé příkazy do složených výrazů, ovšem tak, aby program zůstal dostatečně přehledný.

Alternativní tvar funkce pro určení vzdálenosti dvou bodů jsme vytvořili již v kapitole 3.8:

import math
def distance(x1,y1, x2,y2):
    return math.sqrt((x2-x1)**2 + (y2-y1)**2)

4.6 Schema zásobníku

V kapitole 3.6 a 3.10 jsme definovali funkci cat_twice, která obsahovala volání funkce print_twice.

Abychom mohli sledovat, která proměnná je ve které funkci použita, ukážeme si schematické zobrazení zásobníku (stack diagram).

Každá funkce je reprezentována svým rámcem. Rámec tvoří jméno funkce s přilehlým obdélníkem, ve kterém jsou uvedeny její parametry a proměnné. Schéma zásobníku pro předchozí příklad vypadá takto:

Uspořádání schematu ukazuje průběh provádění. Funkce print_twice byla volána funkcí cat_twice a cat_twice byla volána funkcí __main__, což je speciální jméno pro nejvrchnější funkci. Když vytvoříme proměnnou vně jakékoliv funkce, patří do __main__!

Každý parametr se vztahuje k téže hodnotě jako jeho odpovídající argument. Tudíž part1 má stejnou hodnotu jako chant1, part2 má stejnou hodnotu jako chant2 a bruce má stejnou hodnotu jako cat.

Objeví-li se chyba při volání funkce, vytiskne Python jména všech funkcí, které se na tomto volání podílely; počínaje poslední, konče první zúčastněnou.

Vyzkoušejme si to. Vytvoříme skript se jménem test2.py, který vypadá takto:

def print_twice(bruce):
    print(bruce, bruce) 
    print(cat)

def cat_twice(part1, part2):
    cat = part1 + part2
    print_twice(cat)
chant1 = "Pie Jesu domine, "
chant2 = "Dona eis requim."
cat_twice(chant1, chant2)

Do funkce print_twice jsme přidali volání print cat, ale tam není proměnná cat definována. Běh skriptu vyvolá takovéto chybové hlášení:

Traceback (innermost last):
  File "test2.py", line 11, in <module>
    cat_twice(chant1, chant2)
  File "test2.py", line 7, in catTwice
    print_twice(cat)
  File "test2.py", line 3, in printTwice
    print(cat)
NameError: global name 'cat' is not defined

Tento seznam funkcí se nazývá zpětný záznam (traceback). Říká nám ve kterém souboru se chyba vyskytla, který řádek a které funkce byly právě prováděny. Také označí číslo řádku na kterém k chybě došlo

Všimněme si podobnosti mezi zpětným záznamem a zobrazením zásobníku, není náhodná.


4.7 Styl programu

Dobrá čitelnost programu je pro programátora velmi důležitá, protože programy jsou častěji čteny a upravovány než psány. Všechny příklady kódů v této knize jsou psány ve shodě s manuálem Python Enhancement Proposal 8 (PEP 8), v němž jsou uvedeny pokyny pro stylové úpravy kódu.

Až naše programy budou rozsáhlejší, budou i naše potřeby stylu naléhavější ale několik pokynů bude užitečných už teď:


4.8 Testování s 'doctestem'

V dnešní době je při rozvoji programu velmi používané automatické testování vybraných úseků (jednotek) zdrojového kódu - jednotkové testování (unit testing).

Pro toto testování má Python vestavěný modul doctest. Zkoumané vzorky kódu (doctesty) se umístí do dokumentačního řetězce na první řádek skriptu nebo těla funkce. V každém vzorku je na prvním řádku výsek seance v interaktivním režimu, na druhém řádku je očekávaná odezva.

Modul doctest automaticky spustí příkaz začínající >>> a jeho výstup porovná s následujícím řádkem.

Následující ukázku si uložte do souboru ch05_doctest.py, který spustíte z příkazového řádku konzoly:

def is_divisible_by_2_or_5(n):
    """
    >>> is_divisible_by_2_or_5(8)
    True
    """

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Poslední tři řádky spouští celou parádu. Umisťujeme je na konec každého souboru, který obsahuje doctesty.

Spuštění skriptu vyprodukuje následující výstup:

> python ch05_doctest.py 
******************************************************************
File "ch05_doctest.py", line 3, in __main__.is_divisible_by_2_or_5
Failed example:
    is_divisible_by_2_or_5(8)
Expected:
    True
Got nothing
******************************************************************
1 items had failures:
   1 of   1 in __main__.is_divisible_by_2_or_5
***Test Failed*** 1 failures.

Předvedli jsme si ukázku neúspěšného testu. V textu se říká, že očekávaná hodnota True nebyla nalezena. Není divu, tělo volané funkce zatím ve skriptu chybí.

Doplníme si ukázku o další jednotky a hlavně o tělo funkce:

def is_divisible_by_2_or_5(n):
    """
    >>> is_divisible_by_2_or_5(8)
    True
    >>> is_divisible_by_2_or_5(7)
    False
    >>> is_divisible_by_2_or_5(5)
    True
    >>> is_divisible_by_2_or_5(9)
    False
    """

    return n % 2 == 0 or n % 5 == 0

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Pokud procedura zjistí shodu mezi zkoumanými vzorky a zadanými výsledky, reaguje "mlčením". Pokud narazí na rozpor, spustí rozsáhlé chybové hlášení, zejména doplníme-li evokaci skriptu atributem -v (verbose).

4.9 Glosář

prozatimní proměnná (temporary variable)
Proměnná použitá k uložení přechodné hodnoty u složitějšího výpočtu.
mrtvý kód (dead code)
Část programu, která se nikdy neprovede, často proto, že se nacházi za příkazem return
přírůstkový rozvoj (incremental development)
Postupné rozšiřování programu se záměrem testovat najednou jenom menší objem kódu.
brlení (scaffolding)
Kód, použitý při rozvoji programu, který není součástí konečné verze.
zpětný záznam (traceback)
Seznam prováděných funkcí, který se zobrazí na obrazovce, když se vyskytne chyba při běhu programu. Zpětný záznam je také uváděn jako záznam zásobníku, protože probírá funkce v pořadí, ve kterém byly do zásobníku běhu programu zařazeny (runtime stack).
 jednotkové testování (unit testing)
Automatická procedura používaná k ověření správné činnosti jednotlivých úseků kódu. V Pythonu se pro tento účel používá vestavěný modul doctest.

4.10 Cvičení

Všechna cvičení budou postupně přidávána do souboru se jménem ch05.py. Na konec totoho skriptu se umístí následující kód:

if __name__ == '__main__':
    import doctest
    doctest.testmod()
Po zapsání každého cvičení spustíme skipt, abychom se přesvědčili, že naše nová funkce projde testem.

  1. Napište funkci compare, která vrací 1 pro a>b, 0 pro a==b a -1 pro a<b.
    def compare (a, b):
        """
        >>> compare(5,4)
        1
        >>> compare(7,7)
        0
        >>> compare(2,3)
        -1
        """
        # Zde napište svou fci
    
  2. Použijte přírůstkový rozvoj k vytvoření funkce hypotenuse, která vrací délku přepony pravoúhlého trojúhelníka pro zadané délky odvěsen.
    def hypotenuse (a,b):
        """
        >>> hypotenuse(3,4)
        5.0
        >>> hypotenuse(12,5)
        13.0
        >>> hypotenuse(7,24)
        25.0
        """
        # Zde napište svou fci
    
  3. Napište funkci slope(x1,y1,x2,y2), která vrací úhel sklonu přímky procházející body (x1,y1) a (x2,y2). Funkce musí vyhovět následujícím testům:
    def slope (x1,y1,x2,y2):
        """
        >>> slope(5,3,4,2)
        1.0
        >>> slope(1,2,3,2)
        0.0
        >>> slope(1,4,1,2)
        kolmice k ose 'x'           Ošetřená výjimka!
        >>> slope(2,4,1,2)
        2.0
        """
        # Zde napište svou fci
    
    Funkci slope použijte v další funkci intercept(x1,y1,z2,y2), která vrací ypsilonovou pořadnici přímky, procházející bodem (x1,y1) a (x2,y2).
    def intercept (x1,y1,x2,y2):
        """
        >>> intercept(1,6,3,12)
        3.0
        >>> intercept(6,1,1,6)
        7.0
        >>> intercept(4,6,12,8)
        5.0
        """
        # Zde napište svou fci
    
  4. Napište funkci is_even(n), která přijme celé číslo jako argument a vrací True, je-li číslo sudé a False, je-li liché. Vytvořte vlastní doctesty.
  5. Nyní napište funkci is_odd(n), která vrací True, je-li n liché a False, je-li liché. Upravte fci tak, že k určení sudosti / lichosti volá fci is_even. Vytvořte vlastní doctesty.
  6. Napište funkci is_factor(f,n) se dvěma parametry f a n, která vrátí True, je-li f dělitelem n a False, není-li.
    def is_factor (f,n):
        """
        >>> is_factor(3,12)
        True
        >>> is_factor(5,12)
        False
        >>> is_factor(2,14)
        True
        """
        # Zde napište svou fci
    
  7. Napište funkci is_multiple(m,n) se dvěma parametry m a n, která vrátí True, je-li m násobkem n a False, není-li.
    def is_multiple( (m,n):
        """
        >>> is_multiple(12,3)
        True
        >>> is_multiple(12,4)
        True
        >>> is_multiple(12,5)
        False
        """
        # Zde napište svou fci
    
    Šlo by použít fci is_factor při definici fce is_multiple?
  8. Napište tělo funkce f2c(t), která převede teplotu ve stupních Fahenheitových na stupně Celsiovy a vrátí ji jako celé číslo. Použijete vestavěnou fci round(n). Informaci o této funkci získáte zadáním >>> round.__doc__ v konzole Pythonu.
    def f2c( (t):
        """
        >>> f2c(212)
        100
        >>> f2c(32)
        0
        >>> g2c(-40)
        -40
        >>> g2c(37)
        3
        """
        # Zde napište svou fci
    
  9. Napište funkci c2f(t), která převede teplotu ve stupních Celsiových na stupně Fahrenheitovy.

comment up next how to end end