Skip to content
elephantoo

Tuples & sets

Lesson 9 of 38 13 min read

Immutable tuples, unpacking and NamedTuple; sets for uniqueness, fast membership and set algebra.


Lists are the default collection, but Python offers two other built-ins that often fit a problem better. Tuples are ordered and immutable — perfect for fixed records like coordinates or database rows. Sets are unordered collections of unique items with lightning-fast membership tests and mathematical operations.

Tuples: fixed, ordered records#

Python
point = (3, 4)
rgb = (255, 128, 0)
person = ("Ada", "Lovelace", 1815)
single = ("only",)        # the trailing comma makes it a tuple
empty = ()
also_a_tuple = 1, 2, 3    # parentheses are optional

print(point[0], person[-1], len(rgb))
print(type(single), type(("only")))
print(also_a_tuple)
Output
3 1815 3
<class 'tuple'> <class 'str'>
(1, 2, 3)

Tuples support indexing, slicing, in, len, count and index — everything lists do except changing contents:

Python
point = (3, 4)
point[0] = 10
Output
TypeError: 'tuple' object does not support item assignment

Why use an immutable sequence?

  • Safety — a tuple passed to a function can't be changed behind your back.
  • Meaning — a tuple says "this is one record with a fixed structure"; a list says "a collection of similar things that may grow".
  • Hashability — tuples of immutable values can be dictionary keys and set members; lists can't.
Python
distances = {("Delhi", "Mumbai"): 1400, ("Delhi", "Jaipur"): 280}
print(distances[("Delhi", "Jaipur")])
Output
280

Tuple unpacking#

Unpacking is where tuples really shine. You've already used it for swapping and multiple return values:

Python
name, surname, year = ("Grace", "Hopper", 1906)
print(f"{name} {surname} was born in {year}")

def min_max(values):
    return min(values), max(values)    # returns a tuple

low, high = min_max([7, 2, 9, 4])
print(low, high)

head, *tail = (1, 2, 3, 4)
print(head, tail)
Output
Grace Hopper was born in 1906
2 9
1 [2, 3, 4]

Unpacking also works in loops, which is why iterating over dict.items() or enumerate() feels so natural:

Python
points = [(0, 0), (3, 4), (6, 8)]
for x, y in points:
    print(f"({x}, {y}) is {(x**2 + y**2) ** 0.5:.1f} from the origin")
Output
(0, 0) is 0.0 from the origin
(3, 4) is 5.0 from the origin
(6, 8) is 10.0 from the origin

Use _ for values you don't need: _, surname, _ = person.

Named tuples: records with field names#

point[0] doesn't say much. collections.namedtuple (or its typed cousin typing.NamedTuple) gives tuple fields names while keeping them immutable and lightweight:

Python
from typing import NamedTuple


class Point(NamedTuple):
    x: float
    y: float


p = Point(3, 4)
print(p)
print(p.x, p[1])
x, y = p                   # still unpacks like a tuple
print(p._replace(x=10))    # "modify" by creating a new one
Output
Point(x=3, y=4)
3 4
Point(x=10, y=4)

For richer records with defaults and methods you'll later use dataclasses.

Sets: unique, unordered items#

A set stores each value at most once. Create one with braces or set():

Python
tags = {"python", "web", "python", "api"}
print(tags)               # duplicates vanish (order is not guaranteed)
print(len(tags))

empty = set()             # NOT {} — that's an empty dict!
from_list = set([3, 1, 3, 2, 1])
print(sorted(from_list))
Output
{'python', 'web', 'api'}
3
[1, 2, 3]

Sets are unordered: you can't index them (tags[0] is an error), and printing may show items in any order. If you need a stable order, sort them: sorted(tags).

Adding and removing

Python
skills = {"python", "sql"}
skills.add("git")
skills.update(["docker", "linux"])
skills.discard("java")     # no error if missing
skills.remove("sql")       # KeyError if missing
print(sorted(skills))
print("git" in skills)
Output
['docker', 'git', 'linux', 'python']
True

Fast membership tests

Checking x in some_list scans items one by one. A set uses hashing, so x in some_set takes about the same time whether the set holds ten items or ten million. Whenever you check membership repeatedly, convert to a set first:

Python
banned = {"spam", "scam", "phish"}
words = "this is not spam or a phish attempt".split()
flagged = [w for w in words if w in banned]
print(flagged)
Output
['spam', 'phish']

Set algebra

Sets support the operations you know from maths, as operators or methods:

Python
python_devs = {"Ada", "Grace", "Linus", "Guido"}
java_devs = {"James", "Grace", "Linus"}

print(sorted(python_devs & java_devs))   # intersection: both
print(sorted(python_devs | java_devs))   # union: either
print(sorted(python_devs - java_devs))   # difference: python only
print(sorted(python_devs ^ java_devs))   # symmetric difference: exactly one
print({"Ada"} <= python_devs)            # subset?
print(python_devs.isdisjoint({"Bjarne"}))
Output
['Grace', 'Linus']
['Ada', 'Grace', 'Guido', 'James', 'Linus']
['Ada', 'Guido']
['Ada', 'Guido', 'James']
True
True

Removing duplicates while keeping order

list(set(items)) removes duplicates but scrambles the order. To keep the first occurrence of each item in order, use dict.fromkeys (dicts preserve insertion order):

Python
emails = ["a@x.com", "b@x.com", "a@x.com", "c@x.com", "b@x.com"]
unique_in_order = list(dict.fromkeys(emails))
print(unique_in_order)
Output
['a@x.com', 'b@x.com', 'c@x.com']

Frozen sets

A frozenset is an immutable set, so it can be a dict key or live inside another set: frozenset({"read", "write"}).

Hashability in one paragraph#

Sets and dict keys rely on a value's hash — a number computed from its contents. If the contents could change, the hash would change and Python would lose track of the item. That's why only hashable (effectively immutable) values are allowed: numbers, strings, tuples of hashables, frozensets. Lists, dicts and sets are not:

Python
places = {["Delhi", "Mumbai"]}
Output
TypeError: unhashable type: 'list'

Choosing the right collection#

NeedUse
Ordered items you'll add to or changelist
A fixed record, or a dict key made of several valuestuple / NamedTuple
Unique items, fast in checks, set mathsset
Look up values by a keydict (next lesson)

Common mistakes#

  • (5) is not a tuple — write (5,).
  • {} is an empty dict, not an empty set — use set().
  • Relying on set order — sort when you need an order.
  • Trying to index a set — convert to a list or sorted list first.

What's next#

The last and arguably most important built-in collection is the dictionary: fast lookups by key, and the backbone of JSON, configuration and countless programs.

Check your understanding

Quick quiz

0/3 answered
  1. 1.How do you create a tuple containing just the number 5?

  2. 2.What does {1, 2, 3} & {2, 3, 4} return?

  3. 3.Why can't you put a list inside a set?

Finished reading?

Mark this lesson complete to track your progress.