Python Iterators and Generators

 
undefined
Iterators & Generators
 
Iterators
 
Iterators and Iterable objects
 
In the last few lectures, while talking about for loops and list
comprehensions, we mentioned the concept of an iterator and that an
object can be iterable.
Today, we'll talk about how to use Python's iterators.  Later in the
course we'll talk about how to make our objects iterable.
In Python, an object is 
iterable
 if it supports the capability to be
processed one element at a time.
Lists and strings are iterable
numbers are not
We use iterators to access the individual elements of an iterable
object, one at a time and in order
Iterators
An 
iterator
 is an object that provides sequential access to values, one
by one.
                              
returns an iterator over the elements of an iterable.
                              returns the next element in an iterator.
iter(iterable)
next(iterator)
toppings = ["pineapple", "pepper", "mushroom", "roasted red pepper"]
topperator = iter(toppings)
next(topperator)
next(topperator)
next(topperator)
next(topperator)
next(topperator)
# 'pineapple'
# 'pepper'
# 'mushroom'
# 'roasted red pepper'
# ❌ StopIteration exception
A useful detail
Calling 
iter()
 on an iterator just returns the iterator:
numbers = ["
一つ
", "
二つ
", "
三つ
"]
num_iter = iter(numbers)
num_iter2 = iter(num_iter)
num_iter is num_iter2
# True
For loop with iterator
When used in a for loop, Python will call 
next()
 on the iterator in each
iteration:
nums = range(1, 4)
num_iter = iter(nums)
for num in num_iter:
    print(num)
Iterables
Lists, tuples, dictionaries, strings, and ranges are all 
iterable
 objects.
my_order = ["Yuca Shepherds Pie", "Pão de queijo", "Guaraná"]
best_topping = "pineapple"
scores = range(1, 21)
prices = {"pineapple": 9.99, "pen": 2.99, "pineapple-pen": 19.99}
Making iterators for iterables
iter() 
can return an iterator for any iterable object.
my_order = ["Yuca Shepherds Pie", "Pão de queijo", "Guaraná"]
order_iter = iter(my_order)
next(order_iter)
best_topping = "pineapple"
topping_iter = iter(best_topping)
next(topping_iter)
scores = range(1, 21)
score_iter = iter(scores)
next(score_iter)
# "Yuca Shepherds Pie"
# "p"
# 1
Making iterators for dictionaries
In Python 3.6+, items in a dict are ordered according to when they
were added.
An iterator for the keys:
An iterator for the values:
An iterator for key/value tuples:
prices = {"pineapple": 9.99, "pen": 2.99, "pineapple-pen": 19.99}
price_iter = iter(prices.keys())
next(price_iter)
price_iter = iter(prices.values())
next(price_iter)
price_iter = iter(prices.items())
next(price_iter)
# "pineapple"
# 9.99
# ("pineapple", 9.99)
For loops with used-up iterators
Iterators are mutable! Once the iterator moves forward, it won't
return the values that came before.
nums = range(1, 4)
num_iter = iter(nums)
first = next(num_iter)
for num in num_iter:
    print(num)
nums = range(1, 4)
sum = 0
num_iter = iter(nums)
for num in num_iter:
    print(num)
for num in num_iter:
    sum += num
print(sum)
Iterating over iterables
If you want all the items from start to finish, it's better to use a for-in
loop.
my_order = ["Yuca Shepherds Pie", "Pão de queijo", "Guaraná"]
for item in my_order:
    print(item)
lowered = [item.lower() for item in my_order]
ranked_chocolates = ("Dark", "Milk", "White")
for chocolate in ranked_chocolates:
    print(chocolate)
best_topping = "pineapple"
for letter in best_topping:
    print(letter)
Reasons for using iterators
Code that processes an iterator using 
iter() 
or 
next() 
makes few
assumptions about the data itself
.
Changing the data storage from a list to a tuple, map, or dict doesn't
require rewriting code.
Others are more likely to be able to use your code on their data.
An iterator 
bundles together a sequence and a position 
within the
sequence in a single object.
Passing that iterator to another function always retains its position.
Ensures that each element of the sequence is only processed once.
Limits the operations that can be performed to only calling 
next()
.
 
 
Useful built-in functions
 
Functions that return iterables
Functions that return iterators
 
 
Generators
 
Generators
A 
generator function
 uses 
yield
 instead of 
return
:
A 
generator
 is a type of iterator that yields results from a generator
function.
Just call the generator function to get back a generator:
def evens():
    num = 0
    while num < 10:
        yield num
        num += 2
evengen = evens()
next(evengen)  # 0
next(evengen)  # 2
next(evengen)  # 4
next(evengen)  # 6
next(evengen)  # 8
next(evengen)  # ❌ StopIteration exception
# 0
# 2
# 4
# 6
# 8
# ❌ StopIteration exception
How generators work
When the function is called, Python immediately returns an iterator without
entering the function.
When next()is called on the iterator, it executes the body of the generator from
the last stopping point up to the next yield statement.
If it finds a yield statement, it pauses on the next statement and returns the value
of the yielded expression.
If it doesn't reach a yield statement, it stops at the end of the function and raises
a StopIteration exception.
def evens():
    num = 0
    while num < 2:
        yield num
        num += 2
gen = evens()
next(gen)
next(gen)
View in PythonTutor
Looping over generators
 
We can use for loops on generators, since generators are just special
types of iterators.
 
 
 
 
 
Looks a lot like:
def evens(start, end):
    num = start + (start % 2)
    while num < end:
        yield num
        num += 2
for num in evens(12, 60):
   print(num)
evens = [num for num in range(12, 60) if num % 2 == 0]
for num in evens:
    print(num)
Why use generators?
Generators are lazy: they only generate the next item when needed.
Why generate the whole sequence...
… if you only want some elements?
A large list can cause your program to run out of memory!
def find_matches(filename, match):
    matched = []
    for line in open(filename):
        if line.find(match) > -1:
            matched.append(line)
    return matched
matched_lines = find_matches('frankenstein.txt', "!")
matched_lines[0]
matched_lines[1]
def find_matches(filename, match):
    for line in open(filename):
        if line.find(match) > -1:
            yield line
line_iter = find_matches('frankenstein.txt', "!")
next(line_iter)
next(line_iter)
Exercise: Countdown
 
def countdown(n):
    """
    Generate a countdown of numbers from n down to 'blast off!'.
    >>> c = countdown(3)
    >>> next(c)
    3
    >>> next(c)
    2
    >>> next(c)
    1
    >>> next(c)
    'blast off!'
    """
Exercise: Countdown (solution)
 
def countdown(n):
    """
    Generate a countdown of numbers from n down to 'blast off!'.
    >>> c = countdown(3)
    >>> next(c)
    3
    >>> next(c)
    2
    >>> next(c)
    1
    >>> next(c)
    'blast off!'
    """
    while n > 0:
        yield n
        n -= 1
    yield "blast off!"
Virahanka-Fibonacci generator
Let's transform this function...
… into a generator function!
def virfib(n):
    """Compute the nth Virahanka-Fibonacci number, for n >= 1.
    >>> virfib(6)
    8
    """
    prev = 0  # First Fibonacci number
    curr = 1  # Second Fibonacci number
    k = 1
    while k < n:
        (prev, curr) = (curr, prev + curr)
        k += 1
    return curr
Virahanka-Fibonacci generator
 
def generate_virfib():
    """Generate the next Virahanka-Fibonacci number.
    >>> g = generate_virfib()
    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    1
    >>> next(g)
    2
    >>> next(g)
    3
    """
Virahanka-Fibonacci generator (solution)
 
def generate_virfib():
    """Generate the next Virahanka-Fibonacci number.
    >>> g = generate_virfib()
    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    1
    >>> next(g)
    2
    >>> next(g)
    3
    """
    prev = 0  # First Fibonacci number
    curr = 1  # Second Fibonacci number
    while True:
        yield prev
        (prev, curr) = (curr, prev + curr)
 
 
Yield from
 
Yielding from iterables
A 
yield from 
statement can be used to yield the values from an
iterable one at a time.
Instead of
We can write …
def a_then_b(a, b):
    for item in a:
        yield item
    for item in b:
        yield item
list(a_then_b(["Apples", "Aardvarks"], ["Bananas", "BEARS"]))
def a_then_b(a, b):
    yield from a
    yield from b
list(a_then_b(["Apples", "Aardvarks"], ["Bananas", "BEARS"]))
Yielding from generators
A 
yield from
 can also yield the results of another generator function
(which could be itself).
def countdown(k):
    if k > 0:
        yield k
        yield from countdown(k - 1)
Visualizing countdown()
 
Calls
Executed Code
Bindings
Yields
>>> c = countdown(3)
def countdown(k)
k = 3
 
>>> next(c)
 
if k > 0:
 
yield k
 
3
 
>>> next(c)
 
yield from countdown(k
 
-
 
1)
 
Def countdown(k):
 
if k > 0:
 
yield k
 
k = 2
 
2
 
>>> next(c)
 
yield from countdown(k
 
-
 
1)
 
Def countdown(k):
 
if k > 0:
 
yield k
 
k = 1
 
1
 
>>> next(c)
 
yield from countdown(k
 
-
 
1)
 
Def countdown(k):
 
if k > 0:
 
yield k
 
yield from countdown(k
 
-
 
1)
 
k = 0
 
StopIteration
 
 
Generator functions with returns
 
Generator function with a return
When a generator function executes a return statement, it exits and
cannot yield more values.
def f(x):
    yield x
    yield x + 1
    return
    yield x + 
3
list(f(2))
# [2, 3]
Generator functions with return values
 
Python allows you to specify a value to be returned, but this value is
not yielded.
 
 
 
 
 
It is possible to access that return value, with this one weird trick. But
you won't ever need this in CS 111!
def g(x):
    yield x
    yield x + 1
    return x + 2
    yield x + 
3
list(g(2))
# [2, 3]
def h(x):
    y = yield from g(x)
    yield y
list(h(2))
# [2, 3, 4]
 
 
Slide Note
Embed
Share

Explore the concept of iterators and iterable objects in Python. Learn how to use iterators to access elements one by one in an iterable object. Discover the sequential access provided by iterators and how to create iterators for various iterable objects like lists, tuples, dictionaries, strings, and ranges.

  • Python
  • Iterators
  • Generators
  • Iterable Objects

Uploaded on Sep 19, 2024 | 0 Views


Download Presentation

Please find below an Image/Link to download the presentation.

The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author. Download presentation by click this link. If you encounter any issues during the download, it is possible that the publisher has removed the file from their server.

E N D

Presentation Transcript


  1. Iterators & Generators

  2. Iterators

  3. Iterators and Iterable objects In the last few lectures, while talking about for loops and list comprehensions, we mentioned the concept of an iterator and that an object can be iterable. Today, we'll talk about how to use Python's iterators. Later in the course we'll talk about how to make our objects iterable. In Python, an object is iterable if it supports the capability to be processed one element at a time. Lists and strings are iterable numbers are not We use iterators to access the individual elements of an iterable object, one at a time and in order

  4. Iterators An iterator is an object that provides sequential access to values, one by one. returns an iterator over the elements of an iterable. iter(iterable) returns the next element in an iterator. next(iterator) toppings = ["pineapple", "pepper", "mushroom", "roasted red pepper"] topperator = iter(toppings) next(topperator) next(topperator) next(topperator) next(topperator) next(topperator) # 'pineapple' # 'pepper' # 'mushroom' # 'roasted red pepper' # StopIteration exception

  5. A useful detail Calling iter() on an iterator just returns the iterator: numbers = [" ", " ", " "] num_iter = iter(numbers) num_iter2 = iter(num_iter) num_iter is num_iter2 # True

  6. For loop with iterator When used in a for loop, Python will call next() on the iterator in each iteration: nums = range(1, 4) num_iter = iter(nums) for num in num_iter: print(num)

  7. Iterables Lists, tuples, dictionaries, strings, and ranges are all iterable objects. my_order = ["Yuca Shepherds Pie", "P o de queijo", "Guaran "] best_topping = "pineapple" scores = range(1, 21) prices = {"pineapple": 9.99, "pen": 2.99, "pineapple-pen": 19.99}

  8. Making iterators for iterables iter() can return an iterator for any iterable object. my_order = ["Yuca Shepherds Pie", "P o de queijo", "Guaran "] order_iter = iter(my_order) next(order_iter) # "Yuca Shepherds Pie" best_topping = "pineapple" topping_iter = iter(best_topping) next(topping_iter) # "p" scores = range(1, 21) score_iter = iter(scores) next(score_iter) # 1

  9. Making iterators for dictionaries In Python 3.6+, items in a dict are ordered according to when they were added. prices = {"pineapple": 9.99, "pen": 2.99, "pineapple-pen": 19.99} An iterator for the keys: price_iter = iter(prices.keys()) next(price_iter) # "pineapple" An iterator for the values: price_iter = iter(prices.values()) next(price_iter) # 9.99 An iterator for key/value tuples: price_iter = iter(prices.items()) next(price_iter) # ("pineapple", 9.99)

  10. For loops with used-up iterators nums = range(1, 4) num_iter = iter(nums) first = next(num_iter) for num in num_iter: print(num) Iterators are mutable! Once the iterator moves forward, it won't return the values that came before. nums = range(1, 4) sum = 0 num_iter = iter(nums) for num in num_iter: print(num) for num in num_iter: sum += num print(sum)

  11. Iterating over iterables If you want all the items from start to finish, it's better to use a for-in loop. my_order = ["Yuca Shepherds Pie", "P o de queijo", "Guaran "] for item in my_order: print(item) lowered = [item.lower() for item in my_order] ranked_chocolates = ("Dark", "Milk", "White") for chocolate in ranked_chocolates: print(chocolate) best_topping = "pineapple" for letter in best_topping: print(letter)

  12. Reasons for using iterators Code that processes an iterator using iter() or next() makes few assumptions about the data itself. Changing the data storage from a list to a tuple, map, or dict doesn't require rewriting code. Others are more likely to be able to use your code on their data. An iterator bundles together a sequence and a position within the sequence in a single object. Passing that iterator to another function always retains its position. Ensures that each element of the sequence is only processed once. Limits the operations that can be performed to only calling next().

  13. Useful built-in functions

  14. Functions that return iterables Function Description Returns a list containing all items in iterable list(iterable) Returns a tuple containing all items in iterable tuple(iterable) Returns a sorted list containing all items in iterable sorted(iterable)

  15. Functions that return iterators Function Description Iterate over item in sequence in reverse order (See example in PythonTutor) reversed(sequence) Iterate over co-indexed tuples with elements from each of the iterables (See example in PythonTutor) zip(*iterables) Iterate over func(x) for x in iterable Same as [func(x) for x in iterable] (See example in PythonTutor) map(func, iterable, ...) Iterate over x in iterable if func(x) Same as [x for x in iterable if func(x)] (See example in PythonTutor) filter(func, iterable)

  16. Generators

  17. Generators A generator function uses yield instead of return: def evens(): num = 0 while num < 10: yield num num += 2 A generator is a type of iterator that yields results from a generator function. Just call the generator function to get back a generator: evengen = evens() # 0 # 2 # 4 # 6 # 8 # next(evengen) # 0 next(evengen) # 2 next(evengen) # 4 next(evengen) # 6 next(evengen) # 8 next(evengen) # StopIteration exception StopIteration exception

  18. How generators work def evens(): num = 0 while num < 2: yield num num += 2 gen = evens() next(gen) next(gen) View in PythonTutor When the function is called, Python immediately returns an iterator without entering the function. When next()is called on the iterator, it executes the body of the generator from the last stopping point up to the next yield statement. If it finds a yield statement, it pauses on the next statement and returns the value of the yielded expression. If it doesn't reach a yield statement, it stops at the end of the function and raises a StopIteration exception.

  19. Looping over generators We can use for loops on generators, since generators are just special types of iterators. def evens(start, end): num = start + (start % 2) while num < end: yield num num += 2 for num in evens(12, 60): print(num) Looks a lot like: evens = [num for num in range(12, 60) if num % 2 == 0] for num in evens: print(num)

  20. Why use generators? Generators are lazy: they only generate the next item when needed. Why generate the whole sequence... def find_matches(filename, match): matched = [] for line in open(filename): if line.find(match) > -1: matched.append(line) return matched matched_lines = find_matches('frankenstein.txt', "!") matched_lines[0] matched_lines[1] if you only want some elements? def find_matches(filename, match): for line in open(filename): if line.find(match) > -1: yield line line_iter = find_matches('frankenstein.txt', "!") next(line_iter) next(line_iter) A large list can cause your program to run out of memory!

  21. Exercise: Countdown def countdown(n): """ Generate a countdown of numbers from n down to 'blast off!'. >>> c = countdown(3) >>> next(c) 3 >>> next(c) 2 >>> next(c) 1 >>> next(c) 'blast off!' """

  22. Exercise: Countdown (solution) def countdown(n): """ Generate a countdown of numbers from n down to 'blast off!'. >>> c = countdown(3) >>> next(c) 3 >>> next(c) 2 >>> next(c) 1 >>> next(c) 'blast off!' """ while n > 0: yield n n -= 1 yield "blast off!"

  23. Virahanka-Fibonacci generator Let's transform this function... def virfib(n): """Compute the nth Virahanka-Fibonacci number, for n >= 1. >>> virfib(6) 8 """ prev = 0 # First Fibonacci number curr = 1 # Second Fibonacci number k = 1 while k < n: (prev, curr) = (curr, prev + curr) k += 1 return curr into a generator function!

  24. Virahanka-Fibonacci generator def generate_virfib(): """Generate the next Virahanka-Fibonacci number. >>> g = generate_virfib() >>> next(g) 0 >>> next(g) 1 >>> next(g) 1 >>> next(g) 2 >>> next(g) 3 """

  25. Virahanka-Fibonacci generator (solution) def generate_virfib(): """Generate the next Virahanka-Fibonacci number. >>> g = generate_virfib() >>> next(g) 0 >>> next(g) 1 >>> next(g) 1 >>> next(g) 2 >>> next(g) 3 """ prev = 0 # First Fibonacci number curr = 1 # Second Fibonacci number while True: yield prev (prev, curr) = (curr, prev + curr)

  26. Yield from

  27. Yielding from iterables A yield from statement can be used to yield the values from an iterable one at a time. Instead of def a_then_b(a, b): for item in a: yield item for item in b: yield item list(a_then_b(["Apples", "Aardvarks"], ["Bananas", "BEARS"])) We can write def a_then_b(a, b): yield from a yield from b list(a_then_b(["Apples", "Aardvarks"], ["Bananas", "BEARS"]))

  28. Yielding from generators A yield from can also yield the results of another generator function (which could be itself). def countdown(k): if k > 0: yield k yield from countdown(k - 1)

  29. Visualizing countdown() Calls Executed Code Bindings Yields >>> c = countdown(3) def countdown(k) k = 3 >>> next(c) if k > 0: yield k 3 >>> next(c) yield from countdown(k-1) Def countdown(k): if k > 0: yield k k = 2 2 >>> next(c) yield from countdown(k-1) Def countdown(k): if k > 0: yield k k = 1 1 >>> next(c) yield from countdown(k-1) Def countdown(k): if k > 0: yield k yield from countdown(k-1) k = 0 StopIteration

  30. Generator functions with returns

  31. Generator function with a return When a generator function executes a return statement, it exits and cannot yield more values. def f(x): yield x yield x + 1 return yield x + 3 # [2, 3] list(f(2))

  32. Generator functions with return values Python allows you to specify a value to be returned, but this value is not yielded. def g(x): yield x yield x + 1 return x + 2 yield x + 3 # [2, 3] list(g(2)) It is possible to access that return value, with this one weird trick. But you won't ever need this in CS 111! def h(x): y = yield from g(x) yield y # [2, 3, 4] list(h(2))

More Related Content

giItT1WQy@!-/#giItT1WQy@!-/#giItT1WQy@!-/#giItT1WQy@!-/#giItT1WQy@!-/#giItT1WQy@!-/#giItT1WQy@!-/#