Generator
Iterable Objects
  • Iterable is an object, which one can iterate over
  • from collections.abc import Iterator, Iterable
    
    # List
    l = [1, 2, 3, 4, 5, 6]
    isinstance(l, Iterable) # True
    
    # Range
    r = range(10)
    isinstance(r, Iterable) # True
    
    # Tuple
    t = (1, 2, 3, 4)
    isinstance(t, Iterable) # True
    
    # String
    s = 'Hello World!'
    isinstance(s, Iterable) # True
    
    # Dict
    d = {'x':1, 'y':2}
    isinstance(d, Iterable) # True
    		
    Iterator
  • Iterator is an object, which is used to iterate over an iterable object using __next__() method
  • # List
    l = [1, 2, 3, 4, 5, 6]
    obj = iter(l) # list_iterator
    isinstance(obj, Iterator) # True
    
    # Range
    r = range(10)
    obj = iter(r) # range_iterator
    isinstance(obj, Iterator) # True
    
    # Tuple
    t = (1, 2, 3, 4)
    obj = iter(t) # tuple_iterator
    isinstance(obj, Iterator) # True
    
    # String
    s = 'Hello World!'
    obj = iter(s) # str_iterator
    isinstance(obj, Iterator) # True
    
    # Dict
    d = {'x':1, 'y':2}
    obj = iter(d) # dict_keyiterator
    isinstance(obj, Iterator) # True
            
    l = [1, 2, 3, 4, 5, 6]
    obj = iter(l) # iterator
    
    try:
        while True:
            #if no more element, next raise StopIteration
            item = next(obj) # int
    except StopIteration:
        pass
    		
    Implement Iterable and Iterator as Classes
  • Iterable and iterator are the same object
  • class yrange:
        def __init__(self, n):
            self.i = 0
            self.n = n
     
        def __iter__(self): # make an object to be iterable
            print('iter')
            self.i = 0
            return self # return an iterator
     
        def __next__(self): # make an object to be iterator
            print('next')
            if self.i < self.n:
                i = self.i
                self.i += 1
                return i
            else:
                raise StopIteration()
            
    y = yrange(10)
    isinstance(y, Iterable) # True
    isinstance(y, Iterator) # True
            
    # use as an iterable
    y = yrange(3)
    # call _iter_ to initialize, then call __next__ to iterate
    for i in y: # print 0, 1, 2
        print(i)
    
    for i in y: # print 0, 1, 2
        print(i)
            
    # use as an iterator
    y = yrange(3)
    try:
        # call _next__ to iterate
        while True: # print 0, 1, 2
            temp = next(y) # int
            print(temp)
    except StopIteration:
        pass
    
    try:
        while True: # print nothing, iterator has been comsumed
            temp = next(y) # int
            print(temp)
    except StopIteration:
        pass
    		
  • Iterable and iterator are not the same object
  • # define iterable
    class zrange:
        def __init__(self, n):
            self.n = n
     
        def __iter__(self):
            print('iter')
            return zrange_iter(self.n)
     
    # define iterator
    class zrange_iter:
        def __init__(self, n):
            self.i = 0
            self.n = n
        
        def __next__(self):
            print('next')
            if self.i < self.n:
                i = self.i
                self.i += 1
                return i
            else:
                raise StopIteration()
    
    z = zrange(3)
    isinstance(z, Iterable) # True
    isinstance(z, Iterator) # False
    
    # used as an iterable
    z = zrange(3)
    for i in z: # call _iter_ to initialize, then call __next__ to iterate
        print(i)
    		
    Generator
  • Generator is iterator, can be consumed in a single iteration
  • import types
    
    # list
    l = [x*x for x in range(4)]; # [0, 1, 4, 9]
    print(l)
     
    # generator
    g = (x*x for x in range(4));
    isinstance(g, Iterable) # True
    isinstance(g, Iterator) # True
    isinstance(g, types.GeneratorType) # True
    
    for i in g: # 0 1 4 9
    	print(i)
    
    for i in g: # do nothing, g has been consumed in the above for loop
    	print(i)
    		
    Implement Generator
    def grange(n):
        i = 0;
        while i < n:
            yield i;
            i += 1
            
    g = grange(10);
    
    isinstance(g, Iterable) # True
    isinstance(g, Iterator) # True
    isinstance(g, types.GeneratorType) # True
    
    # use as an iterable
    for i in g:
    	print(i)
     
    for i in g: # do not print anything, g has been consumed
    	print(i)
    
    # use as an iterator
    try:
        while True: 
            temp = next(g) # int
            print(temp)
    except StopIteration:
        pass
    
    try:
        while True: # print nothing, iterator has been comsumed
            temp = next(g) # int
            print(temp)
    except StopIteration:
        pass
    		
    Memory Size
    import sys
    
    # list, iterable
    l = [x*x for x in range(100)]
    sys. getsizeof(l) # 920
    
    # list iterator
    obj = iter(l)
    sys. getsizeof(obj) # 48
            
    # generator, iterable, iterator
    g = (x*x for x in range(100))
    sys. getsizeof(g) # 112
    
    obj = iter(g)
    sys. getsizeof(obj) # 112
            
    # range, iterable
    r = range(100)
    sys. getsizeof(r) # 48
    
    obj = iter(r)
    sys. getsizeof(obj) # 48
            
    Reference