Collections: Beginner
Reading from a tuple works the way list reading does: index 0 is the first item, -1 is the last, and an item inside a nested tuple takes a second index. The run record holds 4 items of 3 types: 2 strings, an integer and a tuple.
The comma builds the tuple
The comma is what tells Python to build a tuple. Parentheses only group, as they do in (2 + 3) * 4. That has 2 consequences: values separated by commas with no parentheses around them are already a tuple, and a single value wrapped in parentheses stays that value. A tuple with 1 item needs a trailing comma.
Python prints a one-item tuple with its trailing comma, (5,), so the printed form shows which kind you are holding.
Which line creates a tuple holding exactly 1 item, the number 7?
A tuple refuses every change
A list lets you replace, append and remove items. A tuple has no method that changes it, and assigning to one of its indexes stops the program with a TypeError at the line that attempted the edit.
The first print runs, then line 3 raises TypeError: 'tuple' object does not support item assignment, and after: never prints. That final line of the error names the problem. Calling splits.append(280) also stops the program, with AttributeError: 'tuple' object has no attribute 'append', because tuples have no append method.
To correct a value, build a new tuple and assign it to the name, as in point = (38.7223, -9.1400). That line points the name point at a brand-new tuple, and the original was never edited. This is the safety a tuple buys: a record's values change only where code writes out a whole new record on purpose.
- Square brackets: [312, 298]
- Items can be replaced, appended and removed
- Suits data that grows, such as today's uploads
- Refused as a dictionary key or set member
- Commas, usually in parentheses: (312, 298)
- Fixed at creation; an edit raises an error
- Suits a record of known shape, such as a GPS point
- Accepted as a key or set member when its contents are
Tuples as dictionary keys
The Sets lesson showed that a set refuses a list as a member because a list can change. Dictionary keys follow the same rule, and a tuple of strings and numbers passes it. That makes a tuple the right key when no single value is unique: a runner logs many dates and a date holds many runners, but the pair (runner, date) picks out a single day's distance.
("r_4821", [312, 298]) cannot be a dictionary key, and the list inside it can still be appended to. Build records from strings, numbers and other tuples.Reading a record by index means remembering that position 2 is the distance and position 3 is the splits. Unpacking gives each position a name instead. Write several names on the left of =, separated by commas, and Python assigns the tuple's items to them in order: first item to first name, second to second.
The reverse mismatch, more names than items, raises ValueError: not enough values to unpack (expected 3, got 2). In both cases no name gets a value, and the program stops on that line.
A starred name takes what is left
Put * in front of 1 name and it becomes a collector. Python fills the plain names first, those before the star from the left end and those after it from the right end, then gives every remaining item to the starred name as a list. The 7-item row becomes readable in a single line: run_id, date, *splits = row gives 2 named fields and a list of however many splits the run recorded.
1splits = (312, 298, 305, 291, 284)23first, *middle, last = splits4print(first)5print(middle)6print(last)78warmup, *rest = splits9print(warmup, rest)1011*early, final = splits12print(early, final)
Plain names fill from the ends; the starred name collects what remains, as a list.
| Target, for a 5-item tuple | Plain names get | Starred name gets |
|---|---|---|
| `a, b, c, d, e` | all 5 items | no starred name |
| `a, *rest` | `a` = 312 | `rest` = [298, 305, 291, 284] |
| `*rest, e` | `e` = 284 | `rest` = [312, 298, 305, 291] |
| `a, *rest, e` | `a` = 312, `e` = 284 | `rest` = [298, 305, 291] |
| `a, b, c, d, e, *rest` | all 5 items | `rest` = [] |
What does this program print?
Where a starred name still fails
A star relaxes the count, but every plain name still needs an item. a, b, *rest = (1,) stops with ValueError: not enough values to unpack (expected at least 2, got 1). The words at least in the message tell you a starred name was involved.
An assignment can hold 1 starred name at most. With 2, Python would have no way to decide how to divide the leftovers between them, so *a, *b = (1, 2, 3) is refused with a SyntaxError before any line of the program runs.
Skipping fields with an underscore
Often only a few fields matter. By convention the name _ marks a value you are deliberately not using, and *_ discards any number of them. Python treats _ like any other name; the convention tells the next reader that skipping it was on purpose.
Unpacking in a for loop header
A for loop over a list of tuples can unpack each tuple right in its header. Every pass assigns the current tuple's items with the same rules: an exact count without a star, and at least the plain names with one. Here the uploads carry different numbers of splits.
runner still reads correctly a month later, while row[0] makes the next reader look up what position 0 held.Three built-in functions answer the first questions anyone asks about a column of numbers. sum adds every item, min returns the smallest and max returns the largest. Each takes a whole collection, walks through it and returns 1 value, replacing a loop and a running variable with a single call.
The last 2 lines are the usual combinations: the spread between the slowest and fastest kilometre, and the average split. Division with / always produces a float, which is why the average prints as 298.0. Written out by hand, the work behind sum and min looks like this:
The 2 loops start differently. A total can start at 0 before any item is seen, while a minimum has to start from a real item, since no number is guaranteed to be larger than all of them. That difference decides what an empty collection does, further down.
Any collection, or loose arguments
All 3 accept anything you can loop over: a list, a tuple, a set or a range. min and max also accept 2 or more separate values, which suits picking the larger of 2 readings. sum takes the collection as its first argument, so sum(140, 152) raises a TypeError.
Strings and dictionaries compare differently
min and max work on anything that < can compare. Strings compare character by character in character-code order, where every uppercase letter sorts before every lowercase one. A dictionary passed to min or max is compared by its keys, so the answer is a key and the values play no part.
max(best_km) returns caro because c is the latest letter among the keys, though Caro ran the shortest distance. When you want the largest number, pass the numbers. The lesson on dictionary views, later in the track, shows the direct way to hand a dictionary's values to max.
Empty collections and mixed types
An empty collection splits the 3 functions apart. sum starts at 0, finds nothing to add and returns 0. min and max have no item to start from, so they raise an error. A day with no uploads is ordinary input for a daily job, so this case reaches real code.
Without default, max(todays_splits) stops the program with ValueError: max() arg is an empty sequence. The default keyword argument gives min and max a value to return when the collection is empty. Choose one that cannot be mistaken for real data, or check the length first and skip the summary. default works only with a single collection; combined with loose arguments it raises a TypeError.
Mixed types fail as well. sum adds with +, so a string among the numbers raises TypeError: unsupported operand type(s) for +: 'int' and 'str'. min and max compare with <, so min([312, "298"]) raises TypeError: '<' not supported between instances of 'str' and 'int'. A split stored as text must become a number first, which the Type Conversions lesson covers.
A daily job receives a runner's splits for a day with no uploads. What does this program print?
Counting with sum
In arithmetic Python treats True as 1 and False as 0. Summing a list of comparison results therefore counts how many were true, here the kilometres run in under 5 minutes.
- Pass default= to min and max when the input can be empty
- Check the length before computing sum(x) / len(x)
- Pass the numbers themselves to max when you want the largest number
- Don't write a running-total loop where sum does the job
- Don't feed numbers stored as text into sum, min or max
- Don't expect max(some_dict) to look at the values
abs returns how far a number lies from zero: the same number with its sign removed. It accepts integers and floats and gives back the type it received.
The distance between 2 numbers
The everyday form is abs(a - b): how far apart 2 values are, whichever is larger. Subtraction alone returns a signed answer that flips with the order you wrote, and wrapping it in abs makes the order stop mattering. Here a running watch and a map disagree about a route's elevation gain.
Tolerance checks
Two measurements from different devices rarely agree exactly, so comparing them with == fails over a 1-metre disagreement. A useful check asks whether the gap is small enough: abs(a - b) <= tolerance.
Drop the abs and the second call returns True. The difference 430 - 470 is -40, and -40 is below every positive tolerance, so a watch reading 40 metres low would pass a check it should fail.
Which expression tells whether 2 readings, a and b, are within 5 of each other in either direction?
Signed difference or size?
Removing the sign removes information. Whether a runner got faster or slower than last week lives in the sign of this_week - last_week, and how big the change was lives in its absolute value. Keep the signed number when direction matters, and take abs when only size does.
A climb profile shows both at once. Five elevation readings give a net change from the first point to the last, and a total amount of climbing and descending along the way. Only the second needs abs.
1elevation = (12, 30, 22, 41, 35)2moved = 034for i in range(1, len(elevation)):5 step = elevation[i] - elevation[i - 1]6 moved = moved + abs(step)7 print(step, moved)89net = elevation[-1] - elevation[0]10print("net:", net, "moved:", moved)
Each step keeps its sign; abs turns it into distance before it joins the total.
Average size of an error
Common mistakes
abs(a) - abs(b) and abs(a - b) are different calculations. With a = -3 and b = 4, the first is 3 - 4, which is -1; the second is abs(-7), which is 7. Only the second is a distance.
abs takes 1 number. A list of gaps needs a loop that calls abs on each item. Passing the list itself, as in abs(gaps), stops the program with TypeError: bad operand type for abs(): 'list', and a number stored as text such as "-12" fails the same way, with 'str' at the end of the message.
len returns how many items a collection holds, as an integer. The same call works on every collection in this track so far, and what counts as 1 item depends on the type.
| Value | len counts | Example | Result |
|---|---|---|---|
| string | characters, including spaces and escapes | `len("5K run")` | 6 |
| list | items | `len([312, 298])` | 2 |
| tuple | items | `len((38.7, -9.1))` | 2 |
| dict | keys, each key-value pair once | `len({"ana": 21, "ben": 42})` | 2 |
| set | distinct members after duplicates drop | `len({5, 10, 5})` | 2 |
| range | numbers it would produce | `len(range(1, 6))` | 5 |
Two of those results catch people out. The \n escape is 1 character, so the note has length 16 even though it prints on 2 lines. The runs list inside the profile holds 4 numbers, and len(profile) is still 3 because it counts the 3 keys and never looks inside a value.
What does this program print?
Only the top level counts
len counts the items directly inside a collection. A list of 3 tuples has length 3 however long the tuples are. To count everything inside, loop over the outer collection and add up the inner lengths.
Length and indexes
A sequence of length n has valid indexes from 0 to n - 1. That is why range(len(splits)) produces exactly the indexes of splits, and why splits[len(splits)] raises IndexError: tuple index out of range. The last item sits at len(splits) - 1, the same position as -1.
Counting distinct values
A set drops duplicates, so len(set(items)) counts distinct values. Comparing it with len(items) shows whether duplicates exist, a standard check on IDs that are meant to be unique.
Values with no length
A single number is 1 value, so len(1490) raises TypeError: object of type 'int' has no len(). Floats and True fail the same way. Counting the digits of a number is a question about its text form, which needs a conversion first.
Guarding a summary with len
An average divides by the length, and an empty list has length 0, so sum(x) / len(x) on it raises ZeroDivisionError: division by zero. A length check in front of the division decides what an empty input returns.
Checking a row before unpacking it
len pairs with starred assignment. The target run_id, runner, *splits needs at least 2 fields, and a row with exactly 2 unpacks to an empty splits list. This job only summarizes runs that carry at least 1 split, so it checks len(row) < 3 first, sets those rows aside and keeps going. The program combines tuples, starred assignment, len and sum.
Ben's row has no splits to summarize, so the length check sets it aside. Without the check it would unpack cleanly to an empty splits list and print r_4822 ben 0 0, a 0-second run that never happened. The other rows unpack with a starred splits list of whatever length they carry, and len and sum summarize it.
- ▸Row checks: a record with the wrong field count is set aside before unpacking.
- ▸Empty guards:
len(x) == 0skips a summary that would divide by zero. - ▸Duplicate checks:
len(items)againstlen(set(items))on IDs meant to be unique. - ▸Batch sizes: the number of records loaded is logged so the next run can compare.
> Picture the weekly training summary a Strava runner opens on Monday. Each activity from the week arrives as a tuple: activity ID, runner, then that run's kilometre splits in seconds. The job behind the summary sets aside rows with fewer than 3 fields, unpacks the rest with a starred splits list, reports total time and the fastest and slowest kilometre, flags any run whose watch elevation strays more than 15 metres from the map, and stores a single total per runner and week.
len guards the unpacking: a row with no splits is set aside, so every row that reaches the starred assignment carries at least 1 split.splits list feeds sum, min and max directly, and the length check means min and max never meet an empty list.abs turns the watch-versus-map gap into a size, so a reading 40 metres high and one 40 metres low are flagged alike.(runner, week) tuple key keeps each runner's weekly total in a single dictionary.(5,) is a one-item tuple, and any edit raises an error.sum, min and max summarize any collection; sum of nothing is 0, while min and max need default=.abs(a - b) is a distance; keep the sign when direction matters and drop it when only size does.len counts top-level items: characters, keys or distinct set members, and a plain number has no length.Tuples for fixed records, starred names for leftovers, and the built-ins that total, bound, measure and count them.
- Category
- Python
- Difficulty
- beginner
- Duration
- 34 minutes
- Challenges
- 20 hands-on challenges
Topics covered: Creating Tuples, Starred Assignment, Using min, max, sum, abs() for Absolutes, len() Across Types
Lesson Sections
- Creating Tuples (concepts: Tuples)
A tuple is an ordered sequence, like a list, that cannot change after Python builds it. You write one as values separated by commas, usually inside parentheses. Reach for a tuple when a record has a fixed shape: a GPS point is always a latitude and a longitude, and a 5K run always has 5 kilometre splits. Once the record exists, no later line of code can overwrite one of its values by accident. The comma builds the tuple Python prints a one-item tuple with its trailing comma, (5,), so the printed
- Starred Assignment (concepts: Unpacking)
Plain unpacking needs an exact match: 4 names take 4 items. Give 3 names to a 7-item row and Python stops rather than guess which items you meant. A starred name takes what is left Three rules come out of that run. The starred name always holds a list, even when the source is a tuple. The star can sit first, last or between plain names. When nothing is left over, the starred name gets an empty list and no error is raised. Where a starred name still fails Skipping fields with an underscore Unpack
- Using min, max, sum (concepts: Math Ops)
Any collection, or loose arguments Strings and dictionaries compare differently Empty collections and mixed types Counting with sum
- abs() for Absolutes (concepts: Math Ops)
The distance between 2 numbers Tolerance checks Signed difference or size? The net change is 23 metres, yet the runner moved 51 metres up and down. Adding the signed steps would cancel each descent against a climb and land on 23 again, hiding 28 metres of effort. Average size of an error The same idea measures how wrong a forecast was. A pace predictor guessed each kilometre before the run; the mean absolute error averages the size of each miss, so misses in opposite directions cannot cancel. Su
- len() Across Types (concepts: Collections)
Only the top level counts Length and indexes Counting distinct values Values with no length Guarding a summary with len Checking a row before unpacking it