Key terms in this chapter

Chapter 6 – Lists

Source: Automate the Boring Stuff with Python, 3rd Edition — Al Sweigart (No Starch Press). This Markdown version was derived from the chapter PDF.

About this chapter

One more topic you’ll need to understand before you can begin writing programs in earnest is the list data type and its cousin, the tuple. Lists and tuples can contain multiple values, which makes writing programs that handle large amounts of data easier. And since lists themselves can contain other lists, you can use them to arrange data into hierarchical structures.

In this chapter, I’ll discuss the basics of lists. I’ll also teach you about methods, which are functions that are tied to values of a certain data type. Then, I’ll briefly cover the sequence data types (lists, tuples, and strings) and show their differences. In the next chapter, I’ll introduce you to the dictionary data type.

The List Data Type

A list is a value that contains multiple values in an ordered sequence. The term list value refers to the list itself (which you can store in a variable or pass to a function, just like any other value), not the values inside the list value. A list value looks like this: ['cat', 'bat', 'rat', 'elephant']. Just as string values use quotation marks to mark where the string begins and ends, a list begins with an opening square bracket and ends with a closing square bracket, [].

We call values inside the list items. Items are separated with commas (that is, they are comma-delimited). For example, enter the following into the interactive shell:

[1, 2, 3]  # A list of three integers
[1, 2, 3]
['cat', 'bat', 'rat', 'elephant']  # A list of four strings
['cat', 'bat', 'rat', 'elephant']
['hello', 3.1415, True, None, 42]  # A list of several values
['hello', 3.1415, True, None, 42]
❶ >>> spam = ['cat', 'bat', 'rat', 'elephant']
spam
['cat', 'bat', 'rat', 'elephant']

The spam variable ❶ is assigned only one value: the list value. But the list value itself contains other values.

Note that the value [] is an empty list that contains no values, similar to '', the empty string.

Indexes

Say you have the list ['cat', 'bat', 'rat', 'elephant'] stored in a variable named spam. The Python code spam[0] would evaluate to 'cat', the code spam[1] would evaluate to 'bat', and so on. The integer inside the square brackets that follows the list is called an index. The first value in the list is at index 0, the second value is at index 1, the third value is at index 2, and so on. Figure 6-1 shows a list value assigned to spam, along with the index expressions they’d evaluate to. Note that because the first index is 0, the last index is the size of the list minus one. So, a list of four items has 3 as its last index.

Figure 6-1: A list value stored in the variable spam, showing which value each index refers to.

spam 'cat' 'bat' 'rat' 'elephant' 0123 -4-3-2-1 positive index counts from the left · negative index counts from the right
The list stored in spam. Positive indexes start at 0 on the left; negative indexes start at −1 on the right, so spam[0] is 'cat' and spam[-1] is 'elephant'.

For an example of working with indexes, enter the following expressions into the interactive shell. We start by assigning a list to the variable spam:

spam = ['cat', 'bat', 'rat', 'elephant']
spam[0]
'cat'
spam[1]
'bat'
spam[2]
'rat'
spam[3]
'elephant'
['cat', 'bat', 'rat', 'elephant'][3]
'elephant'
❶ >>> 'Hello, ' + spam[0]
❷ 'Hello, cat'
'The ' + spam[1] + ' ate the ' + spam[0] + '.'
'The bat ate the cat.'

Notice that the expression 'Hello, ' + spam[0] ❶ evaluates to 'Hello, ' + 'cat' because spam[0] evaluates to the string 'cat'. This expression in turn evaluates to the string value 'Hello, cat' ❷.

Python will give you an IndexError error message if you use an index that exceeds the number of values in your list value:

spam = ['cat', 'bat', 'rat', 'elephant']
spam[10000]
Traceback (most recent call last):
  File "", line 1, in 
    spam[10000]
IndexError: list index out of range

Lists can also contain other list values. You can access the values in these nested lists using multiple indexes, like so:

spam = [['cat', 'bat'], [10, 20, 30, 40, 50]]
spam[0]
['cat', 'bat']
spam[0][1]
'bat'
spam[1][4]
50

