0% found this document useful (0 votes)
40 views46 pages

Understanding Iterables and Iterators

The document provides an overview of iterables, iterators, and generators in Python, explaining their definitions and functionalities. It describes how iterables can be any data type that supports iteration, while iterators maintain the state of an iterable. Additionally, it includes examples of implementing custom iterators and practical applications such as a simple blackjack game.

Uploaded by

likevin1022
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views46 pages

Understanding Iterables and Iterators

The document provides an overview of iterables, iterators, and generators in Python, explaining their definitions and functionalities. It describes how iterables can be any data type that supports iteration, while iterators maintain the state of an iterable. Additionally, it includes examples of implementing custom iterators and practical applications such as a simple blackjack game.

Uploaded by

likevin1022
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Iterable, Iterator,

Generator
Prof. Pai H. Chou
National Tsing Hua University
Outline
• Data types that are iterable
• any type that can be used by for-loop or *unpack
• by supporting __iter__ or __getitem__

• Iterator
• a built-in class that makes an iterable out of any type
• wrapped type implements __next__ method

• Generator
• a function that uses yield to work like an iterable
• known as "coroutine" in other language terminology
What is an iterable? 可

• a data structure that can produce one item at a time

• sequence (list, str, tuple, bytes, etc)
• non-sequence collection (set, dict, ...)
• range(), "view" classes for dict keys and values
• any type that can do A[i] (i.e., __getitem__)

• Typical usage
• for i in iterable:
• function(*iterable)
Example use of iterable
• for loop over iterable >>> for i in {'a', 'b', 'c'}:
... print(i)
• (sequence) list, tuple, str, ...
b set: order not
• (unordered) set and dict a
c
guaranteed!!!
>>> for i in ['a', 'b', 'c']: >>> for i in {'a':2, 'b':3, 'c':4}:
... print(i) ... print(i)
... ...
a list a dict: prints the keys, but
b b order not guaranteed!
c c
>>> for i in ('a', 'b', 'c'): >>> for i in 'abc':
... print(i) ... print(i)
... ...
a tuple a str
b b
c c
Example use of iterable
• range() as iterable • can be unpacked
• can use index also • assignment
• has len() • parameter passing
>>> a, b, c = range(3):
>>> for i in range(3):
>>> a
... print(i)
0
...
0 range >>>
1
b
1
>>> c
2
2
>>> R = range(2, 20, 3)
>>> R[4]
iterable can be unpacked
14 can be indexed
>>> len(R) >>> max(*R) # max(2,5,8,11,14,17)
6 17
What makes a data type iterable?
• option 1: supports __iter__()
• to return an iterator object for the iterable object
• Called by the built-in iter() function to track state
• option 2: __getitem__()
• to return the indexed item, so an iterator can
select the item to produce
What is an iterator? 迭

• an object that tracks an iterable's state

• iterable = "content", iterator = "current position"
• Explicit
• r = iter(iterable) to make an iterator object
• next(r) to advance the state of an iterator

• Implicit
• made by for-loop or an unpacking assignment
Example of iterable vs. iterator
• iterable: the "content" part
>>> D = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

• iterator: the "position" part


>>> r = iter(D) >>> next(r)
>>> next(r) 'Fri'
'Sun' >>> next(r)
>>> next(r) 'Sat'
'Mon' >>> next(r)
>>> next(r) Traceback (most recent call last):
'Tue' File "<stdin>", line 1, in <module>
>>> next(r) StopIteration
'Wed' >>>
>>> next(r)
'Thu'
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r

return value
next(r) 'Sun'
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r

return value
next(r) 'Sun'
next(r) 'Mon'
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r

return value
next(r) 'Sun'
next(r) 'Mon'
next(r) 'Tue'
Example: multiple iterators per
iterable
• iterable: the "content" part
>>> D = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

• iterator: the "position" part


>>> r = iter(D) # one iterator >>> next(r)
>>> s = iter(D) # another 'Wed'
>>> next(r) >>> next(r)
'Sun' 'Thu'
>>> next(r) >>> next(r)
'Mon' 'Fri'
>>> next(r) >>> next(s)
'Tue' 'Tue'
>>> next(s) >>> next(s)
'Sun' 'Wed'
>>> next(s) >>> next(r)
'Mon' 'Sat'
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r

