Příkazy

Na našem prvním příkladě pro výpočet největšího společného dělitele jsme viděli, že cíle lze dosáhnout rozdělením problému na menší části, které měly následující povahu

  • uschovávání hodnot (např. mezivýsledků) do proměnných

  • rozhodnutí, jak dále postupovat (která z proměnných a,b je větší?)

  • opakování souboru kroků

  • tisk informací pro uživatele (print)

To jsou příklady tzv. příkazů. Za desetiletí vývoje programování se ukázalo, že i nejuniverzálnější jazyk nepotřebuje takových příkazů jen několik a složitost programů spočívá v jejich vhodném složení do většího celku.

Příkazy a jejich posloupnosti

Již víme, že kód sestává z posloupnosti příkazů. Ty zapisujeme textem dodržujícím jistá pravidla. Pro ilustraci si ukažme, že téhož lze dosáhnout graficky, například v jazyce scratch diagram

Scratch

popisuje kód v Pythonu vypadající takto:

a = 33
b = 121

while not a==b:
   if a>b:
      a = a-b
   else:
      b = b-a

Vidíme tedy, že místo skládání puzzle v Pythonu jen vhodně odsazujeme řádky s jednotlivými příkazy.

Přiřazovací příkaz

Oznamuje, že proměnná má nabývat hodnotu určenou nějakým výrazem.

promenna = vyraz

tedy například

energie = hmotnost * rychlost_svetla**2

V jazyce Python je ale přiřazovací příkaz také místem, kde proměnné vznikají. Prvním přiřazením do proměnné se s jejím jménem sváže hodnota určená výrazem na pravé straně znaku =. Jak ještě několikrát zmíníme, proměnná nepředstavuje tak jako v jiných počítačových jazycích popisku nějakého místa paměti pro uložení dat, ale odkaz na taková data. Dalším přiřazením do téže proměnné se tedy změní nejen hodnota, ale také místo, kde v paměti data sídlí. To je velmi důležité při práci s poli a tam se k tomuto faktu znovu vrátíme.

V důsledku přiřazení se může změnit i typ dat.

Pokročilejší výklad

Chování přiřazení si můžeme demonstrovat za pomoci zabudované funkce Pythonu id(...), která sděluje na jaké adrese jsou data proměnné uložena.

[9]:
x = 1.2
print(f"{id(x)=}")
y = x
print(f"{id(y)=}")
x = x/y
print(f"{id(x)=}")
x = 1.2
print(f"{id(x)=}")


id(x)=140717650747248
id(y)=140717650749072
id(x)=140717650741360
id(x)=140717650740496

Povšimněte si, že přiřazení x=y způsobí, že obě proměnné odkazují na tatáž data. Pokud bychom použili x=y+0, nebylo by tomu tak, ačkoli by x a y nabývaly stejné hodnoty. Stejné adresy tedy znamenjí více než stejné hodnoty.

  • Ke zjištění totožnosti slouží operátor is

  • Ke zjištění rovnosti slouží operátor ==

[13]:
x1 = 1.2
y1 = x1

x2 = 1.2
y2 = x2+0

print(f'{x1 is y1 = }')
print(f'{x2 is y2 = }')
print()
print(f'{x1 == y1 = }')
print(f'{x2 == y2 = }')
x1 is y1 = True
x2 is y2 = False

x1 == y1 = True
x2 == y2 = True

Další komplikace je v tom, že z hlediska is se celá čísla a řetězce chovají zcela resp. poněkud jinak než čísla reálná:

[12]:
x1 = 1.0
x2 = 1.0+0
print( f"{x1 is x2 = }" )

s1 = "ab"
s2 = "a"+"b"
print( f"{s1 is s2 = }" )

i1 = 10
i2 = 10+0
print( f"{i1 is i2 = }" )

i1 = 400
i2 = 400+0
print( f"{i1 is i2 = }" )
x1 is x2 = False
s1 is s2 = True
i1 is i2 = True
i1 is i2 = False

Konec pokročilejšího textu

Vícenásobné přiřazení

Velmi pohodlná je varianta přiřazovacího příkazu zahrnujícího dvě nebo i více přiřazení zároveň. Její užitečnost si můžeme demonstrovat na prohození obsahu dvou proměnných. Uvidíme, že v programování jde o častý úkaz.