The first index dictates which list value to use, and the second indicates the value within that inner list. For example, spam[0][1] evaluates to 'bat', the second value in the first list.

Negative indexes

While indexes start at 0 and go up, you can also use negative integers for the index. For example, enter the following into the interactive shell:

spam = ['cat', 'bat', 'rat', 'elephant']
spam[-1]  # Last index
'elephant'
spam[-3]  # Third to last index
'bat'
'The ' + spam[-1] + ' is afraid of the ' + spam[-3] + '.'
'The elephant is afraid of the bat.'

The integer value -1 refers to the last index in a list, the value -2 refers to the second-to-last index in a list, and so on.

Slices

Just as an index can get a single value from a list, a slice can get several values from a list, in the form of a new list. We enter a slice between square brackets, like an index, but include two integers separated by a colon. Notice the difference between indexes and slices:

  • spam[2] uses an index (one integer).
  • spam[1:4] uses a slice (two integers).

In a slice, the first integer is the index where the slice starts. The second integer is the index where the slice ends. The new list from a slice goes up to, but does not include, the value at the second index. For example, enter the following into the interactive shell:

spam = ['cat', 'bat', 'rat', 'elephant']
spam[0:4]
['cat', 'bat', 'rat', 'elephant']
spam[1:3]
['bat', 'rat']
spam[0:-1]
['cat', 'bat', 'rat']

As a shortcut, you can leave out one or both of the indexes on either side of the colon in the slice:

spam = ['cat', 'bat', 'rat', 'elephant']
spam[:2]
['cat', 'bat']
spam[1:]
['bat', 'rat', 'elephant']
spam[:]
['cat', 'bat', 'rat', 'elephant']

Leaving out the first index is the same as using 0, or the beginning of the list. Leaving out the second index is the same as using the length of the list, which slices to the end of the list.

The len() function

The len() function returns the number of values in a list passed to it. For example, enter the following into the interactive shell:

spam = ['cat', 'dog', 'moose']
len(spam)
3

This matches how len() counts characters in a string value.

Value updates

Normally, a variable name goes on the left side of an assignment statement, as in spam = 42. However, you can also use an index of a list to change the value at that index:

spam = ['cat', 'bat', 'rat', 'elephant']
spam[1] = 'aardvark'
spam
['cat', 'aardvark', 'rat', 'elephant']
spam[2] = spam[1]
spam
['cat', 'aardvark', 'aardvark', 'elephant']
spam[-1] = 12345
spam
['cat', 'aardvark', 'aardvark', 12345]

Here, spam[1] = 'aardvark' means “assign the string 'aardvark' to the slot at index 1 in the list spam.” You can also use negative indexes like -1 to update lists.

Concatenation and replication

You can concatenate and replicate lists with the + and * operators, just like strings:

[1, 2, 3] + ['A', 'B', 'C']
[1, 2, 3, 'A', 'B', 'C']
['X', 'Y', 'Z'] * 3
['X', 'Y', 'Z', 'X', 'Y', 'Z', 'X', 'Y', 'Z']
spam = [1, 2, 3]
spam = spam + ['A', 'B', 'C']
spam
[1, 2, 3, 'A', 'B', 'C']

The + operator combines two lists into a new list value, and the * operator combines a list and an integer to replicate the list.

del statements

The del statement deletes the value at an index in a list. All values after the deleted item move up one index. For example:

spam = ['cat', 'bat', 'rat', 'elephant']
del spam[2]
spam
['cat', 'bat', 'elephant']
del spam[2]
spam
['cat', 'bat']

The del statement can also operate on a simple variable to delete it, as if it were an “unassignment” statement. If you use the variable after deleting it, you’ll get a NameError because the variable no longer exists. In practice you almost never need to delete simple variables this way; del is most useful for removing items from lists.

Working with Lists

When you first begin writing programs, you may be tempted to create many individual variables to store a group of similar values. For example, if I wanted to store the names of my cats, I might think to write code like this:

python
cat_name_1 = 'Zophie'
cat_name_2 = 'Pooka'
cat_name_3 = 'Simon'
cat_name_4 = 'Lady Macbeth'