return value
next(r) 'Sun'
next(r) 'Mon'
next(r) 'Tue'
s = iter(D) s
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r

return value
next(r) 'Sun'
next(r) 'Mon'
next(r) 'Tue'
s = iter(D) s
next(s) 'Sun'
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r

return value
next(r) 'Sun'
next(r) 'Mon'
next(r) 'Tue'
s = iter(D) s
next(s) 'Sun'
next(s) 'Mon'
visualizing iterable vs iterator
iterable D 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat'

r = iter(D) r

return value
next(r) 'Sun'
next(r) 'Mon'
next(r) 'Tue'
s = iter(D) s
next(s) 'Sun'
next(s) 'Mon'
next(r) 'Wed'
how do iterators of built-in types
work?
>>> r = iter([])
>>> type(r)
<class 'list_iterator'>
>>> type(iter({}))
<class 'dict_keyiterator'>
>>> type(iter(()))
<class 'tuple_iterator'>
>>> type(iter('hello'))
<class 'str_iterator'>
• built-in iterable type use own iterator class!
• For your own types
• Option 1: let iter() call __getitem__ to make iterator
• Option 2: define your own iterator class for your own
iterable, and add __iter__ to your iterable
Example: Vector iterator
Option 1: default iterator
• Already supports __getitem__()
def Vector:
def __getitem__(self, i):
if type(i) == int:
return self._v[i]
elif type(i) == slice:
....

• call iter() to use default implementation


>>> r = iter(Vector(3, 5, 2, 7, 9))
>>> type(r)
<class 'iterator'>
>>> next(r)
3
>>> next(r)
5
>>> next(r)
2
Example: Vector iterator
Option 2: custom iterator
• Vector_Iterator • add __iter__ to
• __init__() Vector class
• __next__() • pass self to iterator's
constructor
class Vector_Iterator: class Vector:
def __init__(self, vec): def __iter__(self):
self._vec = vec return Vector_Iterator(self)
self._i = 0
def __next__(self): called by iter() to iterate
if self._i >= len(self._vec):
raise StopIteration
over elements of Vector
val = self._vec[self._i]
self._i += 1 called by next() to get next
return val
element
as an iterable Vector, you can
• Iterate over elements in for loop
>>> v = Vector(7, 1, 4, 3, 9, 6, 5)
>>> for i in v: print(i, end='')
...
7143965>>>

• Convert to sequence
>>> list(v)
[7, 1, 4, 3, 9, 6, 5]

• Use in unpacking assignment


>>> a, b, c, d, e, f, g = v
>>> a, b, c, d, e, f, g
(7, 1, 4, 3, 9, 6, 5)
Once you define iterator for
Vector, you can
• Pass it as an iterable parameter
>>> v = Vector(7, 1, 4, 3, 9, 6, 5)
>>> max(v) # take it as an iterable
9

• Pass with unpacking operator


>>> print(*v) # same print(v[0],v[1],v[2],v[3],v[4],v[5],v[6])
7 1 4 3 9 6 5

• Compare with printing w/out unpacking


>>> print(v)
Vector(7, 1, 4, 3, 9, 6, 5)

• All are done by it=iter(v) and next(it) behind


the scenes
Application example:
simple blackjack
• Dealer (computer) shuffles cards
• for as many times as the player (user) requests
• Loop
• Dear issues one card # it = iter(deck); c = next(it)
• Player decides more cards or stop
• Ace = 1 point, 2 = 2 points...; J, Q, K = 10 points
• if total = 21: player wins
• elif total > 21: player loses
• elif total < 21, next card + total <= 21: player loses
Card representation
• Card
• SUITS = { club, spade, heart, diamond } #

• FACES = ( 'A', 2, ..., 10, 'J', 'Q', 'K' )


