Lists & Tuples
The Input & Data Types page introduced lists and tuples as two ways to hold more than one value at a time. This page covers indexing and slicing, the operations that change a list, and where tuples are used beyond a simple coordinate pair.
The Python documentation has guides on Lists and Tuples.
Indexing and Slicing
A list is indexed the same way as a string: [0] for the first element, [-1] for the last, and [start:stop] for a slice that includes start up to (but not including) stop.
altitudes = [300, 450, 550, 800, 1200]
print(altitudes[0]) # first element
print(altitudes[-1]) # last element
print(altitudes[1:3]) # elements 1 up to (not including) 3
print(altitudes[:2]) # from the start through index 1
print(altitudes[2:]) # from index 2 to the end
print(altitudes[::-1]) # reversed
300
1200
[450, 550]
[300, 450]
[550, 800, 1200]
[1200, 800, 550, 450, 300]
altitudes[::-1] uses a slice with a step of -1, which walks the list backward and produces a reversed copy.
Modifying a List
Unlike a string or a tuple, a list can be changed after it’s created:
altitudes = [450, 800, 1200]
altitudes[0] = 400
print(altitudes)
altitudes.append(1500) # add to the end
print(altitudes)
altitudes.insert(0, 300) # insert at a specific index
print(altitudes)
removed = altitudes.pop() # remove and return the last element
print(removed, altitudes)
altitudes.remove(400) # remove the first matching value
print(altitudes)
[400, 800, 1200]
[400, 800, 1200, 1500]
[300, 400, 800, 1200, 1500]
1500 [300, 400, 800, 1200]
[300, 800, 1200]
append adds a value to the end, insert adds one at a specific position, pop removes and returns the last value, and remove deletes the first value that matches the one given.
Each of these changes the list in place, rather than creating a new one.
A few more methods round out the common ways to modify a list:
altitudes = [300, 450, 800, 1200, 800]
altitudes.extend([2000, 2500]) # append multiple values at once
print(altitudes)
print(altitudes.count(800)) # how many times a value appears
print(altitudes.index(1200)) # index of the first match
altitudes.reverse() # reverses IN PLACE
print(altitudes)
[300, 450, 800, 1200, 800, 2000, 2500]
2
3
[2500, 2000, 800, 1200, 800, 450, 300]
extend adds every value from another list, count reports how many times a value appears, index finds the position of the first match, and reverse flips the list in place.
Concatenation and Repetition
+ and * work on lists, but not the way they work on numbers:
a = [1, 2, 3]
b = [4, 5, 6]
print(a + b) # concatenation, NOT element-wise addition
print(a * 3) # repeats the list, NOT element-wise multiplication
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 1, 2, 3, 1, 2, 3]
a + b joins the two lists end to end, and a * 3 repeats the list’s contents three times.
Neither one does element-wise math.
That’s what NumPy Arrays are for.
Assignment Does Not Copy a List
Assigning a list to a new variable does not create a second, independent list. It just gives the same list a second name:
original = [1, 2, 3]
alias = original # alias and original are the SAME list
alias.append(4)
print(original) # also changed! both names point to one list
independent = original.copy()
independent.append(5)
print(original) # unaffected
print(independent)
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
alias and original refer to the same list, so a change made through alias shows up when reading original too.
Calling .copy() (or list(original)) creates an independent list, so changes to the copy don’t affect the original.
This is a common source of bugs when it’s unexpected.
Common List Operations
A handful of built-in functions work on any list:
altitudes = [1200, 300, 800, 450]
print(len(altitudes))
print(sum(altitudes))
print(max(altitudes))
print(min(altitudes))
print(sorted(altitudes)) # returns a NEW sorted list
altitudes.sort() # sorts the list IN PLACE
print(altitudes)
print(800 in altitudes)
print(9999 in altitudes)
4
2750
1200
300
[300, 450, 800, 1200]
[300, 450, 800, 1200]
True
False
sorted(altitudes) returns a new, sorted list without changing the original, while altitudes.sort() sorts the list in place.
The in operator tests whether a value appears anywhere in the list.
Example: Fuel Log Summary
Question
An aircraft’s fuel remaining is logged once per hour during a flight. Given the hourly readings, find the largest single-hour fuel burn and which hour it happened in.
Solution
A for loop builds a list of the hour-to-hour differences, then max and index find the largest one and where it occurred.
fuel_readings_lb = [3000, 2550, 2120, 1710, 1315]
burns = []
for i in range(len(fuel_readings_lb) - 1):
burns.append(fuel_readings_lb[i] - fuel_readings_lb[i + 1])
print(burns)
peak_burn = max(burns)
peak_hour = burns.index(peak_burn) + 1
print(f"Peak burn: {peak_burn} lb during hour {peak_hour}")
[450, 430, 410, 395]
Peak burn: 450 lb during hour 1
Tuples in Depth
A tuple looks and behaves like a list, except that it cannot be changed after it’s created.
Attempting to assign to an element, such as coordinates[0] = 0, raises a TypeError.
This makes tuples a good fit for a fixed group of values that should never be modified, such as coordinates or a return value made up of several related numbers.
A function can only return a single value, but that value can be a tuple, which is how a function returns several results at once:
import math
def orbit_stats(mu, r):
v = math.sqrt(mu / r)
T = 2 * math.pi * r / v
return v, T
velocity, period = orbit_stats(398600, 6778)
print(f"v = {velocity:.4f} km/s, T = {period:.1f} s")
result = orbit_stats(398600, 6778)
print(type(result), result)
v = 7.6686 km/s, T = 5553.5 s
<class 'tuple'> (7.668631425322557, 5553.458974626874)
orbit_stats returns v, T, which Python packs into a tuple automatically.
Writing velocity, period = orbit_stats(398600, 6778) unpacks that tuple directly into two variables. Assigning the result to a single variable, as in result, keeps it as one tuple.
Nested Lists
A list can contain other lists, which is a crude way to represent a 2D grid in plain Python:
grid = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
print(grid)
print(grid[1]) # the second row
print(grid[1][1]) # the center element
[[0, 0, 0], [0, 1, 0], [0, 0, 0]]
[0, 1, 0]
1
grid[1] is the second row (itself a list), and grid[1][1] indexes into that row for a single element.
Nested lists get unwieldy quickly for anything numeric, which is one of the reasons NumPy Arrays exist.
Reading Questions
- What is the difference between
altitudes[1:3]andaltitudes[:3]? - Name two list methods that change a list in place.
- What is the difference between
sorted(altitudes)andaltitudes.sort()? - Why can’t a list element be reassigned inside a tuple?
- If a function ends with
return v, T, what type of value does it return, and how would you unpack it into two variables? - How would you access the middle element of
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]? - What does
[1, 2] + [3, 4]produce? What about[1, 2] * 2? - If
b = afor two lists, and thenb.append(5)is called, doesachange? Why? - How would you make an independent copy of a list, rather than a second name for the same list?