That is a brittle style. If the number of cats changes, your program will never store more cats than you have variables. These programs also duplicate nearly identical code. To see this, enter the following program into the file editor and save it as allMyCats1.py:

python
print('Enter the name of cat 1:')
cat_name_1 = input()
print('Enter the name of cat 2:')
cat_name_2 = input()
print('Enter the name of cat 3:')
cat_name_3 = input()
print('Enter the name of cat 4:')
cat_name_4 = input()
print('The cat names are:')
print(cat_name_1 + ' ' + cat_name_2 + ' ' + cat_name_3 + ' ' + cat_name_4)

Instead of many repetitive variables, use a single variable that holds a list. Here is an improved version using one list so the user can enter any number of cats. Save it as allMyCats2.py:

python
cat_names = []
while True:
    print('Enter the name of cat ' + str(len(cat_names) + 1) +
          ' (Or enter nothing to stop.):')
    name = input()
    if name == '':
        break
    cat_names = cat_names + [name]  # List concatenation

print('The cat names are:')
for name in cat_names:
    print('  ' + name)

Sample run:

text
Enter the name of cat 1 (Or enter nothing to stop.):
Zophie
Enter the name of cat 2 (Or enter nothing to stop.):
Pooka
Enter the name of cat 3 (Or enter nothing to stop.):
Simon
Enter the name of cat 4 (Or enter nothing to stop.):
Lady Macbeth
Enter the name of cat 5 (Or enter nothing to stop.):

The cat names are:
  Zophie
  Pooka
  Simon
  Lady Macbeth

Using a list puts your data in one structure your program can process flexibly.

for loops and lists

In Chapter 3, you learned to use for loops to run a block of code a set number of times. Technically, a for loop runs its block once per item in a list value. If you run:

python
for i in range(4):
    print(i)

the output is:

text
0
1
2
3

because range(4) returns a sequence treated like [0, 1, 2, 3]. This has the same output:

python
for i in [0, 1, 2, 3]:
    print(i)

The loop variable i takes each successive value from the list on each iteration.

A common pattern is range(len(some_list)) to iterate over indexes:

supplies = ['pens', 'staplers', 'flamethrowers', 'binders']
for i in range(len(supplies)):
...     print('Index ' + str(i) + ' in supplies is: ' + supplies[i])
...
Index 0 in supplies is: pens
Index 1 in supplies is: staplers
Index 2 in supplies is: flamethrowers
Index 3 in supplies is: binders

That way the loop body can use both the index (i) and the value (supplies[i]). range(len(supplies)) covers every index no matter how long the list is.

The in and not in operators

You can test membership with in and not in. They join a value and a list and evaluate to a Boolean. For example:

'howdy' in ['hello', 'hi', 'howdy', 'heyas']
True
spam = ['hello', 'hi', 'howdy', 'heyas']
'cat' in spam
False
'howdy' not in spam
False
'cat' not in spam
True

This program asks for a pet name and checks it against a list. Save as myPets.py:

python
my_pets = ['Zophie', 'Pooka', 'Fat-tail']
print('Enter a pet name:')
name = input()
if name not in my_pets:
    print('I do not have a pet named ' + name)
else:
    print(name + ' is my pet.')

Sample output:

text
Enter a pet name:
Footfoot
I do not have a pet named Footfoot

The not in operator is distinct from the Boolean not operator.

The multiple-assignment trick

The multiple assignment trick (technically tuple unpacking) assigns several variables from a list in one line. Instead of:

cat = ['fat', 'gray', 'loud']
size = cat[0]
color = cat[1]
disposition = cat[2]

you can write:

cat = ['fat', 'gray', 'loud']
size, color, disposition = cat

The number of variables must equal the length of the list, or Python raises ValueError:

cat = ['fat', 'gray', 'loud']
size, color, disposition, name = cat
Traceback (most recent call last):
  File "", line 1, in 
    size, color, disposition, name = cat
ValueError: not enough values to unpack (expected 4, got 3)

This keeps code shorter and readable.

List item enumeration

Instead of range(len(some_list)), you can use enumerate(). On each iteration it yields the index and the item:

