Lists#

Jesse London

Modern computing would be inconceivable without the ability to interact with a collection of multiple values at once, and more specifically the ordered sequence of values. In Python, the most common of these data types is called the list.

Lists are syntactically defined using commas to separate their constituent elements, and enclosed by square brackets.

primes_abridged = [2, 3, 5, 7, 11, 13, 17, 19, 23]

primes_abridged
[2, 3, 5, 7, 11, 13, 17, 19, 23]

Identifying elements#

Lists not only enable us to address a sequence of values at once. They also permit us to refer to their elements:

primes_abridged[0]
2
primes_abridged[5]
13

Above, we retrieved individual elements from our list, by indicating the index of the element to retrieve – after the name assigned to the list, and with the index itself enclosed in square brackets.

Note

We referred to the first element of our list using the index 0, and to the sixth with the index 5. In computer programming, you can think of this reference not as the “number” or the “count” of the element, but rather as an “offset” from the beginning of the list.

Even the index, or offset, -1 is valid, and this is the general way to refer to the last element of the list:

primes_abridged[-1]
23

Slices#

We can also retrieve a subset of the elements of our list, for example using the slice.

primes_really_abridged = primes_abridged[2:5]

primes_really_abridged
[5, 7, 11]

In the syntax of the slice, we indicated that we would like to construct a new list, consisting of the elements at indices 2 through 4, ending before index 5.

That is, slices are constructed from a generic range. This range may be bounded or unbounded. The lower bound, or start, is always inclusive. The upper bound, or end, is always exclusive.

The full signature of the slice includes a start, end and step. All of these arguments are optional, and their defaults are to start with the first element of the list, to end after the last element of the list, and to “step” through the elements one-by-one. As such, the following three expressions evaluate the same.

primes_abridged[0:9:1]
[2, 3, 5, 7, 11, 13, 17, 19, 23]
primes_abridged[:]
[2, 3, 5, 7, 11, 13, 17, 19, 23]
primes_abridged[::]
[2, 3, 5, 7, 11, 13, 17, 19, 23]

We can of course supply some arguments and omit others.

Let’s slice our list starting with its second element – at index 1.

primes_abridged[1:]
[3, 5, 7, 11, 13, 17, 19, 23]

…Or ending before index 3.

primes_abridged[:3]
[2, 3, 5]

We can even specify negative indices.

Let’s start with the last element.

primes_abridged[-1:]
[23]

…Or, construct a list consisting of only the third-to-last and second-to-last elements.

primes_abridged[-3:-1]
[17, 19]

A slice can also indicate a step other than 1, such that some elements are stepped over, or skipped.

every_other_prime_abridged = primes_abridged[::2]

every_other_prime_abridged
[2, 5, 11, 17, 23]

We can even step through the list backwards.

primes_abridged[-1::-1]
[23, 19, 17, 13, 11, 7, 5, 3, 2]

Above, we started with the index of the last element, and told the slice to decrement this index by one for each subsequent element. (And, given this negative step, the list knew by default that the last element should be the first.)

Aggregation#

Most important, lists enable us to instruct the computer to apply an operation to each element of the list in sequence.

And, most simply, we can count the number of elements in our list.

len(primes_abridged)
9

Better yet, we can sum the elements.

sum(primes_abridged)
100

Combining the two of these, we can compute an average or mean.

sum(primes_abridged) / len(primes_abridged)
11.11111111111111

The elements of multiple lists may be combined – or concatenated – forming a new list, with the addition operator.

primes_abridged + [29, 31, 37, 41]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]

And a list may even be trivially combined with itself – with the multiplication operator!

3 * primes_really_abridged
[5, 7, 11, 5, 7, 11, 5, 7, 11]

Mutation & Methods#

A list may be mutated – that is, manipulated without creating a new list – thanks in part to list methods.

Note

Mutation is is also known as manipulation “in place.”

Making changes to a list of data, without creating a new list and assigning a new name, is less straight-forward, and may be prone to error.

However, mutation can also be integral to the process of constructing or building a list; (and, such methods can be useful when computing performance becomes important).

We might extend our list primes_really_abridged with the append method.

primes_really_abridged.append(13)

…What happened? The above expression had no output – (literally, it evaluated to None, which is hidden by default).

Many of Python’s built-in mutational methods have no output, because none (or None!) is necessary.

Indeed, append had the desired effect. The prime 13 has been added to the end of our list.

primes_really_abridged
[5, 7, 11, 13]

Let’s continue to make our list a little less abridged with the insert method.

