Lists

Use the workhorse container properly — and understand the day you lose to aliasing before you lose it.

04of 24
≈ 13Min read
5Parts

A list is an ordered, mutable sequence

A list holds references to objects of any type, grows on demand, and is indexed and sliced exactly like a string. Everything about s[a:b:c] from the strings lesson applies here too.

py
items = ["a", "b", "c"]
items.append("d")        # at the end        — O(1)
items.insert(0, "z")     # at a position     — O(n), everything after shifts
items.extend(["e", "f"]) # several at once   ( items += [...] is the same )
items.remove("b")        # the first one equal to "b"; ValueError if absent
last = items.pop()       # take from the end — O(1)
first = items.pop(0)     # take from the front — O(n); use collections.deque instead
items[1:3] = ["x"]       # slices assign, and can change the length

Two names, one list

Assignment never copies in Python. It binds a name to an object, and both names then see the same object — which is the single most common source of "but I never changed that" bugs.

A shallow copy makes a new list containing the same inner objects: change one of those inner objects and both lists see it. copy.deepcopy copies all the way down, and is slow enough that you should only reach for it when you mean it.

py
a = [1, 2, 3]
b = a                # NOT a copy — another name for the same list
b.append(4)
print(a)             # [1, 2, 3, 4]

c = a.copy()         # a shallow copy — also list(a), or a[:]
c.append(5)
print(a)             # unchanged

import copy
d = copy.deepcopy(nested)   # when the list contains lists

The multiplication trap

[x] n repeats the reference n times. For immutable contents ([0] 3) that is harmless; for a list of lists it gives you one row pretending to be three.

py
grid = [[0] * 3] * 3        # three references to ONE row
grid[0][0] = 9
print(grid)                 # [[9, 0, 0], [9, 0, 0], [9, 0, 0]]

grid = [[0] * 3 for _ in range(3)]   # three separate rows
grid[0][0] = 9
print(grid)                 # [[9, 0, 0], [0, 0, 0], [0, 0, 0]]

Sorting

list.sort() sorts in place and returns None — items = items.sort() is a bug that assigns nothing, and it happens to everyone once. sorted() returns the new list.

Python’s sort is stable: equal keys keep their previous order. That is what makes sorting twice work — sort by the secondary key first, then by the primary.

py
people = [("ada", 36), ("grace", 45), ("alan", 41)]

people.sort()                             # in place, returns None
ordered = sorted(people)                  # a new list, leaves the original alone

sorted(people, key=lambda p: p[1])                # by age
sorted(people, key=lambda p: -p[1])               # descending, numerically
sorted(people, key=lambda p: (p[1], p[0]))        # by age, then by name
sorted(words, key=str.casefold)                   # case-insensitive
max(people, key=lambda p: p[1])                   # the same key idea

Testing membership

x in items scans the list: it is O(n). For a big collection you are looking things up in repeatedly, convert it to a set once and the test becomes O(1). That one change has rescued more slow scripts than any other.

py
banned = {"a", "b", "c"}          # a set literal
[w for w in words if w not in banned]

Practice