This chapter is taken from the book A Primer on Scientific Programming with Python by H. P. Langtangen, 5th edition, Springer, 2016.
We have already solved the problem of printing out a nice-looking conversion table for Celsius and Fahrenheit degrees. Nevertheless, there are usually many alternative ways to write a program that solves a specific problem. The next paragraphs explore some other possible Python constructs and programs to store numbers in lists and print out tables. The various code snippets are collected in the program file session.py.
Any for
loop can be implemented as a while
loop.
The general code
for element in somelist:
<process element>
can be transformed to this while
loop:
index = 0
while index < len(somelist):
element = somelist[index]
<process element>
index += 1
In particular, the example involving the printout of a table of
Celsius and Fahrenheit degrees can be implemented as follows in terms
of a while
loop:
Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40]
index = 0
print ' C F'
while index < len(Cdegrees):
C = Cdegrees[index]
F = (9.0/5)*C + 32
print '%5d %5.1f' % (C, F)
index += 1
It is tedious to write the many elements in the Cdegrees
in the
previous programs. We should use a loop to automate the construction of
the Cdegrees
list. The range
construction is particularly
useful in this regard:
range(n)
generates integers 0, 1, 2, ..., n-1
.range(start, stop, step)
generates a sequence if integers start
, start+step
, start+2*step
, and so on up to, but not including, stop
. For example, range(2, 8, 3)
returns 2 and 5 (and not 8), while range(1, 11, 2)
returns 1, 3, 5, 7, 9.range(start, stop)
is the same as range(start, stop, 1)
.for
loop over integers are written as
for i in range(start, stop, step):
...
We can use this construction to create a Cdegrees
list of
the values \( -20, -15, \ldots, 40 \):
Cdegrees = []
for C in range(-20, 45, 5):
Cdegrees.append(C)
# or just
Cdegrees = range(-20, 45, 5)
Note that the upper limit must be greater than 40
to ensure that
40
is included in the range of integers.
Suppose we want to create Cdegrees
as \( -10, -7.5, -5, \ldots, 40 \).
This time we cannot use range
directly, because range
can only
create integers and we have decimal degrees such as \( -7.5 \) and \( 1.5 \).
In this more general case, we introduce an integer counter \( i \) and
generate the \( C \) values by the formula \( C=-10 + i\cdot 2.5 \) for
\( i=0,1,\ldots, 20 \). The following Python code implements this task:
Cdegrees = []
for i in range(0, 21):
C = -10 + i*2.5
Cdegrees.append(C)
Instead of iterating over a list directly with the construction
for element in somelist:
...
we can equivalently
iterate of the list indices and index the list inside the loop:
for i in range(len(somelist)):
element = somelist[i]
...
Since len(somelist)
returns the length of somelist
and
the largest legal index is len(somelist)-1
, because indices always
start at 0, range(len(somelist))
will generate all the correct
indices: 0
, 1
, \( \ldots \), len(somelist)-1
.
Programmers coming from other languages, such as Fortran,
C, C++, Java, and C#, are very much used to for
loops with
integer counters and usually
tend to use for i in range(len(somelist))
and work with
somelist[i]
inside the loop. This might be necessary or convenient,
but if possible, Python programmers are encouraged to use
for element in somelist
, which is more elegant to read.
Iterating over loop indices is useful when we need to process two
lists simultaneously. As an example, we first create two Cdegrees
and Fdegrees
lists, and then we make a list to write out a table
with Cdegrees
and Fdegrees
as the two columns of the table.
Iterating over a loop index is convenient in the final list:
Cdegrees = []
n = 21
C_min = -10
C_max = 40
dC = (C_max - C_min)/float(n-1) # increment in C
for i in range(0, n):
C = -10 + i*dC
Cdegrees.append(C)
Fdegrees = []
for C in Cdegrees:
F = (9.0/5)*C + 32
Fdegrees.append(F)
for i in range(len(Cdegrees)):
C = Cdegrees[i]
F = Fdegrees[i]
print '%5.1f %5.1f' % (C, F)
Instead of appending new elements to the lists, we can start with lists
of the right size, containing zeros, and then index the lists to fill
in the right values. Creating a list of length n
consisting of zeros
(for instance) is done by
somelist = [0]*n
With this construction, the program above can use for
loops over
indices everywhere:
n = 21
C_min = -10
C_max = 40
dC = (C_max - C_min)/float(n-1) # increment in C
Cdegrees = [0]*n
for i in range(len(Cdegrees)):
Cdegrees[i] = -10 + i*dC
Fdegrees = [0]*n
for i in range(len(Cdegrees)):
Fdegrees[i] = (9.0/5)*Cdegrees[i] + 32
for i in range(len(Cdegrees)):
print '%5.1f %5.1f' % (Cdegrees[i], Fdegrees[i])
Note that we need the construction [0]*n
to create a list of the
right length, otherwise the index [i]
will be illegal.
We have two seemingly alternative ways to traverse a list, either a
loop over elements or over indices. Suppose we want to change the
Cdegrees
list by adding 5 to all elements. We could try
for c in Cdegrees:
c += 5
but this loop leaves Cdegrees
unchanged, while
for i in range(len(Cdegrees)):
Cdegrees[i] += 5
works as intended. What is wrong with the first loop?
The problem is that c
is an ordinary variable, which
refers to a list element in the loop, but when we execute
c += 5
, we let c
refer to a new float
object (c+5
).
This object is never inserted in the list. The first two
passes of the loop are equivalent to
c = Cdegrees[0] # automatically done in the for statement
c += 5
c = Cdegrees[1] # automatically done in the for statement
c += 5
The variable c
can only be used to read list elements and never
to change them. Only an assignment of the form
Cdegrees[i] = ...
can change a list element.
There is a way of traversing a list where we get both the index and an element in each pass of the loop:
for i, c in enumerate(Cdegrees):
Cdegrees[i] = c + 5
This loop also adds 5 to all elements in the list.
Because running through a list and for each element creating a new element in another list is a frequently encountered task, Python has a special compact syntax for doing this, called list comprehension. The general syntax reads
newlist = [E(e) for e in list]
where
E(e)
represents an expression involving element e
.
Here are three examples:
Cdegrees = [-5 + i*0.5 for i in range(n)]
Fdegrees = [(9.0/5)*C + 32 for C in Cdegrees]
C_plus_5 = [C+5 for C in Cdegrees]
List comprehensions are recognized as a for
loop inside square
brackets and will be frequently exemplified throughout the document.
We may use the Cdegrees
and Fdegrees
lists to
make a table. To this end, we need to traverse both arrays.
The for element in list
construction is not suitable in this
case, since it extracts elements from one list only.
A solution is to use a for
loop over the integer indices so that
we can index both lists:
for i in range(len(Cdegrees)):
print '%5d %5.1f' % (Cdegrees[i], Fdegrees[i])
It happens quite frequently that two or more lists need to be traversed
simultaneously. As an alternative to the loop over indices,
Python offers a special nice syntax that can be sketched as
for e1, e2, e3, ... in zip(list1, list2, list3, ...):
# work with element e1 from list1, element e2 from list2,
# element e3 from list3, etc.
The zip
function turns \( n \) lists
(list1, list2, list3, ...
) into one list
of \( n \)-tuples, where each \( n \)-tuple (e1,e2,e3,...)
has its first element (e1
) from the first list (list1
),
the second element (e2
) from the second list (list2
), and
so forth. The loop stops when the end of the shortest list is reached.
In our specific case of iterating over the two lists
Cdegrees
and Fdegrees
, we can use the zip
function:
for C, F in zip(Cdegrees, Fdegrees):
print '%5d %5.1f' % (C, F)
It is considered more Pythonic to iterate over list elements,
here C
and F
, rather than over list indices as in
the for i in range(len(Cdegrees))
construction.