Tuple of Tuples in Python (With Examples)

A tuple of tuples in Python is a tuple whose items are themselves tuples, so you get rows of fixed values you can’t change. You build one with nested parentheses and read it with two indexes:

cities = (("Austin", "TX"), ("Denver", "CO"))
cities[1][0]     # 'Denver'

These examples use a small table of US cities, and the screenshots come from Python 3.12.5 running in a Windows Command Prompt.

I’ll cover creating and indexing nested tuples, looping with unpacking, converting to and from lists, why they’re hashable, and sorting and flattening them.

How to create a tuple of tuples in Python

Write each row as its own tuple and wrap them all in an outer pair of parentheses. The first index picks the row, the second picks the field:

cities = (
    ("Austin", "TX", 979_882),
    ("Denver", "CO", 716_577),
    ("Boston", "MA", 653_833),
)

print(cities[1])          # the second inner tuple
print(cities[1][0])       # first item of the second tuple
print(cities[-1][2])      # last city's population
print(len(cities), "rows,", len(cities[0]), "fields each")

one_row = (("Austin", "TX"),)    # the trailing comma makes it a tuple of tuples
not_nested = (("Austin", "TX"))  # without it, the outer parentheses do nothing
print(len(one_row), len(not_nested))

Output:

('Denver', 'CO', 716577)
Denver
653833
3 rows, 3 fields each
1 2
Command Prompt output of a Python tuple of tuples holding US cities, indexed by row and field, plus the single-row trailing comma case
cities[1][0] is Denver. The last line shows why a one-row tuple needs a trailing comma.

That last check trips people up. (("Austin", "TX")) is just a plain two-item tuple, because parentheses alone don’t make a tuple; the comma does.

Looping through a tuple of tuples

Because every row has the same shape, you can unpack each inner tuple straight into named variables in the for line:

cities = (
    ("Austin", "TX", 979_882),
    ("Denver", "CO", 716_577),
    ("Boston", "MA", 653_833),
)

for city, state, population in cities:
    print(f"{city:<8} {state}  {population:>9,}")

for i, (city, state, _) in enumerate(cities, start=1):
    print(i, f"{city}, {state}")

Output:

Austin   TX    979,882
Denver   CO    716,577
Boston   MA    653,833
1 Austin, TX
2 Denver, CO
3 Boston, MA
Command Prompt output of a Python for loop unpacking each city, state and population from a tuple of tuples
Unpacking reads far better than row[0], row[1] and row[2].

Use _ for a field you don’t need. The Python for loop guide covers enumerate() and unpacking in more depth.

Converting a list of lists to a tuple of tuples

Data often arrives as a list of lists, from a CSV file or JSON. Converting each row with tuple() freezes it, and the reverse gives you something you can edit:

rows = [["Austin", "TX"], ["Denver", "CO"]]       # a list of lists, e.g. from a CSV

frozen = tuple(tuple(row) for row in rows)
print(frozen)

back = [list(row) for row in frozen]               # and back again when you need to edit
back[0][0] = "Dallas"
print(back)

pairs = (("TX", 0.0625), ("CO", 0.029))
print(dict(pairs))                                 # two-item tuples convert straight to a dict

Output:

(('Austin', 'TX'), ('Denver', 'CO'))
[['Dallas', 'TX'], ['Denver', 'CO']]
{'TX': 0.0625, 'CO': 0.029}

Tuples of two-item tuples are also exactly what dict() expects, which is handy for lookup tables like state sales-tax rates. Converting a dictionary to a list of tuples goes the other way.

Why a tuple of tuples can’t be changed

Neither the outer tuple nor the inner ones support item assignment. That’s the point: it also makes the whole structure hashable, so it can be a dictionary key or a set member:

grid = ((1, 2), (3, 4))

try:
    grid[0][1] = 99
except TypeError as err:
    print("TypeError:", err)

lookup = {grid: "2x2 block"}           # hashable, so it works as a dict key
print(lookup[((1, 2), (3, 4))])

mixed = ((1, 2), [3, 4])                # one inner list spoils it
try:
    hash(mixed)
except TypeError as err:
    print("TypeError:", err)

Output:

TypeError: 'tuple' object does not support item assignment
2x2 block
TypeError: unhashable type: 'list'
Command Prompt output showing a Python tuple of tuples rejecting item assignment, working as a dict key, and an inner list making it unhashable
Assignment fails, the nested tuple works as a key, and a single inner list makes the whole thing unhashable.

Immutability only goes as deep as the tuples do. Put a list inside and that list can still change, so the outer tuple loses its hash. Mutable vs immutable in Python explains the rule.

Sorting and searching a tuple of tuples

sorted(), max() and in all work on nested tuples. A key function tells them which field to look at:

cities = (
    ("Austin", "TX", 979_882),
    ("Denver", "CO", 716_577),
    ("Boston", "MA", 653_833),
)

by_size = sorted(cities, key=lambda row: row[2], reverse=True)
print(by_size[0][0], "is the largest")

print(max(cities, key=lambda row: row[2])[0])
print(("Denver", "CO", 716_577) in cities)
print([city for city, state, _ in cities if state == "MA"])

Output:

Austin is the largest
Austin
True
['Boston']

sorted() returns a list, since a tuple can’t be reordered in place. Wrap it in tuple() if you want a tuple back. Sorting a list of tuples has more key examples.

How do you flatten a tuple of tuples?

A nested comprehension or itertools.chain turns the rows into one flat sequence. zip(*grid) transposes it instead:

from itertools import chain

grid = ((1, 2, 3), (4, 5, 6))

print([n for row in grid for n in row])
print(tuple(chain.from_iterable(grid)))
print(sum(sum(row) for row in grid))
print(tuple(zip(*grid)))            # swap rows and columns

Output:

[1, 2, 3, 4, 5, 6]
(1, 2, 3, 4, 5, 6)
21
((1, 4), (2, 5), (3, 6))
Command Prompt output flattening a Python tuple of tuples with a comprehension and itertools chain, summing it and transposing it with zip
Flattened, summed and transposed: all one-liners on a tuple of tuples.

Tuple of tuples vs list of lists

Tuple of tuplesList of lists
Change a valueNoYes
Use as a dict key or in a setYesNo
Good forFixed reference data, coordinates, lookup tablesData you’ll edit or append to
Converttuple(map(tuple, rows))[list(r) for r in rows]

I reach for a tuple of tuples when the data shouldn’t change while the program runs, like a list of states and their tax rates. If you’ll be appending or editing rows, a list of lists is the better fit.

Keep going with tuples:

Frequently asked questions

What is a tuple of tuples in Python?

A tuple whose items are tuples, such as ((1, 2), (3, 4)). It works like a small, read-only table of rows.

How do I access an element in a tuple of tuples?

Use two indexes: the first picks the inner tuple and the second picks the item, so t[1][0] is the first item of the second row.

How do I make a tuple with a single tuple inside?

Add a trailing comma: (("Austin", "TX"),). Without the comma the outer parentheses are ignored.

Can I change a value in a tuple of tuples?

No. Convert it to a list of lists, change the value, and convert it back with tuple(tuple(r) for r in rows).

Is a tuple of tuples hashable?

Yes, as long as every inner item is hashable. That means it can be a dictionary key or go in a set. The Python tutorial on tuples covers the basics.