In this section we will compare different ways to fill lists. They are different in computational efficiency and also in code readability.
A ubiquitous programming pattern is to compute elements and store them in a list:
L = [] for k in range(n): # call various functions here # that compute "result" L.append(result)
This approach has a number of disadvantages:
break
instruction, then the preceding code takes care of both generating values and deciding when to stop. This is not desirable and lacks flexibility.Iterators provide us with an elegant solution to problems discussed previously:
def result_iterator(): for k in itertools.count(): # infinite iterator # call various functions here # that compute "result" ... yield result
With iterators, we separate the task of generating the computed values without bothering about the stopping condition or about the storage. If the user of that code wants to store the n first values, it is easily done using the list
constructor:
L = list(itertools.islice(result_iterator(), n)) # no append needed!
If the user wants the sum of the first n generated values, this construction is recommended:
# make sure that you do not use scipy.sum here s = sum(itertools.islice(result_iterator(), n))
What we did here was separating the generation of elements on one hand, and storing those elements on the other.
If the purpose is really to build a list, and when the result at each step does not depend on previously computed elements, one may use the list comprehension syntax (refer to section List of Chapter 3, Container Types, for more information):
L = [some_function(k) for k in range(n)]
When iteratively computing values that depend on the previously computed values, list comprehensions cannot help.
Using iterators to fill out lists will work nicely most of the time, but there are complications to this pattern when the algorithm computing the new values is liable to throw an exception; if the iterator raises an exception along the way, the list will not be available! The following example illustrates this problem.
Suppose we generate the sequence defined recursively by . This sequence quickly diverges to infinity if the initial data u0 is greater than one. Let us generate it with a generator:
import itertools def power_sequence(u0): u = u0 while True: yield u u = u**2
If you try to obtain the first 20 elements of the sequence (initialized by u0 = 2) by executing,
list(itertools.islice(power_sequence(2.), 20))
an exception will be raised and no list will be available, not even the list of elements before the exception was raised. There is currently no way to obtain a partially filled list from a possibly faulty generator. The only way around is to use the append method wrapped in an exception-catching block (refer to section Exceptions in Chapter 10, Error Handling, for more details):
generator = power_sequence(2.) L = [] for iteration in range(20): try: L.append(next(generator)) except Exception: ...
3.144.19.243