Obvyklý způsob, jak prohodit dvě proměnné je

pom_prom = a
a = b
b = pom_prom

Python umožňuje to zapsat přehledněji

a, b = b, a

Protože takový zápis zpřehleňuje kód lze jej doporučit, i když, samozřejmě, nejde o zjednodušení z hlediska rychlosti operací.

Pozn. V našem kurzu nebude příliš času si vysvětlit, že jde o techniku související se složeným datovým typem n-tic (tuple) a jejich rozbalováním (unpacking) v rámci přiřazení.

Příkaz import

Již jsme potkali konstrukci

import math

Jde o příkaz, který zařídí, že můžeme používat funkce z této knihovny za použití zápisu print(  math.sin(math.pi/3) ) a pod.

Pro úplnost uveďme, že tento příkaz se může vyskytovat v dalších podobách. Například

from math import sin, cos, pi

nám dovolí používat ještě jednoušší podobu zápisu print( sin(pi/3) )

Ještě častěji potkáme variantu

import numpy as np

která nám umožní místo delšího numpy psát kratší np. Drobnost, ale velmi používaná a srozumitelná.

Příkaz print a volání podprogramu

Dávno již používáme příkaz print, např.

print( x, x*x, x*x-a )

by nám srozumitelně sdělilo, jak dobře jsme nedávno hledali odmocniu z a.

Jde o speciální variantu příkazu, kterému se odborně říká výrazový příkaz, ale pro jednoduchost budeme teď používat termín volání podprogramu (jde o podmnožinu výrazových příkazů skládajících se právě z jedné funkce).

Podprogram (procedura jak uvidíme vlastně funkce) print je již připravený kus programu, který vezme zadané agumenty a zařídí, že se jejich textová podoba objeví na požadovaném místě (zatím v konzoli nebo jako výstup buňky v sešitu Jupyter).

Jako jiný příklad výrazového příkazu použijme funkci input. Ta sice vrací řetězec, který po výzvě zadáme na klávesnici, nicméně můžeme tuto vrácenou hodnotu ignorovat a psát

[19]:
input('stikněte klávesu <enter>')
print('děkuji')
děkuji

Jde o program s dvěma příkazy, oba mají podobu volání podprogramu.

Větvení - podmíněný příkaz if

Jako obvykle máme jednoduchou variantu s jednou větví

if podminka:
     prikaz1
     prikaz2
     ....

a variantu s

if podminka:
    prikaz1
    prikaz2
    ....
else:
    prikazA
    prikazB
    ....

Použití odsazování (indentace) pro podpříkazy strukturovaných příkazů si vyžádalo ještě variantu s elif

if podminka:
    prikaz1
    prikaz2
    ....
elif podminka2:
    prikazA
    prikazB
    ....

Tu lze kombinovat s else na konci řetězce podmínek.

Uveďme konktétní příklad:

potřebujeme-li prohodit dvě proměnné tak, aby bylo \(a\ge b\), můžeme napsat

if a < b:
    a, b = b, a

Cyklus while

while podminka:
    prikaz1
    prikaz2
    ....

Pokud podmínka není splněna hned na počátku, žádný z příkazů těla cyklu se neprovede.

Jde o klíčový příkaz, který umožní opakovat jisté operace, dokud nedosáhneme požadovaného stavu určeného podmínkou. Například:

s = 0
n = 1
while n < 1000:
    s = s + 1/n
    n = n * 2

spočte součet \(1+\frac{1}{2}+\frac{1}{4}+...+\frac{1}{512}\).

Důležité: mezi příkazy těla cyklu by neměl chybět takový, který někdy změní hodnotu podmínky. Jinak se bude cyklus opakovat bez přestání do té doby, dokud nenastane nějaká chyba, běh programu nepřerušíme nebo neukončíme. (Rozdíl mezi přerušit a ukončit ještě potkáme.)

Cyklus for

