Python 3: Sequences and Loops

Contents

3.1 Tuples or lists you can't change

Why have a special case for lists you can't change ? Tuples probably arose more as a syntax convention and later evolved into a design feature.

Python seems to do what you might expect more often than other languages, so in cases where either a tuple or a list seems to make sense, either will often work. Where you have one kind but need the other you can convert between them using the tuple() and list() built in functions.

Definition: A Tuple is a list of values separated by commas, usually surrounded by round brackets.

We've already seen tuples used for interpolating values in strings:

>>> name="Fred"
>>> age=42
>>> height=1.95
>>> print "%s is %d years old and %.2f metres tall" % (name,age,height)
Fred is 42 years old and 1.95 metres tall

The values in the tuple (name,age,height) are interpolated in the string in left to right order. Specifying literal values, or a mixture of references and literals would have had the same effect:

age=42
print "%s is %d years old and %.2f metres tall" % ("Fred",age,1.95)

Tuples help in assigning more than 1 value at a time.

a,b,c=2,1.5,"Fred"

Tuple assignment gives a direct swap. 'C' needs 3 statements and a temp variable:

Python				'C'
a,b=b,a				temp=a;  a=b;  b=temp;

Tuples get copies of the values when assigned

a,b,c=2,1.5,"fred"
d=(a,b,c) # tuple d gets the literal values, not the references a,b,c
print d
b=2.5 # b now refers to a different number but d is unchanged
print d

Even though b was changed, d is an immutable object so is not changed. The initial values assigned to tuple d are output both times:

(2, 1.5, 'fred')
(2, 1.5, 'fred')

3.2 Lists you can change

Strings and tuples are unchangeable, or immutable. Lists in Python have the useful properties of arrays in 'C' but are much more flexible.

Python lists can be formed and indexed using [] square brackets.

a=[0,1,4,9,16,25,36]

Indexes count from 0 as in 'C', so to get the 4th object in a we use a[3].

>>> print a[3]
9

Python lists can contain more than one type of object.

>>> details=["Sukhwinder",24,1.95,"0121 345 9876"]
>>> a[3]="nine"
>>> a
[0, 1, 4, 'nine', 16, 25, 36]

You can make Python lists shrink and grow, e.g. using del and append:

>>> del a[3]
>>> a
[0, 1, 4, 16, 25, 36]
>>> a.append(49)
>>> a
[0, 1, 4, 16, 25, 36, 49]

If you add 2 lists you get one long list, as happens with strings

>>> b=[64,81]
>>> print a+b
[0, 1, 4, 16, 25, 36, 49, 64, 81]

3.3 Strings as immutable lists of characters

You can make a string reference refer to a different string, but this doesn't change the original string which gets garbage collected.

>>> s="hello world!"
>>> s
'hello world!'
>>> s="good day!"
>>> s
'good day!'

We can read (but not write) individual string characters using [] brackets.

>>> s[5]
'd'
>>> s[5]='p'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment

The same rule applies to tuple items which can be read but not written using [] bracketed indices. If we copy a letter from a string this gives us a one-letter string.

3.4 List and string slices

The string slices we experimented with in the week 1 exercises also apply to getting sub-lists (slices) out of lists, just as we used them to extract substrings. e.g:

>>> a
[0, 1, 4, 9, 16, 25, 36]
>>> a[1:3]
[1, 4]
>>> a[1:4]
[1, 4, 9]

The slicing operator consists of 2 colon separated list indexes in [] brackets. This gives us a new list starting with the index of the first number, up to but not including the index represented by the second number. Why get elements up to one less than the second number ? This seems odd until we consider that the first index subtracted from the second gives the number of elements in the slice.

Slice replacement operations

Strings are immutable, so you'll have to try this with mutable list objects.

>>> a=[0, 1, 4, 9, 16, 25, 36]
>>> a[2:4]=["four","nine"]
>>> a
[0, 1, 'four', 'nine', 16, 25, 36]

In this example the slice a[2:4] was replaced by the list ["four","nine"].

>>> a=[0, 1, 4, 9, 16, 25, 36]
>>> a[2:4]=a[3:6]
>>> a
[0, 1, 9, 16, 25, 16, 25, 36]

A slice can be replaced by a slice of another list, or even by a slice of the same list.

>>> a
[0, 1, 4, 9, 16, 25, 36]
>>> a[3:3]=[5]
>>> a
[0, 1, 4, 5, 9, 16, 25, 36]

Replacing an empty slice (i.e. with starting and ending indexes the same) inserts the replacement list or slice before the index of the empty slice. You can use this approach to insert an object into an existing array at a predefined position, e.g. to maintain the order in a list of sorted records when a new one is added.

3.5 Generating sequences using range()

Given the things Python can do with lists, it's often useful to be able to generate regular sequences of numbers in the form of a list. The range() built in does this. We will see the value of this when we use range() in combination with a for loop. Here are some examples:

>>> range(1,10)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

If we have got the idea that taking the first slice index from the second gives us the number of items in the slice, this shouldn't surprise us. Just remember to specify the second index as one more than the last number in the range you want.

