Most strikingly generators and iterators seem to run rampant in this example

Most strikingly generators and iterators seem to run

This preview shows page 112 - 114 out of 134 pages.

Most strikingly, generators and iterators seem to run rampant in this example. Thearguments passed to minand maxare generator expressions, which run to completionbefore the nested comprehensions begin iterating. Moreover, the nested list compre-hensions employ two levels of delayed evaluation—the Python 3.0 rangebuilt-in is aniterable, as is the generator expression argument to tuple.In fact, no results are produced here until the square brackets of the list comprehensionsrequest values to place in the result list—they force the comprehensions and generatorsto run. To turn these functions themselves into generators instead of list builders, useparentheses instead of square brackets again. Here’s the case for our zip:# Using generators: (...)def myzip(*seqs):minlen = min(len(S) for S in seqs)506|Chapter 20:Iterations and Comprehensions, Part 2Copyright © ${Date}. ${Publisher}. All rights reserved.
Background image
return (tuple(S[i] for S in seqs) for i in range(minlen))print(list(myzip(S1, S2)))In this case, it takes a listcall to activate the generators and iterators to produce theirresults. Experiment with these on your own for more details. Developing further codingalternatives is left as a suggested exercise (see also the sidebar “Why You Will Care:One-Shot Iterations” for investigation of one such option).Why You Will Care: One-Shot IterationsIn Chapter 14, we saw how some built-ins (like map) support only a single traversal andare empty after it occurs, and I promised to show you an example of how that canbecome subtle but important in practice. Now that we’ve studied a few more iterationtopics, I can make good on this promise. Consider the following clever alternative cod-ing for this chapter’s zipemulation examples, adapted from one in Python’s manuals:def myzip(*args):iters = map(iter, args)while iters:res = [next(i) for i in iters]yield tuple(res)Because this code uses iterand next, it works on any type of iterable. Note that thereis no reason to catch the StopIterationraised by the next(it)inside the comprehensionhere when any one of the arguments’ iterators is exhausted—allowing it to pass endsthis generator function and has the same effect that a returnstatement would. Thewhile iters:suffices to loop if at least one argument is passed, and avoids an infiniteloop otherwise (the list comprehension would always return an empty list).This code works fine in Python 2.6 as is:>>> list(myzip('abc', 'lmnop'))[('a', 'l'), ('b', 'm'), ('c', 'n')]But it falls into an infinite loop and fails in Python 3.0, because the 3.0 mapreturns aone-shot iterable object instead of a list as in 2.6. In 3.0, as soon as we’ve run the listcomprehension inside the loop once, iterswill be empty (and reswill be []) forever.To make this work in 3.0, we need to use the listbuilt-in function to create an objectthat can support multiple iterations:def myzip(*args):iters = list(map(iter, args))...rest as is...
Background image
Image of page 114

You've reached the end of your free preview.

Want to read all 134 pages?

  • Left Quote Icon

    Student Picture

  • Left Quote Icon

    Student Picture

  • Left Quote Icon

    Student Picture