Chapter 10 · pp. 111–124
Lists
A list is one of Python's most useful built-in types. It stores an ordered group of values that you can look at, change, combine and process with loops. Because lists can be changed, you also need to understand how Python tracks objects, so that two names for one list do not surprise you.
What a list is
A list is a sequence of values called elements (or items). You create one by putting values between square brackets, such as [10, 20, 30, 40]. A string holds only characters, but a list can hold any type, and the types can be mixed. For example, ['spam', 2.0, 5, [10, 20]] holds a string, a float, an integer and another list (p. 111).
A list inside another list is called nested. A list with no elements, written [], is an empty list (p. 111). You can store lists in variables like any other value, for example cheeses = ['Cheddar', 'Edam', 'Gouda'].
A nested list counts as only one element of the list around it. The list ['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]] has length 4, because len counts the two inner lists as one element each and does not count what is inside them (p. 113).
Indexing, mutability and membership
You reach an element with the bracket operator and an index. Indices start at 0, so cheeses[0] is 'Cheddar'. Any integer expression can be used as an index. A negative index counts backward from the end, so cheeses[-1] is 'Gouda'. If you read or write an index that does not exist, Python raises an IndexError (p. 112).
Unlike strings, lists are mutable, meaning you can change them after they are made. Put the bracket operator on the left side of an assignment to replace one element:
numbers = [42, 123]
numbers[1] = 5 # numbers is now [42, 5]
Doing the same to a string would cause an error (p. 112). Mutability is powerful, but it is also why copies and aliases (covered below) need care.
The in operator checks whether a value is an element of a list. 'Edam' in cheeses is True, and 'Brie' in cheeses is False (p. 112).
Traversing a list
If you only need to read each element, use a for loop directly on the list:
for cheese in cheeses:
print(cheese)
If you need to update elements, you need their positions. Combine range and len. len(numbers) gives the number of elements, n, and range(len(numbers)) produces the indices 0 to n−1. This loop doubles every element in place:
for i in range(len(numbers)):
numbers[i] = numbers[i] * 2
Each time through, i is the index of the next element. The body reads the old value and assigns the new one (p. 113). A for loop over an empty list never runs its body (p. 113).
Operators and slices
The + operator joins two lists into a new list: [1, 2, 3] + [4, 5, 6] gives [1, 2, 3, 4, 5, 6]. The * operator repeats a list: [0] * 4 gives [0, 0, 0, 0], and [1, 2, 3] * 3 gives the three-item list repeated three times. Both build a new list and leave the originals unchanged (p. 113).
A slice t[a:b] makes a new list from index a up to but not including index b. With t = ['a','b','c','d','e','f']:
t[1:3] is ['b', 'c']
t[:4] is ['a', 'b', 'c', 'd'] (no first index means start at the beginning)
t[3:] is ['d', 'e', 'f'] (no second index means go to the end)
t[:] is a copy of the whole list
(pp. 113–114). Because lists are mutable, copying before an operation that modifies a list is often wise (p. 114). A slice on the left side of an assignment can replace several elements at once: t[1:3] = ['x', 'y'] makes t equal to ['a', 'x', 'y', 'd', 'e', 'f'] (p. 114).
List methods
append adds one element to the end.
extend takes a list and adds all of its elements to the end. The list you pass in is not changed.
sort arranges the elements from low to high.
(p. 114)
Most list methods are "void": they change the list in place and return None. String methods are the opposite, returning a new string. So word = word.strip() is fine for strings, but t = t.sort() is a mistake. It replaces t with None and you lose the list (pp. 114, 120–121). Example: if t1 = [1, 2] and t2 = t1.append(3), then t1 is [1, 2, 3] but t2 is None (p. 119).
Map, filter and reduce
Many list tasks fit one of three patterns. Each uses an accumulator, a variable that is updated inside the loop.
- Reduce combines all elements into one value. The function
add_all starts with total = 0 and adds each element. Python's built-in sum does the same job: sum([1, 2, 3]) is 6 (p. 115).
- Map applies an operation to every element and builds a new list.
capitalize_all starts with res = [] and appends s.capitalize() for each string s (p. 115).
- Filter keeps only elements that meet a condition.
only_upper appends a string to res only if s.isupper() is true (pp. 115–116).
The line total += x is an augmented assignment. It is shorthand for total = total + x, which updates a variable using its old value (p. 115).
Deleting elements
t.pop(1) removes the element at index 1 and returns it. With no argument, it removes and returns the last element.
del t[1] removes the element at index 1 and returns nothing, so use it when you don't need the value.
t.remove('b') removes by value, not index, and returns None.
del t[1:5] removes several elements. With ['a','b','c','d','e','f'], the result is ['a', 'f'], because the slice stops before index 5.
(p. 116)
Lists and strings
A list of characters is not the same thing as a string. Three tools convert between them:
list('spam') gives ['s', 'p', 'a', 'm'], one element per character.
split breaks a string into words. 'pining for the fjords'.split() gives four words. An optional delimiter says where to split, so 'spam-spam-spam'.split('-') gives ['spam', 'spam', 'spam'].
join is the inverse of split. It is called on the delimiter and given the list: ' '.join(t) puts spaces between words, and ''.join(t) puts nothing between them.
(pp. 116–117) Since list is a built-in function name, avoid using it as a variable name (p. 117).
Objects, identity and aliasing
Two variables can have the same value without being the same object. The is operator tests whether two names refer to one object. With a = [1, 2, 3] and b = [1, 2, 3], the lists are equivalent (same value) but not identical, so a is b is False. Identical objects are always equivalent, but equivalent objects need not be identical (pp. 117–118).
After b = a, both names refer to the same list, so b is a is True. The object now has two references, and we say it is aliased. If the object is mutable, a change through one name shows through the other: after b[0] = 42, a is [42, 2, 3] (p. 118). This can be useful but is error-prone, so it is safer to avoid aliasing mutable objects. For immutable strings, aliasing rarely matters (p. 119).
Passing a list to a function creates an alias too. The function delete_head(t), which does del t[0], changes the caller's list. After delete_head(letters) with letters = ['a', 'b', 'c'], letters is ['b', 'c'] (p. 119). But t = t[1:] inside a function only makes the local name t point to a new list, and the caller's list stays the same. That is why bad_delete_head fails (p. 120). To avoid changing the original, write a function that returns a new list, like tail(t), which returns t[1:] (p. 120). Remember: append modifies a list, while + creates a new one (pp. 119–120).
Check yourself
- What is the length of
[1, [2, 3], [4, [5, 6]]], and why?
- Given
t = ['a','b','c','d','e','f'], what are t[2:5], t[:2] and t[-1]?
- Why does
t = t.sort() cause trouble, and how can you sort while keeping the original list?
- Classify each as map, filter or reduce: (a) keeping only the even numbers in a list, (b) finding the total of a list, (c) turning every string in a list to uppercase.
- After
a = [1, 2, 3], b = a, b[0] = 42, what is a? Is a is b true? What would change if you wrote b = a[:] instead?