Lists
Creating and indexing
A list is written with square brackets. Lists can be empty or contain comma-separated item. Items can be any type and can even mixed:
scores = [88, 92, 79, 95]
names = ["Ada", "Grace", "Alan"]
mixed = ["Ada", 36, True]
empty = []
Lists can be indexed, concatenated and sliced exactly like strings. Indexes are 0-based:
scores[0] # ⇒ 88 first
scores[-1] # ⇒ 95 last
scores[1:3] # ⇒ [92, 79] a slice is a NEW list
len(scores) # ⇒ 4
[2] + [3] # ⇒ [2, 3]
[2] * 3 # ⇒ [2, 2, 2]
Mutating lists
Unlike strings, you can change a list's contents without making a new one:
scores = [88, 92, 79]
scores[0] = 100 # replace an element
print(scores) # ⇒ [100, 92, 79]
Lists can contain other lists:
matrix = [[1,2,3], [4,5,6], [7,8,9]]
The list type defines several methods that change a list in place:
nums = [1, 2, 3]
nums.append(4) # add ONE item to the end → [1, 2, 3, 4]
nums.insert(0, 9) # insert at an index → [9, 1, 2, 3, 4]
nums.extend([5, 6]) # add several from another list→ [9, 1, 2, 3, 4, 5, 6]
nums.remove(9) # remove first matching VALUE → [1, 2, 3, 4, 5, 6]
last = nums.pop() # remove & RETURN the last → last=6
first = nums.pop(0) # remove & return by index → first=1
nums.sort() # sort IN PLACE (ascending)
nums.sort(reverse=True) # descending
nums.reverse() # reverse in place
nums.clear() # empty it
Mutating methods don't return a value
Generally, methods that change a list's contents in place return None.
A common mistake is to mutate and re-bind a reference to a list:
nums = nums.append(4)
This will not have the expected outcome. The expression will throw away
your list and store None instead.
The exception is .pop() which removes a value from the list and returns it.
Non-mutating alternatives
It is often recommended to avoid changing a list in place, but what alternatives are there?
- concatenation:
.append()and.extend()nums = [1, 2, 3] nums + [4] # → [1, 2, 3, 4] nums + [4,5,6] # → [ 1, 2, 3, 4, 4, 6] - slicing:
.insert()nums[:0] + [9] + nums[0:] # → [9, 1, 2, 3] - non-mutating functions: non-mutating built-in functions for sorting and reversing lists:
nums = [4,3,6,2] sorted(nums) # → [2, 3, 4, 6] sorted(nums, reverse=True) # → [6, 4, 3, 2] list(reversed(nums)) # → [2, 6, 3, 4]
Searching, couting, and aggregation
nums = [1, 2, 3]
3 in nums # → True
nums.count(2) # number of occurrences of 2 in the list
nums.index(3) # position of first occurrence of 3 in the list
sum(nums) # ⇒ total (works only for lists of numbers)
min(nums) # → 1
max(nums) # → 3
Lists from strings and back
text = 'the quick brown fox'
words = text.split()
print(len(words), "words") # ⇒ 3 words
csv_header = ["Name", "Job Title", "Salary"]
';'.join(csv_header)
list gotchas
-
Aliasing
a = [1, 2, 3] b = a # → b is not a new list, but an alias of a b.append(4) print(b) # ⇒ [1, 2, 3, 4] print(a) # ⇒ [1, 2, 3, 4] -
Copying
nums = [1,2,3] nums.copy() # → [1,2,3].copy()only copies references of list items, not their values. For lists that contain other lists, this can lead to subtle bugs:nums = [[1], [1], [1]] copy = nums.copy() id(nums[0]) == id(copy[0]) # → True copy[0][0] = 5 print(copy) # → [[5], [1], [1]] print(nums) # → [[5], [1], [1]]Watch out for this behavior too when you concatenate lists containing other lists:
a = [[]] # a list containing an empty list b = [[1]] c = a + b print(c) # → [[], [1]] c[0].append(3) print(c) # → [[3], [1]] print(a) # -> [[3]] x = y = [[]] x[0].append(1) print(x) # → [[1]] print(x) # → [[1]] x = [[1, 2, 3], [4, 5, 6]] y = x * 2 y[1].clear() print(y) # → [[1, 2, 3], [], [1, 2, 3], []] print(x) # → [[1, 2, 3], []]This is why it is recommended to prefer creating a new list over changing a list in place