![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
K efektivnímu psaní počítačových programů potřebuje programátor vyvinout schopnost zaznamenat průběh běžícího programu. Znamená to "stát se počítačem" a sledovat průběh provádění, zaznamenávajíc stavy všech proměnných a všechny výstupy, které program generuje po provedení jednotlivých instrukcí.
Abychom porozuměli tomuto procesu, sledujme volání funkce sequence(n) z kapitoly 4.1. Abychom měli tuto funkci na očích, vytvořte v IDLE soubor sequence.py, který si k dalšímu výkladu otevřete.
Průběh volané funkce pro n = 3 si rozepíšeme do tabulky, v níž záhlaví sloupců tvoří jednotlivé výrazy funkce.
V prvním sloupci jsou postupně posuzované hodnoty n, ve třetím sloupci jsou postupně tištěné hodnoty n:
n n!=1 print(n) n%2==0 n/2 n*3+1 --- ---- -------- ------ ---- ------ 3 T 3 F - 10 10 T 10 T 5 - 5 T 5 F - 16 16 T 16 T 8 - 8 T 8 T 4 - 4 T 4 T 2 - 2 T 2 T 1 - 1 F - - - -
Správnost naší sestavy si ověříme spuštěním souboru a voláním funkce pro n=3, jejíž výstup je tento:
>>> sequence(3) 3, 10, 5.0, 16.0, 8.0, 4.0, 2.0,
Provádění záznamu může být únavné a náchylné k chybám, je ale nezbytnou dovedností programátora. Z tohoto záznamu se dozvíme mnohé o tom, jak náš kód pracuje. Můžeme si povšimnout, že jakmile se n stane mocninou 2, potřebuje tělo smyčky log2(n) cyklů ke svému ukončení. Také vidíme, že konečná hodnota 1 se nevytiskne.
Nyní 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ěkolik 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:
Stejný 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)
Následující funkce spočítá počet číslic kladného celého čísla (>= 0):
def
num_digits(n):if
n == 0:return
1# specielní případ count = 0# počítadlo while
n: count += 1 n = n//10# celočíselné dělení return
count
Volání num_digits(710) vrátí 3. V rámci cvičení (13.6) si tuto funkci upravíme pro všechna celá čísla.
V této funkci vidíme často používaný objekt, zvaný počítadlo (count). Proměnná count je zavedena s hodnotou 0 a potom zvětšována o 1 při každém splnění podmínky smyčky. Když smyčka skončí, obsahuje count celkový počet splnění podmínek, což je zde počet číslic zadaného čísla.
Postupně prováděné celočíselné dělení vypouští poslední číslici zadaného čísla.
Kdybychom chtěli počítat jenom číslice 0 nebo 5, pomůžeme si rozšířením podmínky před zvyšováním stavu počítadla:
def
num_0_and_5_digits(n):if
n == 0:return
1# nula je také číslice n =abs
(n) count = 0# počítadlo while
n: zbytek = n%10# další pomocná proměnná if
zbytek == 0or
zbytek == 5: count = count+1# případně count += 1 n = n//10# celočíselné dělení return
count
Jedním z vhodných použití smyčky je generování tabelárních dat. Předtím, než byly počítače běžně přístupné, musely být logaritmy, siny, kosiny a jiné funkce čísel počítány ručně. Pro ulehčení výpočtu obsahovaly matematické příručky dlouhé tabulky s uspořádanými hodnotami těchto funkcí. Vytváření tabulek bylo zdlouhavé, nudné a občas plné chyb.
Když se na scéně objevily počítače, jedna z prvních reakcí byla: "Sláva! Můžeme tvořit tabulky na počítači, který nedělá chyby." To se ukázalo být (většinou) pravdivé, ale i krátkozraké. Brzy nato se staly kalkulátory a počítače tak všudepřítomné, že se tabulky staly zbytečné.
Tedy téměř. Pro některé operace používají počítače tabulky hodnot jako přibližnou mezihodnotu, kterou potom v dalším výpočtu upřesňují. Někdy byly chybné právě tyto tabulky; nejproslulejší jsou ty, které Intel Pentium používal k výpočtu dělení v plovoucí řádové čárce.
Při sestavování tabulek se prováděly opakované výpočty pro monotónně měněné vstupní hodnoty. Příkladem opakovaného výpočtu budiž následující program, který vytvoří posloupnost hodnot v levém sloupci a příslušné mocniny čísla 2 v pravém:
x = 1 while x < 13: print(x, '\t', 2**x) x += 1
Řetězec '\t' zastupuje znak tab
, neboli tabulátor. Zpětné lomítko označuje začátek "escape" sekvence, což je pořadí několika určených znaků, které nahrazují neviditelné znaky např. pro tab nebo nový řádek ('\n').
"Escape" sekvence se může použít kdekoliv v řetězci; v našem příkladě je v řetězci pouze znak pro tab. (Jak zapíšeme zpětné lomítko v řetězci?)
Jak jsou postupně znaky a řetězce zobrazovány na obrazovce, jsou kontrolovány neviditelným hlídačem, zvaným kurzor, který zaznamenává jejich polohu.
Znak pro tabulátor posune kurzor doprava k nejbližší pomyslné zarážce. Tabulátory jsou vhodné pro tvorbu uspořádaných řádků, jako má tento výstup z předchozího programu:
>>> 1 2 2 4 3 8 4 16 5 32 6 64 7 128 8 256 9 512 10 1024 11 2048 12 4096 >>>
Protože mezi sloupci máme znaky tab, není pozice druhého sloupce závislá na počtu číslic v prvním sloupci.
Tabulka je uspořádání údajů, ve kterém je odečítáme v průsečíku řádku a sloupce. Dobrým příkladem je tabulka násobení. Řekněmež, že chceme zobrazit tabulku pro násobení čísel od 1 do 6.
Dobrým začátkem je zápis smyčky, která vytiskne všechny násobky dvou na jeden řádek:
i = 1 while i <= 6: print(2*i, "\t", end="") i += 1 print()
První řádek vytvoří proměnnou i, která působí jako počítadlo, neboli proměnnou smyčky. Při procházení smyčkou se hodnota i zvětšuje od 1 do 6. Při každém průchodu se vytiskne hodnota 2*i, následovaná mezerou tabelátoru.
Přechod na nový řádek ve funkci print potlačí instrukce end=" ". Po ukončení smyčky vytiskne funkce print() nový řádek.
Výstup programu je tento:
>>> 2 4 6 8 10 12 >>>
Výborně, jen tak dál. Dalším krokem bude zapouzdření a generalizace.
Zapouzdření je proces zabalení kódu do funkce, umožňující jeho snadné opakované použití. Příklad zapouzdření jsme viděli v části 4.2.2 : print_parity.
Zobecněním se míní přetvoření určité funkce, jako je například tvorba násobků dvou, tak aby tiskla násobky jakéhokoliv čísla.
Následující funkce zapouzdří předchozí smyčku a zobecní ji tak aby vracela násobky n.
def print_multiples(n): i = 1 while i <= 6: print(n*i, "\t", end="") i += 1 print()
Všechno, co jsme pro zapouzdření museli udělat bylo to, že jsme přidali záhlaví funkce s parametrem n.
Zavoláme-li tuto funkci pro argument 2, dostaneme stejný výstup jako předtím. Pro argument 3 obdržíme:
>>> 3 6 9 12 15 18 >>>
Pro argument 4 je výstup:
>>> 4 8 12 16 20 24 >>>
Nyní patrně už umíme odhadnout jak vytvořit tabulku pro násobení - opakovaným voláním print_multiples pro různé argumenty. Vlastně můžeme použít ještě jednu smyčku:
i = 1 while i <= 6: print_multiples(i) i += 1
Všimněme si, jak podobná je tato smyčka té, která je uvnitř print_multiples. Výměna funkce print voláním funkce print_multiples bylo vše, co jsme učinili.
Výstup tohoto programu je tabulka pro vzájemné násobení čísel 1- 6 :
>>> 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36 >>>
Abychom si ještě jednou předvedli zapouzdření, vezměme kód z předcházejícího odstavce a zabalme jej do funkce:
def print_mult_table(): i = 1 while i <= 6: print_multiples(i) i += 1
Tento proces je běžná rozvojová metoda. Vytvářený kód zapisujeme do řádků mimo funkci, případně jej zkoušíme v IDLE či konzole interpreta. Jakmile nám chodí, zabalíme jej do funkce.
Tato metoda je zvláště užitečná, když na počátku ještě nevíme, jak program do funkcí rozdělit. Program tak vzniká postupným vývojem.
Pro další příklad zobecnění předpokládejme, že chceme program, který by vytiskl tabulku násobení jakékoliv velikosti, nikoliv pouze šest krát šest. Můžeme přidat parametr do funkce print_mult_table:
def print_mult_table(high): i = 1 while i <= high: print_multiples(i) i = i + 1
Hodnotu 6 jsme nahradili parametrem high. Zavoláme-li print_mult_table(7), zobrazí se:
>>> 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36 7 14 21 28 35 42 >>>
To je prima, až na to, že asi chceme, aby byla tabulka čtvercová - se stejným počtem řádků jako sloupců. Zajistíme to přidáním dalšího parametru do print_multiples abychom určili, kolik sloupců by tabulka měla mít.
Tento parametr schválně nazveme high, abychom předvedli, že různé funkce mohou mít parametr se stejným jménem (stejně jako lokální proměnné). Zde je celý program:
def print_multiples(n,high): i = 1 while i <= high: print(n*i, "\t", end="") i += 1 print() def print_mult_table(high): i = 1 while i <= high: print_multiples(i, high) i += 1
Povšimněme si, že s přidáním nového parametru jsme museli změnit první řádek funkce (záhlaví funkce), a také jsme museli změnit místo, kde je funkce v print_mult_table volána.
Dle očekávání generuje program čtvercovou tabulku 7x7:
>>> 1 2 3 4 5 6 7 2 4 6 8 10 12 14 3 6 9 12 15 18 21 4 8 12 16 20 24 28 5 10 15 20 25 30 35 6 12 18 24 30 36 42 7 14 21 28 35 42 49 >>>
Provedeme-li zobecnění vhodným způsobem, často dostaneme program se schopnostmi, které jsme nezamýšleli. Například si lze všimnout, že v důsledku toho, že ab = ba, všechny hodnoty se v tabulce opakují dvakrát. Mohli bychom ušetřit inkoust kdybychom tiskli jen polovinu tabulky. Zařídíme to pouhou změnou jednoho řádku v print_mult_table. Změňme
print_multiples(i, high)
na
print_multiples(i, i)
a obdržíme
>>> 1 2 4 3 6 9 4 8 12 16 5 10 15 20 25 6 12 18 24 30 36 7 14 21 28 35 42 49 >>>
Smyčky jsou často užívány v programech, které spočítají výsledek tak, že začnou s přibližnou hodnotou a opakovaným výpočtem výsledek zpřesňují.
Například, jedním ze způsobů počítání druhé odmocniny čísla je Newtonova metoda. Při určení odmocniny čísla n začneme vpodstatě libovolnou aproximací (přibližnou hodnotou), kterou zpřesníme pomocí tohoto vzorce:
better = (approx + n/approx)/2
Výpočet se opakuje tak dlouho, až se zpřesněná hodnota téměř neliší od předchozí. Pro provádění výpočtu napíšeme funkci:
def sqrt(n): approx = n/2.0 better = (approx + n/approx)/2.0 while better != approx: approx = better better = (approx + n/approx)/2.0 return approxVolejme funkci pro argument 25, abychom se přesvědčili, že výsledek je 5.0.
Newtonova metoda je příkladem algoritmu - obecného řešení určitého problému (v našem případě počítání druhé odmocniny).
Introspekce Pythonu je akt zkoumání obsahu a vlastností jednotlivých jeho objektů. Za tímto účelem disponuje Python řadou vestavěných funkcí, z nichž některé již známe.
>>> omen = "Alenka" >>> dir() ['__annotations__', '__builtins__', '__doc__', '__loader__',
'__name__', '__package__', '__spec__', 'omen', 'sys']
>>> isinstance(5, int) True
>>> getattr(omen, "__doc__") "str(object='') -> str\nstr(bytes_or_buffer[, ... ... sys.getdefaultencoding().\nerrors defaults to 'strict'."
Při rozvoji programu se s oblibou provádí testování vybraných úseků zdrojového kódu. Pro toto testování poskytuje Python moduly doctest a unittest.
Popíšeme si práci s modulem doctest. Zkoumané vzorky kódu se umístí do dokumentačního řetězce pod záhlavím funkce. V každém vzorku je na prvním řádku kód, jakoby zadaný 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:
# ch05_doctest.py 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 """ return n % 2 == 0 or n % 5 == 0 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.
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).
Spuštění neúspěšného skriptu vyprodukuje například 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.
|
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).
|
|
|
stvoří tento výstup
25
a zapište výsledky.1 1 2 3 3 6 4 10 5 15
if __name__ == '__main__': import doctest doctest.testmod()
Funkce num_digits(n) v odstavci 5.3 nepracuje správně pro n < 0. Doplňte ji tak, aby pracovala správně pro všechna celá čísla (včetně nuly).
def num_digits(n): """ >>> num_digits(12345) 5 >>> num_digits(0) 1 >>> num_digits(-12345) 5 """
def num_even_digits(n): """ >>> num_even_digits(123456) 3 >>> num_even_digits(2468) 4 >>> num_even_digits(1357) 0 >>> num_even_digits(2) 1 >>> num_even_digits(20) 2 """
def print_digits(n): """ >>> print_digits(13789) 9 8 7 3 1 >>> print_digits(39874613) 3 1 6 4 7 8 9 3 >>> print_digits(213141) 1 4 1 3 1 2 """Nutno zajistit, aby mezi číslicemi výstupu byly mezery a aby za poslední číslicí žádná mezera nebyla. Numerický argument funkce nutno před manipulací přetvořit na
string
(viz 2.6), jehož délku určíme funkcí len
(viz 2.10).def sum_of_squares_of_digits(n): """ >>> sum_of_squares(1) 1 >>> sum_of_squares(9) 81 >>> sum_of_squares(11) 2 >>> sum_of_squares(121) 6 >>> sum_of_squares(987) 194 """Svá řešení ověřte pomocí doctestů.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |