For-cyklus a postupnosti

Syntax for-cyklu v pythone vyzerá takto:

for iteracna_premenna in postupnost:
    prikaz1;
    prikaz2;
    ...

V jednotlivých iteráciách cyklu nadobúda iteracna_premenna postupne všetky hodnoty zo sekvencie. Napríklad:

>>> for x in [1, 2 ,4, 7]:
...   print(x ** 2)
... 
1
4
16
49

Iterovateľné postupnosti

Ako ste už videli, postupnost môže byť napríklad pole (list). Okrem poľa však existujú aj iné typy postupností.

Tuple

Typ tuple sa od bežných polí líši tým, že hodnoty jeho prvkov sa musia určiť pri jeho vytváraní a potom sa už nedajú meniť. Na vytvorenie tuple sa používajú okrúhle zátvorky (()) namiesto hranatých ([]):

>>> x = (4, 9, 19)
>>> type(x)
<class 'tuple'>

Za posledným prvkom v tuple tiež môže (ale zvyčajne nemusí) byť čiarka. Špeciálne, pri jednoprvkových tuple-och je však čiarka za jediným prvkom povinná:

>>> x = (4)
>>> x
4
>>> type(x)
<class 'int'>
>>> x = (4,)
>>> x
(4,)
>>> type(x)
<class 'tuple'>

Dict

Aj slovníky (dict) sa dajú iterovať for-cyklami. V takýchto cykloch iteracna_premenna prechádza cez kľúče (indexy) v slovníku:

>>> cislo_dna = {'pondelok': 0, 'utorok' : 1, 'streda' : 2, 'stvrtok' : 3, 'piatok' : 4, 'sobota' : 5, 'nedela' : 6}
>>> for den in cislo_dna:
...   print(den)
... 
pondelok
utorok
streda
stvrtok
piatok
sobota
nedela

Ak by sme chceli, aby iteracna_premenna nadobúdala hodnoty zo slovníka, môžeme využiť metódu .values():

>>> cislo_dna = {'pondelok': 0, 'utorok' : 1, 'streda' : 2, 'stvrtok' : 3, 'piatok' : 4, 'sobota' : 5, 'nedela' : 6}
>>> for den in cislo_dna.values():
...   print(den)
... 
0
1
2
3
4
5
6

Range

Pravdepodobne ste sa už stretli s funkciou range(), ktorá generuje postupnosti čísel. Už ste možno aj zistili, že jej výstupom nie je pole (list), ale mysteriózny objekt typu range:

>>> r = range(5, 20)
>>> type(r)
<class 'range'>

V Pythone 2 funkcia range() vracala obyčajné pole. To sa však v niektorých prípadoch ukázalo byť nepraktické. Napríklad, predstavte si, že by ste chceli overiť nejakú vlastnosť pre všetky čísla od 1 do 1 000 000 000 a napísali by ste takýto cyklus:

>>> for n in range(1, 1000000001):
...   over(x)

Tento cyklus by si v Pythone 2 najprv musel vytvoriť pole so všetkými číslami medzi 1 a 1 000 000 000, ktoré by zabralo niekoľko gigabajtov. Informácia o tom, že chceme všetky čísla od 1 po 1 000 000 000 sa pritom dá pamätať oveľa úspornejšie. To presne robia aj objekty typu range v Pythone 3 -- ukladajú si iba tri čísla: začiatok intervalu, koniec intervalu a krok. Pre úplnosť, v Pythone 2 existuje funkcia xrange(), ktorá robí to, čo range() v Pythone 3.

S objektami typu range sa ale dá robiť veľa vecí, čo aj s poľami (resp. skôr s tuple-ami, keďže ich tiež nie je možné modifikovať):

>>> r = range(5, 10)
>>> r[3]
8
>>> r[1:4]          
range(6, 9)
>>> len(r)
5
>>> for x in r:
...   print(x * 2)
... 
10
12
14
16
18
>>> r[2] = 8
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'range' object does not support item assignment

