Iteration
Iteration is a general term for taking each item of something, one after another. Any time you
use a loop, explicit or implicit, to go over a group of items, that is iteration.
In [1]: num = [1,2,3]
for i in num:
print(i)
1
2
3
Iterator
An iterator is an object that allows the programmer to traverse through a sequence of data
without having to store the entire data in the memory.
In [ ]: import sys
L = [x for x in range(1,10000000)]
print(len(L))
print(sys.getsizeof(L)/1024)
for i in L:
print(i*2)
In [ ]: L = [x for x in range(1,100000)]
for i in L:
print(i*2)
pass
import sys
sys.getsizeof(L)/1024
In [2]: import sys
x = range(1,10000000000) #iterator
for i in x:
print(i*2)
pass
print(sys.getsizeof(x)/1024)
print(x)
0.046875
range(1, 10000000000)
In [ ]: for i in x:
print()
Iterable
Iterable is an object, which one can iterate over.
It generates an iterator when passed to iter() method.
In [1]: L = [1,2,3]
print(type(L))
# L is an iterable
print(type(iter(L)))
<class 'list'>
<class 'list_iterator'>
Points to remember
1. Every Iterator is also an Iterable.
2. Not all Iterables are Iterators.
3. Every Iterable has an iter function.
4. Every Iterator has both iter function as well as a next function.
In [2]: a = 2
dir(a)
Out[2]: ['__abs__',
'__add__',
'__and__',
'__bool__',
'__ceil__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floor__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getnewargs__',
'__gt__',
'__hash__',
'__index__',
'__init__',
'__init_subclass__',
'__int__',
'__invert__',
'__le__',
'__lshift__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__or__',
'__pos__',
'__pow__',
'__radd__',
'__rand__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rlshift__',
'__rmod__',
'__rmul__',
'__ror__',
'__round__',
'__rpow__',
'__rrshift__',
'__rshift__',
'__rsub__',
'__rtruediv__',
'__rxor__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'__xor__',
'as_integer_ratio',
'bit_count',
'bit_length',
'conjugate',
'denominator',
'from_bytes',
'imag',
'numerator',
'real',
'to_bytes']
In [3]: #T = (1,2,3)
#T = {1,2,3}
T = {1:2,2:3}
dir(T)
Out[3]: ['__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__ior__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__or__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__ror__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'clear',
'copy',
'fromkeys',
'get',
'items',
'keys',
'pop',
'popitem',
'setdefault',
'update',
'values']
In [4]: L = [1,2,3]
#L is not an iterator
dir(L)
Out[4]: ['__add__',
'__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append',
'clear',
'copy',
'count',
'extend',
'index',
'insert',
'pop',
'remove',
'reverse',
'sort']
In [5]: x = iter(L)
dir(x)
Out[5]: ['__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__length_hint__',
'__lt__',
'__ne__',
'__new__',
'__next__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setstate__',
'__sizeof__',
'__str__',
'__subclasshook__']
Understanding how for loop works
In [6]: num = [1,2,3] # iterable
for i in num:
print(i)
1
2
3
In [10]: num = [1,2,3]
# fetches the iterator
iter_num = iter(num)
# call next
print(next(iter_num))
print(next(iter_num))
print(next(iter_num))
next(iter_num)
1
2
3
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In [10], line 10
8 print(next(iter_num))
9 print(next(iter_num))
---> 10 next(iter_num)
StopIteration:
Making our own for loop
In [11]: def own_for(iterable):
iterator = iter(iterable)
while True:
try:
print(next(iterator))
except StopIteration:
break
In [16]: a = [1,2,3]
b = range(1,11)
c = (1,2,3)
d = {1,2,3}
e = {0:1,1:1}
own_for(e)
0
1
Confusion Point
In [17]: num = [1,2,3]
iter_obj = iter(num)
print('Address of iterator 1',id(iter_obj))
iter_obj2 = iter(iter_obj)
print('Address of iterator 2',id(iter_obj2))
Address of iterator 1 2489971767328
Address of iterator 2 2489971767328
Creating our own range() function
In [ ]: # iterable
class my_range:
def __init__(self,start,end):
self.start = start
self.end = end
def __iter__(self):
return my_range_iterator(self)
# iterator
class my_range_iterator:
def __init__(self,iterable_obj):
self.iterable = iterable_obj
def __iter__(self):
pass
def __next__(self):
if self.iterable.start >= self.iterable.end:
raise StopIteration
current = self.iterable.start
self.iterable.start += 1
return current
In [ ]: for i in my_range(1,11):
print(i)
In [ ]: x = my_range(1,11)
type(x)
What is a Generator
Python generators are a simple way of creating iterators.
In [ ]: import sys
L = [x for x in range(100000)]
#for i in L:
#print(i**2)
sys.getsizeof(L)
In [ ]: x = range(100000)
sys.getsizeof(x)
Simple Example of Generator
In [24]: def gen_demo():
yield "first statement"
yield "second statement"
yield "third statement"
In [25]: gen = gen_demo()
print(gen)
<generator object gen_demo at 0x00000243BE6344A0>
In [20]: for i in gen:
print(i)
first statement
second statement
third statement
In [26]: print(next(gen))
print(next(gen))
print(next(gen))
first statement
second statement
third statement
In [27]: print(next(gen))