4.3. Läpikäynnit, iteraattorit, generaattorit
Esitietokurssilla CSE-A1111 Ohjelmoinnin peruskurssi Y1 tutustuttiin for
- ja while
-silmukoihin sekä merkkijonojen, listojen ja
sanakirjojen läpikäyntiin (ks. Y1-kurssin kurssimoniste).
Tässä luvussa perehdymme aiheeseen tarkemmin. Tutustumme myös erilaisiin tapoihin käydä läpi tietorakenteita, iteraattoreihin ja generaattoreihin. Näitä käsitellään Pythonin dokumentaatiossa monessa kohtaa:
- The Python Tutorial:
- 4. More Control Flow Tools, kohdat 4.2, 4.3 ja 4.4.
- 5.1.3. List Comprehensions
- 5.6. Looping Techniques
- The Python Standard Library:
- The Python Language Reference:
Iteraattorit
Peruskurssilla ja tälläkin kurssilla olemme käyneet läpi merkkijonoja, listoja, monikoita ja sanakirjoja for
-silmukalla. Katsotaan
kertaukseksi muutama esimerkki:
>>> l = ['a', 'b', 'c']
>>> for x in l:
... print(x)
...
a
b
c
>>> for x in range(1, 11, 2):
... print(x)
...
1
3
5
7
9
>>> for x in "hijklmno":
... print(x)
...
h
i
j
k
l
m
n
o
>>> for x in {'name': 'Pekka', 'age': 32}:
... print(x)
...
age
name
>>>
Pythonin dokumentaatiossa 8.3. The for statement todetaan:
The for statement is used to iterate over the elements of a sequence (such as a string, tuple or list) or other iterable object:
- for_stmt ::= "for" target_list "in" expression_list ":" suite
- ["else" ":" suite]
The expression list is evaluated once; it should yield an iterable object. An iterator is created for the result of the expression_list. The suite is then executed once for each item provided by the iterator, in the order returned by the iterator.
Iterable
Esimerkissä tällaisia iterable objecteja olivat lista, range, merkkijono ja sanakirja. Tyypillisesti erilaiset säiliöluokat ovat iterableja. Ollakseen iterable, täytyy oliolla olla metodi __iter__, joka palauttaa iteraattorin.
Iteraattori
Mikä tuo iteraattori sitten on? Iteraattori on olio, joka suorittaa varsinaisen läpikäynnin. Jotta olio olisi iteraattori, on sillä oltava
metodi __next__. Listan tapauksessa __next__
palauttaa yksi
kerrallaan listan alkiot (esim. for
-silmukan käyttöön) ja kun kaikki alkiot on palautettu, nostaa poikkeuksen StopIteration. For
-silmukka osaa lopettaa nähdessään tämän poikkeuksen.
Metodia __next__
voi toki kutsua suoraankin ilman for
-silmukkaa.
palautettava oletusarvo.
>>> l=list(range(3))
>>> it = l.__iter__()
>>> it.__next__()
0
>>> it.__next__()
1
>>> it.__next__()
2
>>> it.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Iteraattorien käyttöä varten on kaksi valmiiksi määritettyä funktiota next ja iter, jotka helpottavat toisinaan iteraattorien käyttöä.
Generaattori
Generaattori on kätevä tapa toteuttaa iteraattori. Generaattori näyttää tavalliselta funktiolta, mutta sisältää koodissaan jossain kohtaa
lauseen (tai lausekkeen) yield
. Esim.
>>> def squares(start, end):
... i = start
... while i < end:
... yield i ** 2
... i = i + 1
...
>>>
>>> for x in squares(0, 10):
... print(x)
...
0
1
4
9
16
25
36
49
64
81
>>>
yield
palauttaa i
:n toisen potenssin for
-silmukalle, mutta funktion squares
suoritus ei lopu.x
sidotaan yield
in palauttama arvo.For
-silmukan runko suoritetaan ja tulostetaan x
:n arvo.squares
ja jatkuu kohdasta i = i + 1
.while
-silmukan ehto tulee epätodeksi, loppuu funktion suoritus ja kontrolli siirtyy for
-lauseen jälkeiseen kohtaan
ohjelmassa.Päättymätön iteraattori
Toisinaan on kätevää tehdä iteraattorista tai generaattorista päättymätön, eli että se ei koskaan lopeta toimintaansa. Iteroinnin lopettamisesta pitää tällöin huolta iteraattorin kutsuja. Esimerkiksi:
>>> def repeat(x):
... while True:
... yield x
...
>>> r=repeat(True)
>>> next(r)
True
>>> next(r)
True
>>> next(r)
True
>>> next(r)
True
>>> for (i, x) in zip(range(4), repeat('huhuu')):
... print('{}: {}'.format(i, x))
...
0: huhuu
1: huhuu
2: huhuu
3: huhuu
>>>
repeat
in kutsuja lopeta sitä.repeat
-iteraattori ja kutsutaan sitä suoraan funktion next
avulla. Tätä voitaisiin jatkaa loputtomasti.range(4)
ja repeat('huhuu')
Pythonin valmiilla funktiolla zip. Se tuottaa iteraattorin, joka palauttaa pareittain näiden kahden
iteraattorin (tai oikeammin iterablen iteraattorin) palauttamat arvot.range
:n iteraattori ei ole päättymätön, for
-silmukan suoritus ei jatku loputtomasti.Moduli itertools
Pythonin kirjastossa on moduli itertools
(ks 10.1. itertools — Functions creating iterators for efficient looping). Se määrittää joukon valmiita iteraattoreita, joita yhdistelemällä
saa näppärästi ratkottua monia läpikäynnin ongelmia ilman, että itse tarvitsee toteuttaa iteraattoria. Moduliin kannattaa tuotustua
tarkemminkin, mutta katsotaan tässä paria esimerkkiä.
count
Hieman kuin range
, mutta ei ylärajaa. count()
tuottaa numerot 0, 1, 2, jne. . Parametrina voi antaa alkukohdan ja
askelen. Esim. count(1, 3)
tuottaa numerot 1, 4, 7, 10, jne.
cycle
Tuottaa uudestaan ja uudestaan parametrina annetun iterablen tuottamat arvot. Esim. cycle(range(3))
tuottaa loputtomasti arvot 0, 1, 2, 0,
1, 2, jne.
repeat
Yllä määrittämämme repeat
löytyy valmiina.
Esimerkkejä
Tulostetaan annetun vuoden kaikki arkipäivät:
from itertools import count
from datetime import timedelta, date
def weekdays(start):
return filter(lambda d: d.weekday() < 5, ((start + timedelta(days=i)) for i in count()))
def print_weekdays(year):
for d in weekdays(date(year, 1, 1)):
if d.year > year:
break
else:
print(d)
Joukko reseptejä
Modulin itertools
iteraattorien käytöstä löytyy valmiita reseptejä dokumentaation kohdasta 10.1.2. Itertools Recipes.
For
-silmukan aluksi kutsutaan funktiotasquares
. Sen suoritys etenee, kunnes kohdataanyield
-lause.