supplies = ['pens', 'staplers', 'flamethrowers', 'binders']
for index, item in enumerate(supplies):
...     print('Index ' + str(index) + ' in supplies is: ' + item)
...
Index 0 in supplies is: pens
Index 1 in supplies is: staplers
Index 2 in supplies is: flamethrowers
Index 3 in supplies is: binders

Use this when you need both index and value inside the loop.

Random selection and ordering

The random module includes helpers that take lists. random.choice() returns one random element:

import random
pets = ['Dog', 'Cat', 'Moose']
random.choice(pets)
'Cat'
random.choice(pets)
'Dog'

You can think of random.choice(some_list) as shorthand for some_list[random.randint(0, len(some_list) - 1)].

random.shuffle() reorders a list in place:

import random
people = ['Alice', 'Bob', 'Carol', 'David']
random.shuffle(people)
people
['Carol', 'David', 'Alice', 'Bob']
random.shuffle(people)
people
['Alice', 'David', 'Bob', 'Carol']

It mutates the list rather than returning a new one.

Augmented assignment operators

The + and * operators work on strings and lists. Augmented assignment often updates a variable using itself:

spam = 42
spam = spam + 1
spam
43

Shortcut:

spam = 42
spam += 1
spam
43

Augmented forms exist for +, -, *, /, and %, as in Table 6-1.

Table 6-1: The augmented assignment operators

Augmented assignment statement Equivalent assignment statement
spam += 1 spam = spam + 1
spam -= 1 spam = spam - 1
spam *= 1 spam = spam * 1
spam /= 1 spam = spam / 1
spam %= 1 spam = spam % 1

+= can concatenate strings and lists; *= can replicate strings and lists:

spam = 'Hello,'
spam += ' world!'  # Same as spam = spam + ' world!'
spam
'Hello, world!'
bacon = ['Zophie']
bacon *= 3  # Same as bacon = bacon * 3
bacon
['Zophie', 'Zophie', 'Zophie']

Like multiple assignment, these operators shorten and clarify code.

Methods

A method is like a function, but it is called on a value. If a list is stored in spam, you call the index() list method as spam.index('hello'): the method name follows the value, after a period.

Each type has its own methods. Lists provide methods to find, add, remove, and rearrange items. Think of a method as a function always tied to a value; spam.index('hello') tells Python you mean the list version of index().

Finding values

list.index(x) returns the index of the first x in the list, or raises ValueError if x is missing:

spam = ['hello', 'hi', 'howdy', 'heyas']
spam.index('hello')
0
spam.index('heyas')
3
spam.index('howdy howdy howdy')
Traceback (most recent call last):
  File "", line 1, in 
    spam.index('howdy howdy howdy')
ValueError: 'howdy howdy howdy' is not in list

With duplicates, only the first match is returned:

spam = ['Zophie', 'Pooka', 'Fat-tail', 'Pooka']
spam.index('Pooka')
1

Adding values

append(x) adds x at the end. insert(i, x) inserts x at index i:

spam = ['cat', 'dog', 'bat']
spam.append('moose')
spam
['cat', 'dog', 'bat', 'moose']
spam = ['cat', 'dog', 'bat']
spam.insert(1, 'chicken')
spam
['cat', 'chicken', 'dog', 'bat']

Do not write spam = spam.append('moose') or assign the result of insert()—both return None. These methods modify the list in place (see Mutable and immutable data types).

append() and insert() exist only on lists:

eggs = 'hello'
eggs.append('world')
Traceback (most recent call last):
  File "", line 1, in 
    eggs.append('world')
AttributeError: 'str' object has no attribute 'append'
bacon = 42
bacon.insert(1, 'world')
Traceback (most recent call last):
  File "", line 1, in 
    bacon.insert(1, 'world')
AttributeError: 'int' object has no attribute 'insert'

Removing values

remove(x) removes the first occurrence of x:

spam = ['cat', 'bat', 'rat', 'elephant']
spam.remove('bat')
spam
['cat', 'rat', 'elephant']

A missing value raises ValueError:

spam = ['cat', 'bat', 'rat', 'elephant']
spam.remove('chicken')
Traceback (most recent call last):
  File "", line 1, in 
    spam.remove('chicken')
ValueError: list.remove(x): x not in list

With duplicates, only the first is removed:

spam = ['cat', 'bat', 'rat', 'cat', 'hat', 'cat']
spam.remove('cat')
spam
['bat', 'rat', 'cat', 'hat', 'cat']

Use del when you know the index; use remove() when you know the value.

Sorting values

sort() orders numeric lists numerically and string lists alphabetically:

spam = [2, 5, 3.14, 1, -7]
spam.sort()
spam
[-7, 1, 2, 3.14, 5]
spam = ['Ants', 'Cats', 'Dogs', 'Badgers', 'Elephants']
spam.sort()
spam
['Ants', 'Badgers', 'Cats', 'Dogs', 'Elephants']

Pass reverse=True for descending order:

spam.sort(reverse=True)
spam
['Elephants', 'Dogs', 'Cats', 'Badgers', 'Ants']

Important details:

  1. **sort() sorts in place**—do not write spam = spam.sort().
  2. Homogeneous types—you cannot sort a list that mixes incompatible types (e.g. int and str):
spam = [1, 3, 2, 4, 'Alice', 'Bob']
spam.sort()
Traceback (most recent call last):
  File "", line 1, in 
    spam.sort()
TypeError: '<' not supported between instances of 'str' and 'int'
  1. Default string order follows ASCII/Unicode code points, so uppercase letters sort before lowercase:
spam = ['Alice', 'ants', 'Bob', 'badgers', 'Carol', 'cats']
spam.sort()
spam
['Alice', 'Bob', 'Carol', 'ants', 'badgers', 'cats']

For “human” alphabetical order, pass key=str.lower:

spam = ['a', 'z', 'A', 'Z']
spam.sort(key=str.lower)
spam
['a', 'A', 'z', 'Z']

The items are not permanently lowercased; only the comparison uses lowercase.

Reversing values

reverse() flips the order in place and returns None:

spam = ['cat', 'dog', 'moose']
spam.reverse()
spam
['moose', 'dog', 'cat']

Use spam.reverse(), not spam = spam.reverse().

Short-circuiting Boolean operators

Boolean operators can short-circuit: if False and spam, the right side is never evaluated, because the result must be False. Similarly True or spam is always True.

Consider checking the first item:

python
spam = ['cat', 'dog']
if spam[0] == 'cat':
    print('A cat is the first item.')
else:
    print('The first item is not a cat.')

If spam were empty, spam[0] would raise IndexError. Safer:

python
spam = []
if len(spam) > 0 and spam[0] == 'cat':
    print('A cat is the first item.')
else:
    print('The first item is not a cat.')

If the list is empty, len(spam) > 0 is False, so spam[0] == 'cat' is skipped.

A short program: Magic 8 Ball with a list

Using lists, you can write a compact version of the Chapter 4 magic8Ball.py. Save as magic8Ball2.py:

python
import random

messages = ['It is certain',
    'It is decidedly so',
    'Yes definitely',
    'Reply hazy try again',
    'Ask again later',
    'Concentrate and ask again',
    'My reply is no',
    'Outlook not so good',
    'Very doubtful']

print('Ask a yes or no question:')
input('>')
print(messages[random.randint(0, len(messages) - 1)])

random.randint(0, len(messages) - 1) picks a valid index no matter how many messages you have, so you can edit the list without touching other lines. random.choice(messages) does the same job in one call.

Sequence data types

Lists are not the only ordered sequences. Strings behave like sequences of characters. Python’s sequence types include lists, strings, objects returned by range(), and tuples (covered next). Many list operations work on other sequences:

name = 'Zophie'
name[0]
'Z'
name[-2]
'i'
name[0:4]
'Zoph'
'Zo' in name
True
'z' in name
False
'p' not in name
False
for i in name:
...     print('* * * ' + i + ' * * *')
...
* * * Z * * *
* * * o * * *
* * * p * * *
* * * h * * *
* * * i * * *
* * * e * * *

Indexing, slicing, for loops, len(), and in / not in work on sequences in general.

