Lists¶
Lists provide a way to represent ordered sequences of data. They are an essential part of programming in Python and you will use them repeatedly in your work. The items in a list, also known as the elements of the list, do not need to have the same type (i.e., a single list can contain strings, floats, ints, and even other lists).
Literals
List literals are written using square brackets with the individual
elements separated by commas. The empty list is written as []
.
Here are some examples taken from an ipython3
session:
In [1]: l0 = []
In [2]: l1 = [1, "abc", 5.7, [1, 3, 5]]
The variable l0
refers to an empty list. The variable l1
refers to a list with four elements: an integer, a string, a floating
point number, and a list.
Length
You can find the length of a list using the built-in len
function.
The expression len(l0)
, for example, will evaluate to 0.
Evaluating the expression len(l1)
will yield the value 4.
Indexing
When used in an expression, the indexing operation ([]
) is used to
retrieve the value of the i-th element of a list. That is, given a
list l
, the expression l[i]
will evaluate to the value of the
i-th element of the list. Here are some example uses of the indexing
operation:
In [3]: l1[0]
Out[3]: 1
In [4]: l1[1]
Out[4]: 'abc'
In [5]: l1[3]
Out[5]: [1, 3, 5]
Notice that we use zero to get the first element of the list and that
the last element in a list, l
, is at index len(l)-1
. All
indexing is zero-based in Python. In other words, in a list with five
elements, the first element is at index 0, while the last element is at
index 4. This characteristic of Python is important to note because some
other programming languages,
primarily older languages like Fortran, Matlab, and Smalltalk, use 1 to
index the first element.
As you learn other programming languages, you will find that a fair bit of
your knowledge transfers over, but you must be cognizant of these sorts
of distinctions.
Lists are mutable. That is, we can change them. To change the value
at index i
in list l
, you simply put l[i]
on the left-hand
side of an assignment statement. For example:
In [6]: l1[2] = "xyz"
In [7]: l1
Out[7]: [1, 'abc', 'xyz', [1, 3, 5]]
Notice that the statement l1[2] = "xyz"
changes the value at index 2
from 5.7
to be the string "xyz"
.
Debugging hint: Forgetting about zero-based indexing is the source of two kinds of problems in Python: (1) retrieving the second element when you want the first and (2) indexing beyond the end of the list in an attempt to get the last item in the list. The first of these problems can be difficult to track down, because your program will not fail at the point of the error. In fact, it may not fail at all! The second problem will cause your program to fail, which can be frustrating, but at least you know there is a problem!
While these examples all use integer literals (0, 1, etc.), any expression that yields an integer can be used to describe the index for a list. The expression is evaluated to yield an integer that is then used to index into the list. For example:
In [8]: i = 0
In [9]: l1[(i+1)*2]
Out[9]: "xyz"
In [10]: l2 = [1, 0, 3, 2]
In [11]: l1[l2[3]]
Out[11]: "xyz"
In [12]: l1[l2[3]] = 5.7
In [13]: l1
Out [13]: [1, 'abc', 5.7, [1, 3, 5]]
The first example is straightforward. The second is a bit more
complex: the expression l2[3]
yields the value 2
, which is
then used to index into the list l1
, which yields the string
"xyz"
. The third statement changes the value of l1
at index 2
back to 5.7
.
The result of indexing into a list could yield a value that itself may be a list, which can be indexed into like any other list:
In [14]: l1[3][0]
Out[14]: 1
Slicing
In addition to getting a single value from a list, it can be useful to
extract a copy of a sub-list from a list. The slicing operation
(:
) is used for this purpose. The expression l1[1:3]
, for
example, yields a new list with the value ['abc', 5.7]
. Notice
that the slice yields the elements from indices up to, but not
including, the upper bound. To make a copy of a list l
, of length N
, you
can use the expression l[0:N]
.
The expression on either side of the colon (e1 : e2) or even both expressions can be left out. The default value for the first expression (e1) is 0. The default value for the second expression is the length of the list. Here are some example uses of slicing:
In [15]: l1[1:3]
Out[15]: ['abc', 5.7]
In [16]: l1[:3]
Out[16]: [1, 'abc', 5.7]
In [17]: l1[1:]
Out[17]: ['abc', 5.7, [1, 3, 5]]
In [18]: l1[:]
Out[18]: [1, 'abc', 5.7, [1, 3, 5]]
Useful operations on lists
The equality operator (==
) signifies value equality in Python.
For lists, that means that two lists are deemed to be equal if they
hold the same values in the same order. For example, evaluating the
expression [1, 2, 3] == [1, 2, 3]
will yield the value True
,
while evaluating the expression [1, 2, 3] == []
will yield the
value False
.
The append
method is used to add a new element to the end of an
existing list. It is important to note that this operation changes
the value of the list. For example, after executing the statement
l1.append(27)
, l1
will have the value [1, 'abc', 5.7, [1, 3,
5], 27]
. You’ll notice that this statement uses somewhat unusual
syntax: rather than looking like a function call, the name of the
method (preceded by a dot) comes after the list! We will explain
this notation in detail later in the term. For now, just remember
that appending elements to a list changes the list and uses a somewhat
odd notation.
The concatenation operation (+
) creates a new list from two
existing lists. For example:
In [19]: l3 = [1, 2, 3]
In [20]: l4 = ['a', 'b', 'c']
In [21]: l5 = l3 + l4
In [22]: l5
Out[22]: [1, 2, 3, 'a', 'b', 'c']
In [23]: l5[2] = 7
In [24]: l5
Out[24]: [1, 2, 7, 'a', 'b', 'c']
In [25]: l3
Out[25]: [1, 2, 3]
Notice that the update to l5
does not change the value of l3
,
because l5
is a new list that has copy of the values from
l3
followed by a copy of the values from l4
.
At times, you will want to create a list of a specific size with some
initial value. You could do this task with a loop, but the
concatenation mechanism provides a useful shorthand. Given a list
l
and a value n
, the expression l*n
concatenates n
copies of the list l
into a new list. (If this notation seems
odd, recall that multiplication is just repeated addition.) For
example, the expression [0]*10
creates a list of length 10 with of
all zeros and the expression [False]*len(l7)
creates a list that
has the same length as l7
and in which every element has the value
False
. Notice that in this second example, the number of copies
is determined by evaluating the expression len(l7)
.
The list initialization mechanism works well for simple values, such
as integers, floats, and booleans, and for immutable values, such as
strings, but it does not work well for more complex values, such as
lists. The expression [[]]*5
, for example, will yield a list
with five elements, each of which refers to the same empty list!
Here is some sample code to help make this issue more concrete:
In [26]: l = [[]]*5
In [27]: l[0].append("a")
In [28]: l
Out[28]: [['a'], ['a'], ['a'], ['a'], ['a']]
Why does this happen? Evaluating the inner expressions ([]
)
yields a reference to a newly created empty list. It is that reference
that is copied five times when evaluating the full expression
[[]]*5
. As a result, updating one element of the l
updates
them all.