>>> a=range(1,10)
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Usual rules about assigning a list to an object reference apply.

>>> range(0,20,2)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

We don't have to go up in steps of 1, we can have any size of step.

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The sequence will start at 0 by default. If we only give one number for a range this replaces the end of range value.

>>> range(20,0,-2)
[20, 18, 16, 14, 12, 10, 8, 6, 4, 2]

We can step down through a range if we want, as is sometimes useful. The first and second values specifying the range are inclusive and exclusive as before.

>>> range(-3.5,9.8)
[-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8]

If we give floats these will first be reduced to integers.

3.6 Iteration using for loops

All programming languages need ways of doing similar things many times. This is called iteration.

>>> a=range(4)
>>> for b in a:
...       print b
...
0
1
2
3

You can try out small programs like this very quickly from the Python command line. Here the primary prompt is >>> and Python recognises when you are in an indented part of the program by changing to the secondary prompt ... Pressing enter in response to the ... prompt allows the program to run.

The action of the for statement is to assign the object reference after the for keyword (b in this case) with the next value from the list which follows the in keyword and use this within the indented body of the loop. This is repeated until all of the list values are used up.

We could have the list returned by range(4) directly of course, or any list for that matter. Having a comma after printing a prints a trailing space but not a newline:

>>> for a in range(21,-1,-2):
...       print a,
...
21 19 17 15 13 11 9 7 5 3 1

For this to be useful, we are likely to want to carry out some calculation or action upon the list member being iterated within the loop body e.g:

>>> for n in range(1,13):
...       print "7 x %d = %d" % (n,n*7)
...
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
7 x 10 = 70
7 x 11 = 77
7 x 12 = 84

Just as with if, elif and else statements, the block of statements after the colon character (:) controlled by the for or while statement must be indented by the same amount, e.g. 4 spaces. Code after your block which reverts to the indentation of the for statement will be executed when your loop completes. If you don't understand this, try saving and running the following program in a script, exactly as written below, avoiding mixing spaces and tabs and write down in your logbook how many times each print statement occurs. Then try indenting this program differently and see how this changes the order by which statements are printed:

for N in range(1,5):
    print "time %d through loop" % N
    print "still in loop body"
print "I only get printed at end of loop"

3.7 Iteration using while loops

Where there is a predefined list of objects, or known range of values to use one item at a time, the for loop is probably the best choice. The while loop is preferred if you don't know how many times you need to execute the body of the loop when it starts.

The following program calculates a total and an average:

total=0.0 # accumulated total
input=1 # starts true
values=0 # number of values input by user
print "enter numbers each followed by <enter> and 0 to stop "
while input :
	input=float(raw_input(": "))
	total+=input # adds input to total
	if input: values+=1 # adds 1 if input is not zero
print "total: %.2f" % total
if values : # avoids possible divide by zero
	print "average: %.2f" % (total/values)
The above program was run with the following inputs and outputs:

enter numbers each followed by <enter> and 0 to stop
: 2
: 3
: 0
total: 5.00
average: 2.50

You can get the same effect as the for loop using a while loop if you prefer, e.g:

>>> a=21
>>> while a > -1:
...       print a,
...       a=a-2
...
21 19 17 15 13 11 9 7 5 3 1

This had identical results to the program:

>>> for a in range(21,-1,-2):
...       print a,

Notice the correspondence between the initial assignment on a (a=21), the while test ( a > -1) and the decrement ( a=a-2) at the end of the loop and the range parameters in the corresponding for loop (21,-1,-2). If the loop counts upwards instead of downwards, the while test must be while a < (less than) something, instead of while a > (greater than) something.

3.8 using a loop to build a list

So far our lists have been assigned like this:

a=["Fred","Bill","Jaz"]

Or created by reading text from a file as a list of records like this:

inp = open('scratch','r')
list_of_lines=inp.readlines()
print list_of_lines

We often need to create lists within a program when a new data item is available on each pass through a loop. The following program prompts the user to create a shopping list, one item at a time:

shoplist=[]  # empty shopping list
item="dummy"
while item != "*":
    item=raw_input("enter list item or * to end: ")
    shoplist.append(item) # adds item to end of shoplist
del shoplist[len(shoplist)-1] # gets rid of the * on the end

slfile=open("shop_list.txt","w")
for item in shoplist:
    slfile.write(item+"\n") # writes item and a \n at end of line
print "list saved to shop_list.txt file"

3.9 breaking out of a loop early

Sometimes it is more convenient to finish a loop in the middle or at the end of the loop body, rather than at the top. The break keyword allows you to exit from the innermost enclosing while or for loop. The following program is similar to the do - while type of loop in 'C'. It executes 1 or more times and the test deciding the exit condition occurs at the bottom, rather than at the top of the loop body. The test while 1: is always true, so without a break statement within the body of such a loop (or stopping the entire program) the loop goes on forever.

while 1:
    number=int(raw_input("enter a number between 1 and 10: "))
    if not 1 <= number <= 10:
      print "out of range, try again"
    else:
      break
print "you entered %d" % number

This loop continues until int() or raw_input() raises an exception, or the user enters a number between 1 and 10 inclusive.