Mutable and immutable data types

Lists are mutable: you can add, remove, or change elements. Strings are immutable: you cannot assign into a character position.

name = 'Zophie a cat'
name[7] = 'the'
Traceback (most recent call last):
  File "", line 1, in 
    name[7] = 'the'
TypeError: 'str' object does not support item assignment

Build a new string with slicing and concatenation:

name = 'Zophie a cat'
new_name = name[0:7] + 'the' + name[8:12]
name
'Zophie a cat'
new_name
'Zophie the cat'

The original string is unchanged.

Reassigning a list variable replaces the reference; that is different from mutating the list object:

eggs = ['A', 'B', 'C']
eggs = ['x', 'y', 'z']
eggs
['x', 'y', 'z']

Here eggs now points to a new list. To mutate the original list in place to ['x', 'y', 'z']:

eggs = ['A', 'B', 'C']
del eggs[2]
del eggs[1]
del eggs[0]
eggs.append('x')
eggs.append('y')
eggs.append('z')
eggs
['x', 'y', 'z']

Same variable name, same list object, updated contents—in-place change.

The tuple data type

Tuples differ from lists in two ways:

  1. They use parentheses () instead of [] (though the comma is what often matters).
  2. They are immutable like strings.
eggs = ('hello', 42, 0.5)
eggs[0]
'hello'
eggs[1:3]
(42, 0.5)
len(eggs)
3

Assignment fails:

eggs = ('hello', 42, 0.5)
eggs[1] = 99
Traceback (most recent call last):
  File "", line 1, in 
    eggs[1] = 99
TypeError: 'tuple' object does not support item assignment

A one-element tuple needs a trailing comma; otherwise parentheses are just grouping:

type(('hello',))
type(('hello'))

Trailing commas on the last item in a list or tuple are allowed in Python.

Tuples signal “this sequence should not change” and can be slightly faster than lists.

List and tuple conversion

list() and tuple() convert between types (like str(42)):

tuple(['cat', 'dog', 5])
('cat', 'dog', 5)
list(('cat', 'dog', 5))
['cat', 'dog', 5]
list('hello')
['h', 'e', 'l', 'l', 'o']

Use list() when you need a mutable copy of tuple data.

References

The “variables are boxes” metaphor is simplified. Better: variables are name tags referring to values in memory.

❶ >>> spam = 42
❷ >>> eggs = spam
❸ >>> spam = 99
spam
99
eggs
42

Assigning 42 to spam creates the int and binds spam to it. eggs = spam copies that reference. spam = 99 rebinds only spam; eggs still refers to 42.

Figure 6-2: Variable assignment doesn’t rewrite the value; it changes the reference.

Lists are different because they are mutable:

❶ >>> spam = [0, 1, 2, 3]
❷ >>> eggs = spam  # The reference is copied, not the list.
❸ >>> eggs[1] = 'Hello!'  # Mutates the shared list.
spam
[0, 'Hello!', 2, 3]
eggs
[0, 'Hello!', 2, 3]

Figure 6-3: Because spam and eggs refer to the same list, changing one changes the other.

spam and eggs point to the same list object. Lists store references to their elements, not necessarily the objects themselves—see The copy() and deepcopy() functions for nested structures.

Rules to remember:

  • Variables hold references, not embedded values.
  • = copies references, not deep copies of objects.

Usually you do not worry about this—until shared mutable state surprises you.

Arguments

When you call a function, Python assigns argument references to parameters. For a mutable list, the callee may change the same object the caller sees:

python
def eggs(some_parameter):
    some_parameter.append('Hello')

spam = [1, 2, 3]
eggs(spam)
print(spam)  # Prints [1, 2, 3, 'Hello']

No return value updates spam; append mutates the shared list. Forgetting this causes subtle bugs.

The copy() and deepcopy() functions

The copy module provides copy.copy() for a shallow duplicate of a list or dict so you can edit one without affecting the other (top-level container only):

import copy
spam = ['A', 'B', 'C']
cheese = copy.copy(spam)
cheese[1] = 42
spam
['A', 'B', 'C']
cheese
['A', 42, 'C']