• Deck
• list of 52 cards (4 suits, 13 faces)
• method to shuffle cards by shuffling list items
• __iter__ to return iterator on list of cards
(no need to define DeckIterator class)
Card class
class Card:
ACE, JACK, QUEEN, KING = 'A', 'J', 'Q', 'K'
FACES = (ACE, 2, 3, 4, 5, 6, 7, 8, 9, 10, JACK, QUEEN, KING)
SUITS = tuple(map(chr, (9824, 9827, 9829, 9830)))
SPADE, CLUB, HEART, DIAMOND = SUITS # ♠ ♣ ♥ ♦

def __init__(self, suit, face):


self._suit = suit
self._face = face

def __int__(self):
if self._face in {[Link], [Link], [Link]}:
return 10
return 1 if self._face == [Link] else self._face

def __str__(self):
return self._suit + str(self._face)

def __repr__(self):
return __class__.__name__ + repr((self._suit, self._face))
Example use of Card class
>>> c = Card([Link], 7)
>>> c
Card('♠', 7)
>>> print( c )
♠7
>>> int(c)
7
Deck class
class Deck:
def __init__(self):
self._deck = [Card(suit, face) \
for suit in [Link] for face in [Link]]

def shuffle(self):
import random
[Link](self._deck)

def __iter__(self):
return iter(self._deck)

>>> d = Deck()
>>> list(map(str, d._deck))
['♠A', '♠2', '♠3', '♠4', '♠5', '♠6', '♠7', '♠8', '♠9', '♠10', '♠J',
'♠Q', '♠K', '♣A', '♣2', '♣3', '♣4', '♣5', '♣6', '♣7', '♣8', '♣9',
'♣10', '♣J', '♣Q', '♣K', '♥A', '♥2', '♥3', '♥4', '♥5', '♥6', '♥7',
'♥8', '♥9', '♥10', '♥J', '♥Q', '♥K', '♦A', '♦2', '♦3', '♦4', '♦5',
'♦6', '♦7', '♦8', '♦9', '♦10', '♦J', '♦Q', '♦K']
Deck class use
>>> d = Deck()
>>> di = iter(d)
>>> next(di)
Card('♠', 'A')
>>> next(di)
Card('♠', 2)
>>> [Link]()
>>> list(map(str, d._deck))
['♠5', '♠4', '♥4', '♠8', '♦K', '♣3', '♥7', '♠9', '♥5', '♥9', '♣4',
'♦Q', '♣7', '♥K', '♣5', '♣8', '♦J', '♣K', '♠6', '♥10', '♣J', '♠Q',
'♦3', '♠3', '♦6', '♥A', '♠K', '♦7', '♦A', '♠2', '♦9', '♦10', '♣A',
'♣9', '♥2', '♦5', '♦2', '♦4', '♠J', '♥J', '♥3', '♥Q', '♥8', '♥6',
'♠A', '♠7', '♠10', '♣10', '♣2', '♣6', '♣Q', '♦8']
>>> di = iter(d)
>>> next(di), next(di), next(di), next(di)
(Card('♠', 5), Card('♠', 4), Card('♥', 4), Card('♠', 8))
single-player BlackJack
def BlackJack():
D = Deck() # make a deck of cards
[Link]() # shuffle once here, but could loop
total = 0 # initialize total to 0
it = iter(D) # make iterator of the deck
while True:
c = next(it) # draw the next card
total += int(c) # add card as integer to total
print(f'your card: {c}, total = {total}.', end='')
if total > 21:
print(f'you lose! total = {total}')
break
if total == 21:
print('you win! total = 21')
break
ans = input('More cards? [y/n] ')
if ans not in {'Y', 'y'}:
# draw one more and test
c = next(it)
print(f'next card is {c}. You '+ \
('win' if total + c > 21 else 'lose'))
break
Example session of BlackJack
>>> BlackJack()
your card: ♣4, total = 4. more card? [y/n] y
your card: ♠8, total = 12. more card? [y/n] y
your card: ♠5, total = 17. more card? [y/n] y
your card: ♦5, total = 22. you lose! total = 22
>>> BlackJack()
your card: ♣2, total = 2. more card? [y/n] y
your card: ♦K, total = 12. more card? [y/n] y
your card: ♠8, total = 20. more card? [y/n] n
next card is ♦9. You win
>>> BlackJack()
your card: ♠4, total = 4. more card? [y/n] y
your card: ♣3, total = 7. more card? [y/n] y
your card: ♣A, total = 8. more card? [y/n] y
your card: ♠6, total = 14. more card? [y/n] y
your card: ♦Q, total = 24. you lose! total = 24
Generator in Python3
• Two ways to go back to caller
• return
• => activation record is destroyed
• corresponding to StopIteration in iterator
• yield
• => activation record is kept so it can continue
where it left off!
• corresponding to next() in iterator
Example generator: fibonacci
number
def fib():
yield 0 # for n = 0
yield 1 # for n = 1
fn_minus_2 = 0
fn_minus_1 = 1
n = 2
while True:
fn = fn_minus_2 + fn_minus_1
yield fn
fn_minus_2, fn_minus_1 = fn_minus_1, fn