primes_really_abridged.insert(0, 3)
primes_really_abridged
[3, 5, 7, 11, 13]

Again, our mutation expression had no output.

But this time, we’ve added an element to the beginning of our list, by inserting it before the element that was at index 0.

Let’s add some more.

primes_really_abridged.append(19)
primes_really_abridged.append(23)
primes_really_abridged
[3, 5, 7, 11, 13, 19, 23]

Whoops – we forgot the prime 17.

Luckily, elements may be inserted into any position of a list.

But where do we need to insert 17? Let’s ask the list which index is 19 – appropriately, with the method index.

primes_really_abridged.index(19)
5

Unlike insert and append, index does not mutate the list. It only returns the requested value.

Now we can put 17 in its proper place.

missing_index = primes_really_abridged.index(19)

primes_really_abridged.insert(missing_index, 17)

primes_really_abridged
[3, 5, 7, 11, 13, 17, 19, 23]

Note that the insert method accepts any index.

This includes negative indices.

primes_really_abridged.append(37)

primes_really_abridged
[3, 5, 7, 11, 13, 17, 19, 23, 37]

Huh, we skipped a prime again.

primes_really_abridged.insert(-1, 31)

primes_really_abridged
[3, 5, 7, 11, 13, 17, 19, 23, 31, 37]

Almost there….

primes_really_abridged.insert(-2, 29)

primes_really_abridged
[3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

That’s better.

insert can even get a little silly….

primes_really_abridged.insert(1_000_000, 41)

primes_really_abridged
[3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]

Above, we asked insert to place the element 41 before any element at the index for 1,000,000.

What it did was place this value at the end of the list – that is at index 11.

primes_really_abridged[11]
41

In other words, insert did what we asked it to do.

Also note from the above, list elements must be contiguous – elements must be positioned one after the other, and referred to by a continuous range of indices – and not sparse.

One list method which both mutates the list and returns a value is pop.

The pop method removes an element from the list and returns this value.

This can be useful in moving elements from one list to another, or in otherwise making use of removed elements.

By default, pop removes the last element from the list.

primes_really_abridged.pop()
41
primes_really_abridged
[3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

pop may optionally be given the index of an element to remove.

primes_really_abridged.pop(0)
3
primes_really_abridged
[5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

Because lists are contiguous, and because the index of their first element is always 0, each element removed shifts the indices of the elements that followed it.

As such, when we remove the element at index 3, the element that followed it takes on that index, etc.

primes_really_abridged.pop(3)
primes_really_abridged.pop(3)
primes_really_abridged.pop(3)
primes_really_abridged.pop(3)
primes_really_abridged.pop(3)
primes_really_abridged.pop(3)
primes_really_abridged.pop(3)

primes_really_abridged
[5, 7, 11]

Of course, the above became fairly verbose.

We’ll learn more, generic Python operations for mutating lists, and for concisely manipulating collections of data, in subsequent chapters.

Other lists#

So far we’ve only considered lists of numbers, but in fact list elements may refer to values of any type – and even of multiple types at once.

planets = [
    'Mercury',
    'Venus',
    'Earth',
    'Mars',
    'Jupiter',
    'Saturn',
    'Uranus',
    'Neptune',
]

In the above, we’ve constructed a list of the planets, whose elements are, of course, strings.

Depending on when you attended grade school, you may prefer the following planetary listing:

planets + ['Pluto']
['Mercury',
 'Venus',
 'Earth',
 'Mars',
 'Jupiter',
 'Saturn',
 'Uranus',
 'Neptune',
 'Pluto']

But this needn’t worry the International Astronomical Union! We didn’t use append(), so the planets are safe.

planets
['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

Lists can even contain other lists of data.

# distances from the sun, millions of miles:
planetary_distances = [
    ['Mercury', 36],
    ['Venus', 67.2],
    ['Earth', 93],
    ['Mars', 141.6],
    ['Jupiter', 483.6],
    ['Saturn', 886.7],
    ['Uranus', 1_784.0],
    ['Neptune', 2_794.4],
]

planetary_distances
[['Mercury', 36],
 ['Venus', 67.2],
 ['Earth', 93],
 ['Mars', 141.6],
 ['Jupiter', 483.6],
 ['Saturn', 886.7],
 ['Uranus', 1784.0],
 ['Neptune', 2794.4]]

In the above collection, we’ve combined lists, strings, integers and floats!

This kind of structure, if constructed arbitrarily, can be difficult to use; but, constructed consistently, this is the basis of computational data and data science.

But lists are not the only kind of useful collection available to us in Python, and we’ll explore a few!