Figure 6-4: Lists hold references to values, not necessarily the values directly.

If the list contains other mutable containers, use copy.deepcopy() to duplicate nested structure recursively.

A short program: The Matrix screensaver

In the film The Matrix, monitors show streams of green numerals—digital rain. The digits may be meaningless, but they look cool. Enter the following into a new file and save it as matrixscreensaver.py:

python
import random, sys, time

WIDTH = 70  # The number of columns

try:
    # For each column, when the counter is 0, no stream is shown.
    # Otherwise, it acts as a counter for how many times a 1 or 0
    # should be displayed in that column.
    columns = [0] * WIDTH
    while True:
        # Loop over each column:
        for i in range(WIDTH):
            if random.random() < 0.02:
                # Restart a stream counter on this column.
                # The stream length is between 4 and 14 characters long.
                columns[i] = random.randint(4, 14)

            # Print a character in this column:
            if columns[i] == 0:
                # Change this ' ' to '.' to see the empty spaces:
                print(' ', end='')
            else:
                # Print a 0 or 1:
                print(random.choice([0, 1]), end='')
                columns[i] -= 1  # Decrement the counter for this column.
        print()  # Print a newline at the end of the row of columns.
        time.sleep(0.1)  # Each row pauses for one tenth of a second.
except KeyboardInterrupt:
    sys.exit()  # When Ctrl-C is pressed, end the program.

Figure 6-5: The Matrix screensaver program.

Like the spike and zigzag examples from earlier chapters, this program scrolls by printing rows inside an infinite loop stopped with Ctrl+C. The main structure is the columns list: 70 integers, one per output column. When columns[i] is 0, that column prints a space; when it is greater than 0, the program prints a random 0 or 1 and decrements the counter. When the counter reaches 0 again, that column goes back to spaces. Occasionally each column’s counter is set to a random length between 4 and 14, producing “streams” of bits.

Imports and width

python
import random, sys, time

WIDTH = 70  # The number of columns

The code imports random (for choice() and randint()), sys (for exit()), and time (for sleep()). WIDTH is 70 so the output is 70 characters wide (change it for your terminal). ALL_CAPS names signal a constant: something the program should not need to change at runtime. Nothing in Python forbids reassignment, but the naming convention reminds you not to.

**try and columns**

python
try:
    # For each column, when the counter is 0, no stream is shown.
    # Otherwise, it acts as a counter for how many times a 1 or 0
    # should be displayed in that column.
    columns = [0] * WIDTH

The try block will handle Ctrl+C via KeyboardInterrupt at the end. columns is a list of WIDTH zeros: each entry controls whether that column prints a stream of binary digits or blank space.

Outer loops and restarting streams

python
    while True:
        # Loop over each column:
        for i in range(WIDTH):
            if random.random() < 0.02:
                # Restart a stream counter on this column.
                # The stream length is between 4 and 14 characters long.
                columns[i] = random.randint(4, 14)

while True runs forever. The inner for i in range(WIDTH) walks each column index; columns[0] is the leftmost column, and so on. For each column there is a 2% chance (random.random() < 0.02) to set its counter to a random length between 4 and 14. Raise or lower 0.02 to make streams denser or sparser.

Printing one character per column

python
            # Print a character in this column:
            if columns[i] == 0:
                # Change this ' ' to '.' to see the empty spaces:
                print(' ', end='')
            else:
                # Print a 0 or 1:
                print(random.choice([0, 1]), end='')
                columns[i] -= 1  # Decrement the counter for this column.

If the counter is 0, print a space. Otherwise print a random 0 or 1 from [0, 1] and decrement the counter. Each print(..., end='') suppresses the usual newline so one full row is built before a single print() adds the newline.

If you change the space to '.', you can see the gaps:

text
............................1.........................................
................0...........1......................1..................
................1...........0................1.....0..................
............1...0...........0.....0..........1.....0..................
............1.1.1...........0.....0..........1.....1..1...............
............0.0.0...........0.....1.........00.....1..1...............

End of row and exit

python
        print()  # Print a newline at the end of the row of columns.
        time.sleep(0.1)  # Each row pauses for one tenth of a second.