>>> f = fib()
>>> next(f), next(f), next(f), next(f), next(f), next(f)
(0, 1, 1, 2, 3, 5, 8, 13)
>>> next(f)
21

looks a lot like an iterator with use of next()!!!


How a generator works
• initial call to function is actually to get the
generator object, not to get return value!
• f = fib() # think "instantiate" a generator
• To run it, call next() like an iterator
• next(f) will "continue" where the generator left off
until the next yield or return statement
• yield => save the place to continue;
return => will not come back! (optional)
Difference between iterator and
generator
• iterator • generator
• defined as a class • defined as a function
• implements __next__ • uses yield for next
• raise StopIteration • return from function
try: try:
next() next()
except StopIteration except StopIteration

• iterates an iterable • not tied to iterable


• can be unpacked • cannot be unpacked
• directly convertible to list • list comprehension
• can be used in for loop • can be used in for loop
Recursive generator
• recursive call using for-loop (easiest)
• base case or finished => return
• intermediate results => yield
• that is, one generator can make other
generators recursively
• must pass yielded values all the way to original
caller
Example recursive generator:
atom_gen()
def atom_gen(L):
if L is None: # base case
return # raises StopIteration
if type(L) in {list, tuple}: # recursive
for child in L:
# recursively generate each child of L
for atom in atom_gen(child):
# anything yielded must be atom
yield atom
else: # base case
yield L

>>> L = ['F1', ['F4', 'F5', ['F8']], 'F2', 'F3', 'D3', ['F6', 'F7']]
>>> [i for i in atom_gen(L)]
['F1', 'F4', 'F5', 'F8', 'F2', 'F3', 'D3', 'F6', 'F7']

Note: use list comprehension to create a list from generator output,


because it is a kind of for loop, rather than list(iterable)
sending to generator and
receiving in generator
• Caller • Gen (param)
• g = Gen(param) • setup code
• r1 = next(g) • s1 = yield( r1 )
• try:
• r2 = [Link](s1) • s2 = yield( r2 )
• r3 = [Link](s2) • s3 = yield ( r3 )
• except StopIteration • return R
as R:
Illustrative example
def GenG():

