Iteration simply refers to the process of accessing — one by one — the items stored in some container. The order of the items, and whether or not the iteration is comprehensive, depends on the container.
In Python, we typically perform iteration using the for
loop.
# e.g., iterating over a list
l = [2**x for x in range(10)]
for n in l:
print(n)
# e.g., iterating over the key-value pairs in a dictionary
d = {x:2**x for x in range(10)}
for k,v in d.items():
print(k, '=>', v)
iter
, and next
¶We can iterate over anything that is iterable. Intuitively, if something can be used as the source of items in a for
loop, it is iterable.
But how does a for
loop really work? (Review time!)
l = [2**x for x in range(10)]
l
type(l)
it = iter(l) # or it = l.__iter__()
type(it)
next(it) #repeat evaluating it multiple times
it = iter(l)
while True:
try:
x = next(it) #try to get the next value from the interation
print(x)
except StopIteration: # this exception occurs if there is no more values
break
for x in l:
print(x)
it = iter(l)
for x in it: #difference with "for x in l:" ?
print(x)
class MyIterator:
def __init__(self, max):
self.max = max
self.curr = 0
# the following methods are required for iterator objects
def __next__(self):
if self.curr == self.max:
raise StopIteration
else:
ret = self.curr
self.curr += 1
return ret
def __iter__(self):
return self
it = MyIterator(10)
next(it)
it = MyIterator(10)
while True:
try:
print(next(it))
except StopIteration:
break
it = MyIterator(10)
for i in it: # def __iter__(self) is required for this statement
print(i)
For a container type, we need to implement an __iter__
method that returns an iterator.
# now implement __iter__()
# Note that ArrayList is not an iterator, but an iterable; so no implementation of __next__()
class ArrayListIterator:
def __init__(self, lst):
self.lst = lst
self.currIdx = 0
def __next__(self):
if self.currIdx < len(self.lst.data):
ret = self.lst.data[self.currIdx]
self.currIdx += 1
return ret
else:
raise StopIteration
def __iter__(self):
return self
class ArrayList:
def __init__(self):
self.data = []
def append(self, val):
self.data.append(None)
self.data[len(self.data)-1] = val
def __iter__(self):
ret = ArrayListIterator(self)
return ret
l = ArrayList()
for x in range(10):
l.append(2**x)
it = iter(l)
type(it)
next(it)
for x in l: #not one time thing, iterable, not iterator
print(x)
#how to ensure that no one else can create an instance of ArrayListIterator?
class ArrayList:
def __init__(self):
self.data = []
def append(self, val):
self.data.append(None)
self.data[len(self.data)-1] = val
def __iter__(self):
class ArrayListIterator:
def __init__(self, lst):
self.lst = lst
self.currIdx = 0
def __next__(self):
if self.currIdx < len(self.lst.data):
ret = self.lst.data[self.currIdx]
self.currIdx += 1
return ret
else:
raise StopIteration
def __iter__(self):
return self
ret = ArrayListIterator(self)
return ret
l = ArrayList()
it = iter(l)
type(it)
yield
¶What's a "generator"?
l = [2**x for x in range(10)]
l
len(l)
#generator expression
g = (2**x for x in range(10))
g
type(g)
for x in g: #only once
print(x)
next(g)
# generator
# a special type of iterator
# generate as you need it
g = ((a, b, c) for a in range(1, 100)
for b in range(1, 100)
for c in range(1, 100)
if a*a + b*b == c*c)
next(g)
l = [(a, b, c) for a in range(1, 100)
for b in range(1, 100)
for c in range(1, 100)
if a*a + b*b == c*c]
l
l[1]
next(g)
#generator function
def foo():
yield
foo()
def foo():
print("hello")
yield
foo()
def foo():
print("hello")
if 0 > 10:
yield
foo()
g = foo()
type(g)
next(g)
def foo():
print("hello")
yield 1
g = foo()
next(g)
def foo():
print('L1')
yield 1
print("L2")
yield 5
print('L3')
yield 'ouch'
g = foo()
next(g)
def range_gen(max):
i = 0
while True:
if i == max:
break
else:
yield i
i += 1
g = range_gen(10)
next(g)
# implement our own range
for i in range_gen(10):
print(i)
# one more example of generator function
def infinitely(val):
while True:
yield val
g = infinitely('hello')
next(g)
# one more example of generator function
def fibonacci_series():
i, j = 1, 1
while True:
yield i
i, j = j, i+j
g = fibonacci_series()
next(g)
class ArrayList:
def __init__(self):
self.data = []
def append(self, val):
self.data.append(None)
self.data[len(self.data)-1] = val
def __iter__(self):
for i in range(len(self.data)):
yield self.data[i]
l = ArrayList()
for x in range(10):
l.append(2**x)
for y in l:
print(y)