except KeyboardInterrupt:
    sys.exit()  # When Ctrl-C is pressed, end the program.

After the inner for finishes one row, print() ends the line, time.sleep(0.1) pauses briefly, and the outer loop prints the next row. The except block calls sys.exit() when the user interrupts with Ctrl+C.

Summary

Lists let one variable represent many values you can grow, shrink, or reorder. They are mutable sequences. Tuples and strings are immutable sequences—you can rebind a variable to a new tuple or string, but you cannot change the old value in place the way append() or remove() change a list.

Variables do not embed list objects; they store references. Copying a variable copies the reference, so two names can denote one list. Use copy.copy() or copy.deepcopy() when you need an independent duplicate.

Practice Questions

Practice Questions

  1. What is []?
  2. How would you assign the value 'hello' as the third value in a list stored in a variable named spam? (Assume spam is [2, 4, 6, 8, 10].)

For the next three questions, assume spam is ['a', 'b', 'c', 'd'].

  1. What does spam[int(int('3' * 2) // 11)] evaluate to?
  2. What does spam[-1] evaluate to?
  3. What does spam[:2] evaluate to?

For the next three questions, assume bacon is [3.14, 'cat', 11, 'cat', True].

  1. What does bacon.index('cat') evaluate to?
  2. What does bacon.append(99) make the list value in bacon look like?
  3. What does bacon.remove('cat') make the list value in bacon look like?
  4. What are the operators for list concatenation and list replication?
  5. What is the difference between the append() and insert() list methods?
  6. What are two ways to remove values from a list?
  7. Name a few ways that list values are similar to string values.
  8. What is the difference between lists and tuples?
  9. How do you write a tuple value that contains only the integer 42?
  10. How do you convert a list to a tuple? A tuple to a list?
  11. Variables that “contain” list values don’t actually store the list directly. What do they store instead?
  12. What is the difference between copy.copy() and copy.deepcopy()?

Practice programs

Comma code

Say you have a list like:

python
spam = ['apples', 'bananas', 'tofu', 'cats']

Write a function that takes a list and returns a string with items separated by a comma and a space, with and before the last item. Passing the list above should return 'apples, bananas, tofu, and cats'. It should work for any list; test an empty list [].

Coin flip streaks

If you flip a coin 100 times you might write T T T T H H H H T T. Humans asked to invent “random” sequences rarely write long streaks of heads or tails, though real random flips often produce them.

Write a program to estimate how often a run of six heads or six tails appears in 100 random flips. Split the work: first build a list of 100 'H'/'T' values, then check for a streak. Repeat the whole experiment in an outer loop so you can estimate a percentage. random.randint(0, 1) returns 0 or 1 about half the time each.

You can start with the following template:

python
import random
number_of_streaks = 0
for experiment_number in range(10000):  # Run 100,000 experiments total.
    # Code that creates a list of 100 'heads' or 'tails' values

    # Code that checks if there is a streak of 6 heads or tails in a row

print('Chance of streak: %s%%' % (number_of_streaks / 100))

Of course, this is only an estimate, but 10,000 is a decent sample size.

To build the flip list, append a randomly selected 'H' or 'T' in a loop 100 times. To detect a streak of six, use a slice like some_list[i:i + 6] and compare it to ['H', 'H', 'H', 'H', 'H', 'H'] and ['T', 'T', 'T', 'T', 'T', 'T'].

Try it yourself

Python · runs in your browser

Edit the code and click Run. This is real Python running in your browser — no install needed. Stick to the standard library (no pip).

Check your understanding

Tier 2 depth · Applied coding

0 / 5 correct
  1. Given `spam = ['cat', 'bat', 'rat', 'elephant']`, what does `spam[1]` return?

  2. What does `spam[-1]` refer to?

  3. Given `spam = ['a', 'b', 'c', 'd']`, what does the slice `spam[1:3]` give?

  4. What's the key difference between a list and a tuple?

  5. After `eggs = ['x']; eggs.append('y')`, what is `eggs`?

Go deeper

More in Additional Resources →
← Debugging Dictionaries and Structuring Data →