V počítačových jazycích býval důležitý cyklus, který zařídil, že nějaká proměnná v cyklu nabývala hodnoty z nějakého intervalu celých čísel. Tvůrci počítačových jazyků ovšem tento koncept různě rozšiřovali. V Pythonu je to míněno takto: Vezmi nějaký seznam hodnot a zařiď že tzv. řídící proměnná cyklu postupně nabude všech hodnot z tohoto seznamu. To se později ještě zdokonalilo v tom, seznam může být definován jen předpisem, jak takové hodnoty zjistit, ale není nezbytné jej skutečně vytvářet. To umožní, že počet opakování cyklu for může být vyšší, než kolik je dostupné paměti na uložení takového seznamu.

Na počátku našeho kurzu se ovšem vrátíme k základům programování a ukážeme si jak vypsat seznam celých čísel od 1 do 100:

for i in range(1,101):
    print(i)

Hlavička cyklu v podobě for i in range(1,101): zařídí, že bude provedena následující sekvence operací

i = 1
print(i)
i = 2
print(i)
i = 3
print(i)
...
i = 99
print(i)
i = 100
print(i)

Musíme vzít na vědomí, že funkce range se chová tak, že její první argument představuje hodnotu, kterou se začne, zatímco druhý argument hodnotu před kterou se skončí. Je to nepohodlné, jak ale uvidíme, v rámci jazyka Python odůvodněné. Můžeme ted psát, že základní varianta cyklu for má podobu

for index in range(initial, final+1):
     prikaz1
     prikaz2
     ....

Zajímavým detailem takovéto podoby cyklu for je fakt, že na jeho konci nabývá i hodnoty final, zatímco svojí funkcí podobný cyklus

i = initial
while index < final:
     prikaz1
     prikaz2
     ....
     index = index + 1

skončí i s hodnotou final+1 (v obou případech předpokládáme initial < final).

Funkce range může mít jen jeden argument, tj. range(stop), potom např.range(5) -> 0,1,2,3,4.

Funkce range může mít i třetí argument, tj. range(start,stop,step), potom např.range(2,12,2) -> 2,4,6,8,10.

Cyklus for je velmi důležitý i v této základní podobě. Proto si vyzkoušejte napsat takové argumenty range aby následující kód vypsal: - 1,2,3 - 1,3,5,7 - -4,-3,-2,-1 - -1,-2,-3,-4 (použijte argument step=-1)

[15]:
# prostor pro testování cyklů for

for i in range(2,12,2):
    print(i,end=' ')
2 4 6 8 10

Pozn. Ve skutečnosti je range(...) funkce, která vrací návod, jak a jaký interval má proměnná i procházet. Místo ní může být jakýkoli výraz u kterého dává smysl pocházet jeho hodnoty, např. seznam:

seznam = [1,2,3]
for x in seznam:
     print(x)

Vnořené cykly

Protože příkazy můžeme kombinovat, lze v jednom cyklu mít cyklus další. Je vhodné použít jinou řídící proměnnou, výjimky z tohoto pravidla patří do prokročilého programování.

[18]:
# malá násobilka
for i in range(1,11):
  for j in range(1,11):
    print(f'{i*j:4}',end='')
  print()
   1   2   3   4   5   6   7   8   9  10
   2   4   6   8  10  12  14  16  18  20
   3   6   9  12  15  18  21  24  27  30
   4   8  12  16  20  24  28  32  36  40
   5  10  15  20  25  30  35  40  45  50
   6  12  18  24  30  36  42  48  54  60
   7  14  21  28  35  42  49  56  63  70
   8  16  24  32  40  48  56  64  72  80
   9  18  27  36  45  54  63  72  81  90
  10  20  30  40  50  60  70  80  90 100

Příkaz skoku break

Pokud je cyklus svojí povahou složitější, může bý výhodné zjisťovat podmínku jeho ukončení nejakým výpočtem uvnitř a následně použít příkaz break, který vyskočíz cyklu a běh programu pokračuje dalším příkazem, jaký by následoval po obvyklém ukončení cyklu.

Cyklus repeat

Příkaz cyklu testující podmínku na konci cyklu nikoli na před jeho započetím (jako je repeat v Pascalu nebo do v C) není v Pythonu k dipozici. Místo toho můžeme použít nekonečný cyklus while True: a jeho opakování ukončit po tesu na konci. Například

while True:
    x = int( input('zadejte kladné číslo:') )
    if x>0:
        break

print('děkuji')

