#!/usr/bin/python3 # -*- coding: utf-8 -*- """ Generators create lazy iterators. Generator functions allow you to declare a function that behaves like an iterator, i.e. it can be used in a for loop. Generators are a special kind of function, that uses yield instead of return, which enable us to implement or generate iterators. On the surface generators in Python look like functions. The yield statement turns a functions into a generator. A generator is a function which returns a generator object. This generator object can be seen like a function, which produces a sequence of results instead of a single object. This sequence of values is produced by iterating over it, e.g. with a for loop. A list comprehension in Python works by loading the entire output list into memory. For small or even medium-sized lists, this is generally fine. If you want to sum the squares of the first one-thousand integers, then a list comprehension will solve this problem admirably: >>> squares = [i * i for i in range(1000)] >>> for square in squares: ... print(square) But what if you wanted to progressively see the squares of the first billion integers? If you attempt this using a list, it may not complete! That's because Python is trying to create a list with one billion integers, which consumes more memory than your computer would like. """ # https://mypy.readthedocs.io/en/stable/kinds_of_types.html from typing import Iterator, Generator, Sequence, Any, Iterable # A really simple city-geterator: # Note: type-hinting can use either Iterator or Generator def city_generator() -> Iterator[str]: yield ("London") yield ("Hamburg") yield ("Konstanz") yield ("Amsterdam") yield ("Berlin") yield ("Zurich") yield ("Schaffhausen") # Note if you ever want to quit generating, just return return # Note: raise stopIteration used to be used for this (but isn't anymore) yield ("Stuttgart") print(city_generator) print(type(city_generator)) for city in city_generator(): print(city) # Another easy one to count infinitely up # Note: while you can type-hint this way, the above method is sufficient here instead. def my_range(n: int) -> Generator[int, None, None]: i = 0 while i < n: yield i i += 1 print(my_range) print(type(my_range)) range_obj = my_range(10) print(range_obj) print(type(range_obj)) for i in range_obj: print(i) # Or, to reverse an iterable: def reverse(data: Sequence[Any]) -> Iterator[Any]: for index in range(len(data) - 1, -1, -1): yield data[index] for char in reverse("golf"): print(char) # A more real example: def fibonacci() -> Iterator[int]: """Generates an infinite sequence of Fibonacci numbers on demand""" a, b = 0, 1 while True: yield a a, b = b, a + b fibs = fibonacci() print(type(fibs)) for i in range(10): print(next(fibs)) for i in fibonacci(): print(i) if i > 100: break # Why bother?? # Generators help you make your code more like you: # This is lazy "procrastinating" code! def double_numbers(thing: Iterable[float]) -> Iterator[float]: for i in thing: yield i + i # Generators are memory-efficient because # they only load the data needed to # process the next value in the iterable. # This allows them to perform operations on # otherwise prohibitively large value ranges. for num in double_numbers(range(1, 900000000)): # `range` is a generator too... print(num) if num >= 30: break # double numbers does not compute all the values ahead of time! # Just as you can create a list comprehension, you can create generator # comprehensions as well. values = (-x for x in [1, 2, 3, 4, 5]) for x in values: print(x) # prints -1 -2 -3 -4 -5 to console/terminal # You can also cast a generator comprehension directly to a list. values = (-x for x in [1, 2, 3, 4, 5]) gen_to_list = list(values) print(gen_to_list) # => [-1, -2, -3, -4, -5] # similar to list comprehension, but with parentheses: reverse data = "golf" print(list(data[i] for i in range(len(data) - 1, -1, -1))) # Using a generator to "stream" the same computation referenced above gen = (i * i for i in range(1000000000)) print(gen) for i in gen: print(i) if 5 < i: break # If we tried to pre-compute this, it would be inefficient! # +++++++++ Cahoot-20.1 # https://mst.instructure.com/courses/58101/quizzes/57264