Generátory

Generátory sú ďalší typ úsporného zápisu nejakej postupnosti. Sú to objekty, ktoré si nepamätajú všetky prvky naraz, ale generujú ich, až keď ich treba.

Enumerate

Príkladom užitočného generátora je výstup z funkcie enumerate(). Funkcia enumerate() na vstupe berie postupnosť (nazvime ju p) a vracia postupnosť dvojíc, kde druhý prvok je vždy hodnota z p a prvý prvok je jej index:

>>> x = ['jeden', 'dva', 'tri']
>>> list(enumerate(x))
[(0, 'jeden'), (1, 'dva'), (2, 'tri')]

Ako už bolo spomenuté, výstupom z enumerate() nie je pole, ale generátor typu enumerate:

>>> x = ['jeden', 'dva', 'tri']
>>> enumerate(x)
<enumerate object at 0x7f07c37ca048>
>>> type(enumerate(x))
<class 'enumerate'>

To znamená, že ak zavoláte enumerate() na nejakom veľkom poli, nemusí sa celé pole kopírovať, keďže generátoru pri generovaní stačí pamätať si, na ktorom indexe v poli sa nachádza.

Funkcia enumerate() nám umožňuje pohodlne iterovať cez polia aj v prípade, že potrebujeme aj indexy prvkov, nielen hodnoty:

>>> x = [4, 9, 13, 21, 26]
>>> for i in enumerate(x):
...   index, value = i
...   print(index, value)
... 
0 4
1 9
2 13
3 21
4 26

Modifikácia poľa cez for-cykly

Do premennej iteracna_premenna sa počas for-cyklu postupne priradzujú hodnoty z postupnost-i, ktorú prechádzame. Pri tomto priradzovaní je dokonca môžné priradzovať postupnosti do viacerých premenných (podobne ako pri použití =):

>>> pole = [[0, 1, 2], [4, 9, 13], [28, 16, 5], [-1, -2, -3]]
>>> for prvy, druhy, treti in pole:
...   print(treti, druhy, prvy)
... 
2 1 0
13 9 4
5 16 28
-3 -2 -1

Keďže iteracna_premenna je nová premenná, ak do nej niečo priradíme, v pôvodnej postupnost-i sa to neodrazí:

>>> x = [4, 9, 13, 21, 26]
>>> for i in x:
...   i = i * 2
...   print(i)
... 
8
18
26
42
52
>>> x
[4, 9, 13, 21, 26]

Ak teda chceme vo for-cykle meniť hodnoty v poli, je vhodné použiť enumerate():

>>> x = [4, 9, 13, 21, 26]
>>> for index, value in enumerate(x):
...   x[index] = value * 2
... 
>>> x
[8, 18, 26, 42, 52]

Zmena dĺžky postupnosti počas cyklu

Pri niektorých typoch postupnosí, napríklad poliach (list) sa môže stať, že sa dĺžka postupnost-i, cez ktorú iterujeme, zmení počas cyklu. Cyklus for sa však stále snaží dôjsť až na koniec postupnost-i, cez ktorú iteruje. To znamená, že napríklad nasledujúci cyklus nikdy nezastane (resp. zastane až v momente, keď pole pole spotrebuje všetku dostupnú pamäť a program spadne):

pole = [4, 9, 13]
for x in pole:
  pole.append(x)

Podobný problém vzniká aj pri použití enumerate() -- generátor, ktorý nám enumerate() vráti, sa stále viaže na pôvodnú postupnosť, a teda ak sa pôvodná postupnosť medzičasom predĺžila, generátor nám vráti aj jej nové prvky:

>>> pole = [4, 9, 13]
>>> for index, value in enumerate(pole):
...   print(index, value)
...   if value < 10:
...     pole.append(value + 1)
... 
0 4
1 9
2 13
3 5
4 10
5 6
6 7
7 8
8 9
9 10
>>> pole
[4, 9, 13, 5, 10, 6, 7, 8, 9, 10]