Storing elements in a sequence in Python (Part 1)

Entering and displaying lists

The type of sequence we'll consider in Python is called a list. Entering lists is the same as in pseudocode, and to make life easier, the ways lists are entered and displayed are the same.

How lists are entered

Square brackets around the list, items separated by commas.

[5, 2, 3, 6]

How lists are displayed

Square brackets around the list, items separated by commas.

[5, 2, 3, 6]

If no items are entered between the brackets, the list is empty. Remember that the empty string is one of the few values that has the Boolean value "False"? The empty list is another one.

Caution

The empty list has length zero and Boolean value False.

Python makes lists very easy to use, as there is a lot of flexibility in changing them and in the values stored.

Caution

Python lists are more powerful than most arrays:

  • Length can change.
  • Elements can be values of different types.

When you learn another language and use an array, don't be surprised if the options are more limited.

We aren't going to consider Python arrays in this course, but it is worth your knowing that they exist so that you can keep the terms straight.

Caution

Python arrays are not the same as Python lists.


Common list functions

The fact that both empty strings and empty lists have the Boolean value false is more than a coincidence. Python uses many of the same built-in functions for both.

first_list = [1, 2, 3]
second_list = ["a", "b", "c"]

print(len(first_list))
print(first_list[-1])

You can probably guess that len(first_list) will produce the length of first_list, and that first_list[-1] will produce the last item in first_list. This code block gives the following output:


3
3
first_list = [1, 2, 3]
second_list = ["a", "b", "c"]

print(first_list + second_list)
print(2 * second_list)

Similarly, we can concatenate lists, and also repeatedly concatenate a list. This code block gives the following output:


[1, 2, 3, 'a', 'b', 'c']
['a', 'b', 'c', 'a', 'b', 'c']
first_list = [1, 2, 3]
second_list = ["a", "b", "c"]

print(first_list[1:2])

The slice function works just as in a string. This code block gives the following output:


[2]

The functions, pseudocode, and Python are all summarized here. I've also listed one of the comparison operations to indicate that all of them can work for lists. By “can work” I mean that they do work under certain circumstances.

Function Pseudocode Python
string length length([seq]) len(seq)
index [5, 2, 7][0] [5, 2, 7][0]
concatenation [1, 2] + ["a"] [1, 2] + ["a"]
repeated Not applicable [5, 2, 7] * 2
slice Not applicable [5, 2, 7][1:2]
comparison Not applicable [1, 2] < [2, 3]

Tricky parts about using sequence functions

This is one of several examples of ways in which lists behave differently from strings.

first_list = [1, 2, 3]
second_list = ["a", "b", "c"]

print(first_list < second_list)

When we try to compare the two lists, we obtain an error message:


Traceback (most recent call last):
  File "<string>", line 4, in <module>
TypeError: unorderable types: int() < str()

The reason is that even though there is an order among numbers and an order among strings, there is no ordering that allows us to compare numbers and strings.

first_list = [1, 2, 3]
second_list = ["a", "b", "c"]

print(first_list < [1, 2, 4])
print([2, "a"] < [2, "b"])

These comparisons are fine, since in each case the items being compared can be ordered. The mixture of numbers and strings is fine as long as numbers are compared to numbers and strings are compared to strings. This code block gives the following output:


True
True
print([1, 2] + 3)

You might be tempted to use concatenation to add an element to the end of a list, but you'll get an error message, as both values being concatenated must be lists:


Traceback (most recent call last):
  File "<string>", line 6, in <module>
TypeError: unorderable types: int() < str()
print([1, 2] + [3])

We can fix this by making 3 into a list of length one. This gives the following output:


[1, 2, 3]
print(first_list[1])
print(first_list[1:2])
2
[2]

Here's another possible point of confusion. See how using an index returns an item whereas a slice returns a list, in this case, a list of length one. A number and a list are not the same type of data.


first_list = [1, 2, 3]
second_list = ["a", "b", "c"]

first_list[1] = 4
print(first_list)

This type of assignment statement would result in an error for a string, but since lists are mutable, we can change a single item. Here's the updated list:


[1, 4, 3]

Summary

Be careful about the ordering of items when using comparisons; comparisons only work when list elements are ordered (e.g., compare numbers to numbers, strings to strings).

Be careful about the difference between lists and elements when using concatenation; make sure that + joins two lists, not a list and an element.

Be careful using indices and slices.

Caution

Using an index will return an item.

Using slice will return a list.

And always keep in mind that lists are mutable.

Caution

Lists are mutable.

my_list[i] can appear on the left side of an assignment.


Using is

Now, at last, it is time to look at the comparison is, which I had said might cause confusion. Let's start with something simple.

a = "elephant"
b = a
print(a is b)

If we create a string and then give a new name, the names refer to the same address. Thus, we get the following output:


True
a = "elephant"
c = "elephant"
print(a == c)
print(a is c)

According to our visualization, if we happen to give two names the same value, there should be two copies of the value in two different addresses. But Python saves space by reusing the same address for small numbers and strings, leading to an unexpected result:


True
True

Since numbers and strings are immutable, it doesn't really matter.

a = "elephant"
b = a
a = 10
print(a is b)

In particular, when a is used for a new value, now a and b no longer share an address. Thus, we get the following output:


False
a = [1, 2, 3]
b = a
print(a is b)

What happens if we have lists? Again, if we assign a to b, the two names share an address. We get the following output:


True
a = [1, 2, 3]
c = [1, 2, 3]
print(a == c)
print(a is c)

However, when we create two identical lists, although they are equal, they are not stored at the same address, giving the following output:


True
False

This is important due to mutation.

a = [1, 2, 3]
b = a
a[1] = "cat"
print(b)
print(a is b)

When a and b share an address, any change to a results in a change to b as well, as the address stays the same for both. This gives the following output:


[1, 'cat', 3]
True

The surprising behaviour we saw for strings was due to space-saving by using interning. Hopefully the behaviour for lists was not surprising, as it is simply a consequence of mutation.