In [1]:
#Iterators and Iterable
#Iterable means something that can be looped over or specifically object needs to return iterable object from its dunder iter method and the iterator thats returning must find dunder next method which access elements in container one at a time. 
#Something thats Iteratable doesnot mean it's a iterator
#Iterator means it's an object with a state, so it remembers where's at during it's iteration, knows how to fetch next value using __next method and when doesn't have next value it raises StopIteration exception.
In [2]:
nums = [1,2,3]
In [3]:
for num in nums:
    print(num)
1
2
3
In [4]:
#something can be iteratable if it has __iter__ method.
print(dir(nums))
#we can see list has this method
['__add__', '__class__', '__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]:
print(next(nums))
#it actually tries to run dunder next method on that object and list doesnot have dunder next method
#when we ran dunder iter method, it returns iterators for us
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-ddf9ec797e52> in <module>
----> 1 print(next(nums))

TypeError: 'list' object is not an iterator
In [11]:
i_nums = nums.__iter__() # or iter(nums)
print(i_nums)
print(dir(i_nums))
<list_iterator object at 0x7f14cc90e358>
['__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__']
In [12]:
print(next(i_nums))
print(next(i_nums))
1
2
In [13]:
#iterators can only go forward and can't be reset
nums = [1,2,3]
i_nums = iter(nums)
while True:
    try:
        print(next(i_nums))
    except StopIteration:
        break
1
2
3
In [15]:
class MYRange():
    
    def __init__(self,start,end):
        self.start = start
        self.end = end
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.start >= self.end:
            raise StopIteration
        current = self.start
        self.start += 1
        return current
            
In [24]:
nums = MYRange(1,10)
In [17]:
for num in nums:
    print(num)
1
2
3
4
5
6
7
8
9
In [25]:
print(next(nums))
print(next(nums))
print(next(nums))
print(next(nums))
print(next(nums))
1
2
3
4
5
In [8]:
#same problem using generator
def gen_func(start,end):
    current = start
    while current < end:
        yield current
        current += 1
In [9]:
my_var = gen_func(1,10)
In [3]:
for v in my_var:
    print(v)
1
2
3
4
5
6
7
8
9
In [10]:
print(next(my_var))
print(next(my_var))
print(next(my_var))
print(next(my_var))
print(next(my_var))
1
2
3
4
5
In [9]:
#splitting words from sentence
class Sentence():
    
    def __init__(self,sent):
        self.sent = sent
        self.index = 0
        self.words = self.sent.split()
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index >= len(self.words):
            raise StopIteration
        index = self.index
        self.index += 1
        return self.words[index]

words = Sentence("This is a test")
In [10]:
for word in words:
    print(word)
This
is
a
test
In [6]:
#using next method
print(next(words))
print(next(words))
print(next(words))
print(next(words))
This
is
a
test
In [20]:
#same problem using generator
def my_gen(sent):
    for word in sent.split():
        yield word
In [21]:
words = my_gen("this is a test")
In [19]:
for word in words:
    print(word)
this
is
a
test
In [22]:
print(next(words))
print(next(words))
print(next(words))
this
is
a