← Home

055. Tuples

Immutable records for fixed-structure data

055. Tuples

Aryan’s RAM monitor stores each memory reading as a tuple so it can never be accidentally modified after capture:

import time

def take_reading():
    mem = psutil.virtual_memory()
    # (timestamp, total_gb, used_gb, percent)
    return (time.time(), mem.total / 1e9, mem.used / 1e9, mem.percent)

history = []
for _ in range(10):
    history.append(take_reading())
    time.sleep(1)

# Unpack cleanly at use site
for ts, total, used, pct in history:
    print(f"{ts:.0f}  {used:.1f}/{total:.1f} GB  ({pct}%)")

Tuples are the right container here because:

  1. The fields are positional and fixed — there will always be exactly 4.
  2. Immutability guarantees that a historical reading can never be altered.
  3. Tuples can be used as dict keys (e.g., cache[(pid, timestamp)] = rss_mb), lists cannot.

When Aryan returns (min_proc, max_proc) from a helper function, Python packs it as a tuple automatically. The caller unpacks with lo, hi = f().


💡 Fun fact: Tuples are hashable (as long as all their elements are hashable), which is why they can be used as dictionary keys. This makes tuples the natural choice for composite keys: cache[(pid, timestamp)] works, but cache[[pid, timestamp]] raises a TypeError because lists are not hashable. This hashability distinction between tuples and lists is one of the most practically important differences between the two types.

⚠️ Watch out: A one-element tuple requires a trailing comma(42,) is a tuple, but (42) is just the integer 42 in parentheses. Forgetting the comma is an extremely common mistake: point = (5) gives you an int, not a tuple, and point[0] will raise TypeError: 'int' object is not subscriptable.

🤔 Think about it: Tuples are immutable, so you can’t do point[0] = 10 to update an x-coordinate. If you have a tuple (3, 4) and want to change it to (10, 4), you have to create a new tuple. Does this immutability make tuples harder to work with, or does it actually make programs safer — and can you think of a real scenario where accidentally mutating a coordinate caused a bug?

Learning objectives

  • Create tuples with ()
  • Access tuple elements with indexing
  • Unpack tuples with multiple assignment

Key concepts

  • tuple
  • immutable
  • unpacking

Try it

Concept detail

Tuples are immutable ordered sequences. Created with (1, 2, 3) or tuple([1, 2, 3]). A one-element tuple: (42,) — the comma is required! (42) is just the integer 42.

Immutable means: no append/remove, no item assignment. But you can concatenate: (1, 2) + (3, 4) == (1, 2, 3, 4)

Unpacking: a, b = (1, 2) # basic x, *rest = (1, 2, 3, 4) # x=1, rest=[2,3,4] _, second, _ = (1, 2, 3) # discard first and third

When to use tuples vs lists: Tuple — fixed structure, positional meaning (x,y), function return values, dict keys, “this data should not change” List — variable length, homogeneous items, needs append/remove

Tuples are slightly faster than lists and signal immutability to the reader. Functions that return “two things” implicitly return a tuple: return a, b.

Solution

import math

def make_point(x, y):
    return (x, y)

def distance(p1, p2):
    return math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2)

def midpoint(p1, p2):
    return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2)

def swap(point):
    return (point[1], point[0])

Tests

def test_make_point_is_tuple():
    p = make_point(3, 4)
    assert type(p) == tuple
    assert p == (3, 4)

def test_distance_345():
    # 3-4-5 right triangle
    p1 = (0, 0)
    p2 = (3, 4)
    assert abs(distance(p1, p2) - 5.0) < 0.001

def test_distance_zero():
    assert distance((2, 3), (2, 3)) == 0.0

def test_midpoint():
    assert midpoint((0, 0), (4, 6)) == (2.0, 3.0)

def test_swap():
    assert swap((3, 7)) == (7, 3)

def test_tuple_unpacking():
    x, y = make_point(10, 20)
    assert x == 10 and y == 20

Resources