apple
banana
cherry
6.1 Tuples
A pair of coordinates or a returned count and total has a fixed order. A tuple stores such a group of values as an ordered sequence. You can read its elements by position, as with a list, while its entries remain fixed after creation.
6.1.1 Creating and reading tuples
Commas separate a tuple's elements. Parentheses make the grouping visible, as in (2, 5). A tuple can contain numbers, strings, or other collections. Table 1 shows empty, single-element, mixed, and nested tuples. The Python tuple reference explains the comma-based construction rules.
| Tuple Example | Description |
|---|---|
("apple", "banana", "cherry") |
A tuple of strings. |
(1, 2, 3, 4, 5) |
A tuple of integers. |
("apple", 1, "banana", 2) |
A tuple of mixed data types. |
("apple", ("banana", "cherry"), "orange") |
A tuple with a nested tuple. |
() |
An empty tuple. |
((1, 2), (3, 4), (5, 6)) |
A 2D tuple with three rows and two columns. |
(1,) |
A tuple with a single element. Note the comma , after the element. |
The comma in (1,) makes it a single-element tuple. (1) is the integer 1 enclosed in parentheses. Write () for an empty tuple, or use tuple(). tuple([1, 2, 3]) converts a list to the tuple (1, 2, 3).
Indexing and slicing follow the rules already introduced for lists. Listing 1 reads individual elements and a slice. Indexing begins at zero, a negative index counts backward from the end, and a slice excludes its ending index.
The output is apple, cherry, ('banana', 'cherry'), and 3 on separate lines. The slice produces a tuple. A nested tuple can be read with successive indices; ((1, 2), (3, 4))[1][0] gives 3.
6.1.2 Tuple packing and unpacking
Packing collects values into a tuple. The assignment fruits = "apple", "banana", "cherry" packs three values, with optional parentheses. Tuple unpacking assigns the elements to separate variables in their sequence order. Listing 2 shows both operations.
The three printed names are apple, banana, and cherry. The number of variables must match the number of elements in this form of unpacking. For example, first, second = (1, 2, 3) raises ValueError because three values cannot be assigned to two variables.
Unpacking also explains the familiar loop for index, value in enumerate(values): each generated pair supplies one value for index and one for value. A function can return several values as a tuple, such as return count, total; its caller can receive them with count, total = summarize(values).
6.1.3 Tuple immutability
A tuple is immutable. You cannot replace, add, or remove its entries after creation. Listing 3 attempts to replace the first entry and catches the resulting error so you can inspect the unchanged tuple.
Python prints TypeError: 'tuple' object does not support item assignment and then (2, 5). You can assign point = (3, 5) afterward; that assignment makes the variable refer to a different tuple.
A tuple can contain a mutable object such as a list. The tuple's entry continues to refer to that list, whose contents can change. Listing 4 shows the distinction.
The result is ('Monday', [2, 3, 4]). The list changed; replacing delivery[1] with another list would still raise TypeError.
6.1.4 Returning a value and its identifier
When you find the largest value in a sequence, you may also need to know which item supplied it. Store the value and its identifier in one tuple, then replace that tuple whenever you find a larger value. Listing 5 finds the largest measured package mass.
packages = [("P01", 3.2), ("P02", 4.5), ("P03", 4.5)]
package_id, mass = packages[0]
best = (mass, package_id)
index = 1
while index < len(packages):
package_id, mass = packages[index]
if mass > best[0]:
best = (mass, package_id)
index += 1
print(best)
assert best == (4.5, "P02"), "Check the largest mass and its identifier"The initial tuple is (3.2, "P01"). After visiting P02, it is (4.5, "P02"). The comparison uses >, so the equal mass of P03 leaves the first matching package in the result. The code reads the input list without changing it and assumes at least one package.
For several measurements per item, repeat the search for each measurement position. An outer loop selects the position, and an inner loop visits every item. Collect one result tuple per position in a new list.
6.1.5 Further study
The following operations extend the creation, reading, and unpacking above. Named tuples and zip are useful additional ways to organize related values.
Tuple methods and functions
Tuples have the methods count() and index(). Built-in functions such as len() and sum() also accept tuples. A method is called through an object, as in t.count(1); a function receives the object as an argument, as in len(t). Table 2 keeps that distinction visible for t = (1, 2, 3, 1, 2).
t = (1, 2, 3, 1, 2).
| Method or function | Description | Example | Result of the example |
|---|---|---|---|
tuple.count(element) |
Returns the number of occurrences of the specified element in the tuple. | t.count(1) |
2 |
tuple.index(element) |
Returns the index of the first occurrence of the specified element in the tuple. | t.index(2) |
1 |
len(tuple) |
Returns the number of elements in the tuple. | len(t) |
5 |
max(tuple) |
Returns the maximum element in the tuple. | max(t) |
3 |
min(tuple) |
Returns the minimum element in the tuple. | min(t) |
1 |
sum(tuple) |
Returns the sum of elements in the tuple. | sum(t) |
9 |
sorted(tuple) |
Returns a list with the elements sorted; the example converts that list to a tuple. | tuple(sorted(t)) |
(1, 1, 2, 2, 3) |
reversed(tuple) |
Returns an iterator over the elements in reverse order; the example collects those elements in a tuple. | tuple(reversed(t)) |
(2, 1, 3, 2, 1) |
An iterator supplies values one at a time when a loop or constructor requests them. None of these operations changes t. t.index(value) raises ValueError when the value is absent, and max() and min() require at least one element. For the numeric tuple in the table, sum() adds all values.
Named tuples
Named tuples give names to positions. The namedtuple function from the collections module creates a tuple type with named fields; technically, that type is a subclass of tuple. In Listing 6, Point names a type whose two fields are x and y. Point(1, 2) creates a point, and p.x reads its first field by name.
The output is 1 and 2 on separate lines. Named fields make the intended meaning visible without remembering an index. The tuple's fields remain fixed after creation, as with ordinary tuples.
Zip function
The zip function pairs values by position. It accepts iterable objects, which are objects a loop can visit, such as lists, tuples, and strings. It returns an iterator of tuples. The first tuple contains the first value from each input, the second contains the second value, and so on.
Combining parallel sequences
Two parallel sequences describe corresponding information in the same order. Listing 7 pairs each city with its arrival time. Wrapping zip(...) in list(...) collects the pairs so you can print and inspect them. The same listing also creates consecutive city pairs using slices.
zip function.
[('City A', '10:00'), ('City B', '11:30'), ('City C', '13:00')]
[('City A', 'City B'), ('City B', 'City C')]
The first printed list is [('City A', '10:00'), ('City B', '11:30'), ('City C', '13:00')]. Pairing follows position, so the inputs must use the same city order.
You can also visit the generated pairs directly in a loop. Listing 8 unpacks each fruit and price into separate variables.
zip function.
The output is apple: $1.0, banana: $0.5, and cherry: $2.0 on separate lines.
Adjacent pairs
In Listing 7, route[:-1] contains all cities except the last, and route[1:] contains all cities except the first. Pairing the slices gives [('City A', 'City B'), ('City B', 'City C')]. Each pair describes one consecutive journey segment. The two slices have the same length.
Unequal input lengths
By default, zip stops when its shortest input ends. Remaining elements in a longer input do not appear in the result. Listing 9 shows one fruit with no corresponding price. See the Python zip documentation for the stopping rule and the strict=True option.
The first output is [('apple', 1.0), ('banana', 0.5)]. With strict=True, the calculation raises ValueError when the lengths differ. Use that option when every input item must have a corresponding partner. The error occurs when the iterator is consumed, here by list(...).
Separating paired values
The zip function can also separate a list of pairs into sequences. Listing 10 uses *pairs to supply each pair as a separate argument to zip. For these inputs, zip generates one tuple of fruit names and one tuple of prices. Unpacking assigns those tuples to fruits and prices.
The output is ('apple', 'banana', 'cherry') followed by (1.0, 0.5, 2.0). These returned sequences are tuples. If you need lists that can be changed, convert them with list(fruits) and list(prices). This example assumes at least one pair, with exactly two values in every pair.
6.1.6 Exercises
6.1.Q1 Trace tuple unpacking
The program in Listing 11 processes three pairs of integers. In each iteration, unpacking assigns the pair's first value to first and its second value to second. The program adds first to a running total and calculates the current pair's difference.
Complete Table 3 at the marked checkpoint after each iteration. The first row shows how to record the values. State the final printed output and explain whether unpacking changes the tuples in pairs.
| Iteration | Current pair | first |
second |
running_total |
difference |
|---|---|---|---|---|---|
| 1 | (2, 5) |
2 | 5 | 2 | -3 |
| 2 | (4, 1) |
||||
| 3 | (0, 3) |
The following optional exercise applies the pairs produced by zip to numeric sequences. Review the zip function before attempting it.
6.1.Q2 Element-wise multiplication
Create a function that takes two vectors of the same length as input and returns a new vector containing the element-wise multiplication of the input vectors.
A vector here is a list of numbers. Element-wise multiplication multiplies the values at matching positions. For example, [2, 3] and [5, 7] produce [10, 21]. Leave both input lists unchanged, and return a list in the original position order. Two empty input lists produce an empty list.
Present your algorithm in Python, pseudocode, or a flowchart. If you use Python, you may use zip to visit matching values as pairs. The starter and supplied checks are in Listing 12.
def elementwise_multiply(
vector1: list[float], vector2: list[float]
) -> list[float]:
# Add code here!
pass
def test_elementwise_multiply() -> None:
# Input list 1
vector1 = [1, 2, 3]
# Input list 2
vector2 = [4, 5, 6]
result = elementwise_multiply(vector1, vector2)
assert result[0] == 4
assert result[1] == 10
assert result[2] == 18
test_elementwise_multiply()For optional practice with nested records, use unpacking and a nested search to keep the best grade paired with the student who received it.
6.1.Q3 Best student finder
Table 4 contains student IDs and scores for Programming, Probability, and Calculus. The final two rows illustrate the information that the function must determine for every subject.
| Student ID / output | Programming | Probability | Calculus |
|---|---|---|---|
| s01 | 85 | 75 | 90 |
| s02 | 70 | 80 | 85 |
| s03 | 75 | 70 | 80 |
| Best grade | |||
| Best student |
To analyze student performance and help select next year's teaching assistants, develop a function named find_best_students_per_subject. It must take a list in which each item contains a student ID and a list of subject scores. Use while loops to determine the best grade and student for each subject, and return one (best_grade, student_id) tuple per subject, in the original subject order. If students tie, return the first one in the input list.
Assume the input contains at least one student and at least one subject. Every student has the same number of integer scores, with subject positions in the same order. Leave the input and its score lists unchanged.
Present your algorithm in Python, pseudocode, or a flowchart using while loops. The Python starter in Listing 13 preserves the supplied scores and checks the required result. Add a check for a tie.
For example, in Table 4, the best Programming grade is 85 and the student who obtained it is s01. Therefore, the first returned tuple must be (85, "s01").
def find_best_students_per_subject(
student_scores: list[tuple[str, list[int]]],
) -> list[tuple[int, str]]:
# Add code here!
pass
scores = [
("s01", [85, 75, 90]),
("s02", [70, 80, 85]),
("s03", [75, 70, 80]),
]
assert find_best_students_per_subject(scores) == [
(85, "s01"),
(80, "s02"),
(90, "s01"),
]