cells: - markdown: | # Advanced Python: Iterators and Generators ### Prabhu Ramachandran ### The FOSSEE Python group & ### Department of Aerospace Engineering ### IIT Bombay metadata: slideshow: slide_type: slide - markdown: | ## Background - Container objects present a similar interface - They can all be looped over using `for` metadata: slideshow: slide_type: slide - code: | for i in [1, 2, 3]: print(i) for key in {'a': 1, 'b': 2}: print(key) for char in 'hello world': print(char) for line in open('file.txt'): print(line, end='') - markdown: | ## Background - Similar interface for different objects - These objects are all **iterable** - They are all **iterators** metadata: slideshow: slide_type: subslide - markdown: | ## Introduction - This is a powerful feature/abstraction - Easy to create your own iterators

metadata: slideshow: slide_type: slide - markdown: | - A **generator** makes it easy to create an iterator - How would you write a `range` function?

- Use the `yield` keyword metadata: slideshow: slide_type: fragment - code: | def counter(n): count = 0 while count < n: yield count count += 1 metadata: slideshow: slide_type: slide - code: | c = counter(2) - code: | next(c) - code: | next(c) - code: | next(c) - code: | for i in counter(5): print(i) metadata: slideshow: slide_type: slide - markdown: | ## Observations - Note that `yield` resumes the function - The function/method remembers its state - It can yield any object - An empty `yield` is also acceptable - You can have multiple yields metadata: slideshow: slide_type: slide - code: | def infinite_generator(): while True: yield metadata: slideshow: slide_type: fragment - markdown: | ## Exercise: simple generator Write a simple generator that generates the first `n` odd numbers. Call this function `odd(n)`. metadata: slideshow: slide_type: slide - code: | for x in odd(5): print(x) - markdown: | ## Solution metadata: slideshow: slide_type: slide - code: | def odd(n): for i in range(n): yield 2*i + 1 - markdown: | ## Exercise: Fibonacci generator Write a simple generator that returns the first `n` numbers of the Fibonacci sequence, call this function `fib(n)`. metadata: slideshow: slide_type: slide - code: | for x in fib(10): print(x) - markdown: | ## Solution metadata: slideshow: slide_type: slide - code: | def fib(n): a, b = 0, 1 yield 0 for i in range(n-1): yield b a, b = b, a + b - markdown: | ## Creating iterable objects - What if you want to create an object that is iterable? - Overload the `__iter__` and/or `__next__` methods. - Recall our `Zoo` class? metadata: slideshow: slide_type: slide - code: | class Zoo: def __init__(self, *animals): self.animals = list(animals) - markdown: | We want the following to work, metadata: slideshow: slide_type: slide - code: | c = Animal('crow') m = Mammal('dog') z = Zoo(c, m) for animal in z: animal.greet() - markdown: | ## Making `Zoo` iterable - Overload the `__iter__` to return an iterable - An iterable should have a `__next__` method metadata: slideshow: slide_type: slide - code: | class Zoo: def __init__(self, *animals): self.animals = list(animals) def __iter__(self): return iter(self.animals) - code: | class Animal(object): def __init__(self, name): self.name = name def greet(self): print(self.name, "says hi!") class Mammal(Animal): pass metadata: slideshow: slide_type: slide - code: | c = Animal('crow') m = Mammal('dog') z = Zoo(c, m) for animal in z: animal.greet() metadata: slideshow: slide_type: slide - markdown: | ## Observations - Just returns `iter(self.animals)` - `self.animals` is a `list` and calling `iter` makes it an iterator metadata: slideshow: slide_type: slide - markdown: | ## A more complex example - What if we cannot just return an internal list? - We must provide a `__next__` method - To stop the iteration: `raise StopIteration` metadata: slideshow: slide_type: slide - code: | class SimpleRange: def __init__(self, n): self.n = n self.count = 0 def __iter__(self): return self def __next__(self): if self.count == self.n: raise StopIteration self.count += 1 return self.count metadata: slideshow: slide_type: subslide - code: | r = SimpleRange(5) for i in r: print(i) metadata: slideshow: slide_type: subslide - markdown: | ## Observations - Useful when you want an object to be iterable - Notice the `__next__` method - Notice the `raise StopIteration` - Easier to use functions/methods with `yield` metadata: slideshow: slide_type: slide - markdown: | ## Summary - Easy to use and write - Allows one to abstract a loop - `yield` for functions and methods - Overload `__iter__, __next__` for objects metadata: slideshow: slide_type: slide - markdown: | ## Exercise: Fibonacci object Create a class that when instantiated with an argument `n` can be used as a sequence of the first `n` numbers of the Fibonacci sequence, call this class `Fib`. metadata: slideshow: slide_type: slide - code: | f = Fib(5) for x in f: print(x) - markdown: | ## Solution metadata: slideshow: slide_type: slide - code: | class Fib: def __init__(self, n): self.count = n self.a, self.b = 0, 1 def __iter__(self): return self def __next__(self): if self.count == 0: raise StopIteration old = self.a self.count -= 1 self.a, self.b = self.b, self.a + self.b return old - code: | f = Fib(10) for i in f: print(i) metadata: slideshow: slide_type: slide # The lines below here may be deleted if you do not need them. # --------------------------------------------------------------------------- metadata: celltoolbar: Slideshow kernelspec: display_name: Python 3 language: python name: python3 language_info: codemirror_mode: name: ipython version: 3 file_extension: .py mimetype: text/x-python name: python nbconvert_exporter: python pygments_lexer: ipython3 version: 3.5.2 rise: scroll: true transition: none nbformat: 4 nbformat_minor: 2