def GenF():
❶ print('GenG')
f = GenF()
❸ print('GenF') ❷ print('GenG constructed f')
a = yield 1 h = next(f)
❺ print('GenF a =', repr(a))
❹ print('GenG started f')
b = yield 2 i = [Link]('a')
❼ print('GenF
c = yield 3
b =', repr(b)) ❻ print('GenG sent a')
j = [Link]('b')
print('GenF c =', repr(c))
❽ print('GenG sent b')
❾ print('h i j = ', h, i, j)
>>> GenG()
❶ GenG
❷ GenG constructed f
❸ GenF
❹ GenG started f
❺ GenF a = 'a'
❻ GenG sent a
❼ GenF b = 'b'
❽ GenG sent b
h i j = 1 2 3

Illustrative example
def GenG():

def GenF():
❶ print('GenG')
f = GenF()
❸ print('GenF') ❷ print('GenG constructed f')
a = yield 1 h = next(f)
❺ print('GenF a =', repr(a))
❹ print('GenG started f')
b = yield 2 i = [Link]('a')
❼ print('GenF
c = yield 3
b =', repr(b)) ❻ print('GenG sent a')
j = [Link]('b')
print('GenF c =', repr(c))
❽ print('GenG sent b')
❾ print('h i j = ', h, i, j)
>>> GenG()
❶ GenG
❷ GenG constructed f
❸ GenF
❹ GenG started f
❺ GenF a = 'a'
❻ GenG sent a
❼ GenF b = 'b'
❽ GenG sent b
h i j = 1 2 3

Generator version of BlackJack:
Separate Dealer and Player
• Dealer
• instantiates Deck and shuffle
• instantiates Player, draws & sends one card at a time
• Player
• receives card from yield, tracks total as long as total < 21
• yields 'yes'/'no' more card, or 'won'/'lost'
• Dealer
• base on Player response, yes => loop, 'won'/lost' => stop, or
'no' => draw one more an decide
Generator version of Blackjack:
Dealer
def Dealer():
player = Player()
D = Deck()
[Link]()
it = iter(D)
ans = next(player) # let player run, expect hardwired "yes"
total = 0
while ans == 'yes':
c = next(it) # draw next card
total += int(c)
print(f'your card: {c}, ', end='')
ans = [Link](c)
if ans in {'lost', 'won'}:
print(f'you {ans}')
else: # draw one more card and see
c = next(it)
print(f'next card: {c}, you '+('lost' if total+int(c) <= 21 \
else 'won'))
Generator version of Blackjack:
Player
def Player():
c = yield('yes')
total = int(c)
while total < 21:
ans = input(f'total = {total}, more card? [y/n] ')
if ans not in {'Y', 'y'}: # assume no if not yes
yield("no") # does not come back
else:
c = yield("yes")
total += int(c)
# either we reached or exceeded 21, so no more card
yield 'lost' if total > 21 else 'won'
Generator Blackjack: sample run
$ python -i [Link]
>>> Dealer()
your card: ♦A, total = 1, more card? [y/n] y
your card: ♣7, total = 8, more card? [y/n] y
your card: ♣3, total = 11, more card? [y/n] y
your card: ♦4, total = 15, more card? [y/n] y
your card: ♣10, you lost
>>> Dealer()
your card: ♠10, total = 10, more card? [y/n] y
your card: ♦5, total = 15, more card? [y/n] y
your card: ♦A, total = 16, more card? [y/n] y
your card: ♠2, total = 18, more card? [y/n] n
next card: ♦3, you lost
>>> Dealer()
your card: ♥5, total = 5, more card? [y/n] y
your card: ♠Q, total = 15, more card? [y/n] n
next card: ♣8, you won
>>> Dealer()
your card: ♠A, total = 1, more card? [y/n] y
your card: ♣J, total = 11, more card? [y/n] y
your card: ♦3, total = 14, more card? [y/n] y
your card: ♥6, total = 20, more card? [y/n] n
next card: ♠3, you won
Review of Iterables, Iterators,
and Generators
• Ways of producing items one at a time
• Iterable = data content (list, str, set, range(), ..)
• Iterator = "cursor" that tracks iterable's state
• Generator = function that "yields"
(a way to make an object without instantiating from class!)
• Purposes
• allowing program to be structured like for-loop, unpacking
assignment / parameters (iterators)
• performance optimization - lazy or eager evaluation (e.g.,
disk, network, compression)
Summary: Iterables
• Calling iter() on iterable to get an iterator
• iterable can specify iterator to use by __iter__, or
• iterable can let Python's iterator call __getitem__

• iter() called by for-loop, unpacking


• user only sees the iterable; does not need to see iterator
• r = iter(iterable) called by user
• call next(r) to get the subsequent item
• StopIteration exception when out of items
Summary: Generator
• Caller of generator
• g = generator() looks like a function call but makes generator
• next(g) to start the generator and resume generator
• r = [Link](s) to resume while sending value generator

• Generator code
• yield( r ) transfers control back to caller, can resume
• s = yield( r ) receive the value sent by subsequent
[Link](s)
• return (implicit or explicit) causes StopIteration

You might also like