Cvičení: přepište tento kód bez použití break a rozmyslete si, zda vám to přijde elegantnější. Pozn. je více možností, jedna např. opakuje volání funkce input, jiná nastaví na počátku vhodnou hodnotu x.

Indentace (odsazování) kódu

Viděli jsme, že např. při použití příkazu while potřebujeme odlišit, které příkazy se mají opakovat a které provádět po skončení cyklu, a že za tím účelem jsou příkazy těla cyklu odsazeny.

Máme tu dvě pravidla

  • Jako nápověda, že máme uvažovat o změně odsazování slouží dvojtečka na konci řádků s příkazy if, else, elif, while, def atd.

  • Odsazení podřízených příkazů musí být větší a stejné. Doporučuje se používat stejné odsazení v podobě čtyř mezer.

Komplikaci představuje neviditelný znak <tab>, který odskakuje na začátek dalšího tabulačního sloupce. Jeho šířka je obvykle osm znaků. Python nedovoluje kombinovat tabulátory a mezery:

tomas@dronte:~/tmp/prog$ python3 spaces.py
  File "/home/tomas/tmp/prog/spaces.py", line 3
    print(2)
TabError: inconsistent use of tabs and spaces in indentation
tomas@dronte:~/tmp/prog$ cat spaces.py
if 1 < 2:
        print(1)
        print(2)
tomas@dronte:~/tmp/prog$ hexdump -C spaces.py
00000000  69 66 20 31 20 3c 20 32  3a 0a 09 70 72 69 6e 74  |if 1 < 2:..print|
00000010  28 31 29 0a 20 20 20 20  20 20 20 20 70 72 69 6e  |(1).        prin|
00000020  74 28 32 29 0a                                    |t(2).|
00000025
tomas@dronte:~/tmp/prog$

Tento záznam komunikace s počítačem obsahuje tři příkazy

  • pokus o spuštění programu spaces.py. Ten skončí výpisem chyby TabError: inconsistent use of tabs and spaces in indentation

  • vypsání obsahu soubory spaces.py příkazem cat, které neukazuje nic podezřelého

  • podrobné vypsání obsahu soubory spaces.py příkazem hexdump, které ukazuje , že řádek print(1) začíná tabulátorem (ascii znak 09), nikoli osmi mezerami.

Závěr: Pro indentaci příkazů se doporučuje používat jen mezery.

Pokračování řádků

Někdy se vše nevejde na jeden řádek. Ve fyzice to jsou zejména dlouhé výrazy. Možností je více, nám stačí jediné - výraz rozdělte uvnitř závorek. Následující příklad m.j. ukazuje, že odsazení pokračovacího řádku může být libovolné a nemusí se podřizovat příkazu if.

if abs(x) < 0.2:
    y =  x*(1 + x2*(-0.3333333333333333 + x2*(0.2
       + x2*(-0.14285714285714285 + x2*0.1111111111111111 ))))

Více příkazů na jeden řádek

Někdy je žádoucí mít na jednom řádku více příkazů. V tom případě slouží k oddělení příkazů středník. Používejte sřídmě.

Kontrolní úlohy

  1. Opravte indentaci v programu pro malou násobilku

    # malá násobilka
    for y in range(1,11):
    for x in range(1,11):
    print(f'{x*y:4}',end='')
    if x==1:
    print(' |',end='')
    if y==1:
    print()
    print('-----+------------------------------------',end='')
    print()
    

    tak, aby jeho výstup byl

       1 |   2   3   4   5   6   7   8   9  10
    -----+------------------------------------
       2 |   4   6   8  10  12  14  16  18  20
       3 |   6   9  12  15  18  21  24  27  30
       4 |   8  12  16  20  24  28  32  36  40
       5 |  10  15  20  25  30  35  40  45  50
       6 |  12  18  24  30  36  42  48  54  60
       7 |  14  21  28  35  42  49  56  63  70
       8 |  16  24  32  40  48  56  64  72  80
       9 |  18  27  36  45  54  63  72  81  90
      10 |  20  30  40  50  60  70  80  90 100
    

    Povšimněte si, že x==1 se testuje uvnitř cyklu for x in ..., zatímco y==1 se testuje v rámci cyklu for y in .... Poslední print() ukončuje každý z vytištěných řádků.