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.
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]50The 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)3This 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] = 12345spam['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:
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:
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:
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:
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 MacbethUsing 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:
for i in range(4):
print(i)the output is:
0
1
2
3because range(4) returns a sequence treated like [0, 1, 2, 3]. This has the same output:
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: bindersThat 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']Truespam = ['hello', 'hi', 'howdy', 'heyas']'cat' in spamFalse'howdy' not in spamFalse'cat' not in spamTrueThis program asks for a pet name and checks it against a list. Save as myPets.py:
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:
Enter a pet name:
Footfoot
I do not have a pet named FootfootThe 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 = catThe number of variables must equal the length of the list, or Python raises ValueError:
cat = ['fat', 'gray', 'loud']size, color, disposition, name = catTraceback (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: bindersUse 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 randompets = ['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 randompeople = ['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 = 42spam = spam + 1spam43Shortcut:
spam = 42spam += 1spam43Augmented 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 * 3bacon['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')0spam.index('heyas')3spam.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')1Adding 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 = 42bacon.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:
**sort()sorts in place**—do not writespam = spam.sort().- Homogeneous types—you cannot sort a list that mixes incompatible types (e.g.
intandstr):
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' - 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:
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:
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:
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 nameTrue'z' in nameFalse'p' not in nameFalsefor 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:
- They use parentheses
()instead of[](though the comma is what often matters). - They are immutable like strings.
eggs = ('hello', 42, 0.5)eggs[0]'hello'eggs[1:3](42, 0.5)len(eggs)3Assignment fails:
eggs = ('hello', 42, 0.5)eggs[1] = 99Traceback (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 = 99spam99eggs42Assigning 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:
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 copyspam = ['A', 'B', 'C']cheese = copy.copy(spam)cheese[1] = 42spam['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:
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
import random, sys, time
WIDTH = 70 # The number of columnsThe 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**
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] * WIDTHThe 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
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
# 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:
............................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
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
- What is
[]? - How would you assign the value
'hello'as the third value in a list stored in a variable namedspam? (Assumespamis[2, 4, 6, 8, 10].)
For the next three questions, assume spam is ['a', 'b', 'c', 'd'].
- What does
spam[int(int('3' * 2) // 11)]evaluate to? - What does
spam[-1]evaluate to? - What does
spam[:2]evaluate to?
For the next three questions, assume bacon is [3.14, 'cat', 11, 'cat', True].
- What does
bacon.index('cat')evaluate to? - What does
bacon.append(99)make the list value inbaconlook like? - What does
bacon.remove('cat')make the list value inbaconlook like? - What are the operators for list concatenation and list replication?
- What is the difference between the
append()andinsert()list methods? - What are two ways to remove values from a list?
- Name a few ways that list values are similar to string values.
- What is the difference between lists and tuples?
- How do you write a tuple value that contains only the integer
42? - How do you convert a list to a tuple? A tuple to a list?
- Variables that “contain” list values don’t actually store the list directly. What do they store instead?
- What is the difference between
copy.copy()andcopy.deepcopy()?
Practice programs
Comma code
Say you have a list like:
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:
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'].