# Tuples

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Collections
Source: https://www.devtutorials.ai/learn/python/collections/tuples

> Immutable ordered records: the trailing-comma trap, slicing, rebuilding instead of changing, packing and unpacking, and the two tuple methods.

Think about a printed exam certificate.

It says the name, the subject and the mark. Once it is printed, nobody changes it. If something is wrong you print a new one — you do not scratch out a number and write another.

A list is a whiteboard. A **tuple** is a printed certificate: the same kind of contents, fixed once written.

Here is that idea in action — a record that refuses to be changed:

```python runnable title="A record that cannot be changed"
result = ("Asha", "Maths", 78)

print(result)
print(result[0])
print(len(result))

try:
    result[2] = 90
except TypeError as error:
    print("Refused:", error)
```

A tuple holds many values in order, like a list, but it cannot be changed after it is made.

## What You Will Learn

In this lesson, you will learn:

- How to make a tuple, including the two cases that trip everyone up
- Why immutability is useful rather than a limitation
- When to choose a tuple over a list
- How to reach items, including inside nested tuples
- How to "change" a tuple, and what that really means
- Packing and unpacking, which you have already used without knowing it

By the end of this lesson, you will be able to create tuples, explain why their fixed nature is useful, choose between a tuple and a list, reach items with indexes and slices, build a new tuple in place of a changed one, and pack and unpack values.

## Python Tuples

### Introduction to Tuples

A **tuple** is an ordered collection, like a list, that cannot be changed once created.

### Creating Tuples

There are several ways to make a tuple, and one detail in them matters more than the rest:

```python runnable title="Several ways to make a tuple"
a = ("Asha", "Maths", 78)
b = "Asha", "Maths", 78
c = tuple(["Asha", "Maths", 78])
d = tuple("Asha")

print(a)
print(b)
print(c)
print(d)
print(a == b == c)
```

**It is the commas that make a tuple, not the brackets.** The brackets are usually there for clarity, and sometimes required to avoid ambiguity.

### Empty Tuples

An empty tuple is straightforward — brackets with nothing inside:

```python runnable title="An empty tuple"
empty = ()
also_empty = tuple()

print(empty, also_empty, len(empty))
print(empty == also_empty)
```

### Single-Element Tuples

This is the one that catches everybody.

```python runnable title="A tuple of one needs a trailing comma"
one = (78,)
not_a_tuple = (78)

print(one, type(one).__name__)
print(not_a_tuple, type(not_a_tuple).__name__)
```

`(78)` is just 78 in brackets — brackets used for grouping, as in `(2 + 3) * 4`. The comma is what makes it a tuple.

```python runnable title="Where the missing comma bites"
single = (78)
print("Length of (78,):", len((78,)))

try:
    print(len(single))
except TypeError as error:
    print("Refused:", error)
```

### Tuple Characteristics

Most of a tuple's characteristics are the same as a list's, with one important exception:

<div className="tscroll">

| Characteristic | What it means |
|---|---|
| Ordered | items keep the order you put them in |
| Indexed | the first item is at 0 |
| **Immutable** | it cannot be changed after it is made |
| Duplicates allowed | the same value can appear many times |
| Any type | items need not match |
| Hashable | it can be a dictionary key or go in a set |

</div>

That last one is a real advantage a list does not have:

```python runnable expect-error title="A tuple can be a key; a list cannot"
by_position = {("Maths", 1): 78}
print(by_position[("Maths", 1)])

{["Maths", 1]: 78}
```

### Mutable vs Immutable

A tuple refuses every kind of change:

```python runnable expect-error title="A tuple refuses every change"
marks = (78, 85)

marks[0] = 90
```

```python runnable expect-error title="And it has no methods that change it"
marks = (78, 85)

marks.append(60)
```

One subtlety worth knowing: a tuple holds references, so a **mutable item inside** it can still change.

```python runnable title="A list inside a tuple can still change"
record = ("Asha", [78, 85])

record[1].append(60)

print(record)
```

The tuple still holds the same two items — the same text and the same list. The list itself changed. The tuple is immutable; its contents are not necessarily.

A tuple freezes *which* objects it holds, not what those objects contain. A list
inside a tuple can still grow and shrink, which is also why such a tuple cannot
be a dictionary key.

That is also why a tuple containing a list cannot be a dictionary key:

```python runnable expect-error title="A tuple is only hashable if its items are"
{(78, 85): "fine"}

{("Asha", [78, 85]): "not fine"}
```

### Lists vs Tuples

Since a tuple is so close to a list, here they are side by side:

<div className="tscroll">

| | List | Tuple |
|---|---|---|
| Written | `[78, 85]` | `(78, 85)` |
| Changeable | yes | no |
| Methods | eleven | two |
| As a dictionary key | no | yes, if its items are |
| Memory | more | less |
| Use for | a collection that grows or changes | a fixed record, a return value |

</div>

```python runnable title="The size difference"
import sys

as_list = [78, 85, 60]
as_tuple = (78, 85, 60)

print("list: ", sys.getsizeof(as_list), "bytes")
print("tuple:", sys.getsizeof(as_tuple), "bytes")
```

The practical rule: **a list is many of one thing; a tuple is one thing made of several parts.** A list of marks is a list. One result — name, subject, mark — is a tuple.

```python runnable title="The rule in use"
results = [
    ("Asha", "Maths", 78),
    ("Ravi", "Science", 85),
]

for name, subject, mark in results:
    print(f"{name} scored {mark} in {subject}")
```

A list of tuples: many results, each a fixed record of three parts.

## Accessing Tuple Elements

Now that you know how to make a tuple, here is how to reach what is inside one.

### Tuple Indexing

#### Positive Indexing

```python runnable title="Counting from the front"
result = ("Asha", "Maths", 78)

print(result[0])
print(result[2])
```

#### Negative Indexing

```python runnable title="Counting from the back"
result = ("Asha", "Maths", 78)

print(result[-1])
print(result[-3])
```

```python runnable expect-error title="Past the end"
result = ("Asha", "Maths")
print(result[5])
```

### Tuple Slicing

Slicing gives back a **new tuple**.

```python runnable title="Slicing a tuple"
marks = (78, 85, 60, 92, 45)

print(marks[1:3])
print(marks[:2])
print(marks[-2:])
print(marks[::2])
print(marks[::-1])
```

Slicing does not break immutability: the original is untouched and a new tuple is made.

### Accessing Nested Tuples

A tuple can hold other tuples, and you reach inside with two indexes:

```python runnable title="Tuples inside tuples"
term = (("Maths", 78), ("Science", 85))

print(term[0])
print(term[0][0])
print(term[1][1])

for subject, mark in term:
    print(subject, mark)
```

## Modifying Tuples

A tuple cannot be modified, so this section is about what to do instead — and why the restriction helps.

### Tuple Immutability

There is no way to change a tuple. That is the whole point: anything holding a reference to it knows it cannot change underneath them.

```python runnable title="Why that is useful"
SCHOOL_DAYS = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday")

def add_day(days):
    try:
        days.append("Saturday")
        return "changed it"
    except AttributeError:
        return "could not change it"

print(add_day(SCHOOL_DAYS))
print(SCHOOL_DAYS)
```

Handing a list to that function would have changed it. A tuple cannot be changed by anyone.

### Creating Modified Tuples

To "change" a tuple, build a new one.

```python runnable title="Building a changed copy"
result = ("Asha", "Maths", 78)

updated = result[:2] + (90,)
print(updated)
print(result)

as_list = list(result)
as_list[2] = 90
updated = tuple(as_list)
print(updated)
```

The second form is the one to use when several items change: convert to a list, change it, convert back.

### Concatenating Tuples

```python runnable title="Joining tuples"
term1 = (78, 85)
term2 = (60, 92)

print(term1 + term2)
print(term1 * 2)
print(term1)
```

Both build new tuples. `+=` looks like a change but is not:

```python runnable title="+= makes a new tuple"
marks = (78, 85)
first = marks

marks += (60,)

print("marks:", marks)
print("first:", first)
print("Same object?", marks is first)
```

`marks` points at a **new** tuple. The original still exists, unchanged, and `first` still holds it.

## Deleting Tuples

### Deleting an Entire Tuple

`del` can remove the whole name.

```python runnable expect-error title="Deleting the name"
marks = (78, 85)

print(marks)

del marks

print(marks)
```

### Why Individual Elements Cannot Be Deleted

One item cannot be deleted, for the same reason one cannot be changed:

```python runnable expect-error title="Deleting one item is refused"
marks = (78, 85, 60)

del marks[0]
```

Deleting an item would change the tuple, and a tuple cannot change. Build a new one instead:

```python runnable title="Removing an item by rebuilding"
marks = (78, 85, 60)

without_first = marks[1:]
without_85 = tuple(mark for mark in marks if mark != 85)

print(without_first)
print(without_85)
print(marks)
```

## Tuple Operations

The remaining operations work much as they do with lists.

### Concatenation and Repetition

```python runnable title="+ and *"
print((78, 85) + (60,))
print((0,) * 5)
```

### Membership Operators

```python runnable title="in and not in"
marks = (78, 85, 60)

print(78 in marks)
print(99 in marks)
print(99 not in marks)
```

### Iterating Through Tuples

```python runnable title="Looping over a tuple"
result = ("Asha", "Maths", 78)

for item in result:
    print(item)

for position, item in enumerate(result):
    print(position, item)
```

Looping is safe in a way it never is with a list: nothing can add or remove items partway through.

## Tuple Packing and Unpacking

One part of tuples is worth a section of its own, because it explains things you have been using all along.

### Tuple Packing

**Packing** is putting several values into one tuple. It happens whenever you write values separated by commas.

```python runnable title="Packing"
result = "Asha", "Maths", 78

print(result)
print(type(result).__name__)
```

This is why a function can appear to return several values:

```python runnable title="A function returning 'several' values"
def summarise(marks):
    return min(marks), max(marks), sum(marks)

answer = summarise([78, 85, 60])

print(answer)
print(type(answer).__name__)
```

It returned one tuple. Python packed the three values on the way out.

### Tuple Unpacking

**Unpacking** is the reverse: taking a tuple apart into separate names.

```python runnable title="Unpacking"
result = ("Asha", "Maths", 78)

name, subject, mark = result

print(name)
print(subject)
print(mark)
```

The counts must match:

```python runnable expect-error title="Too few names"
result = ("Asha", "Maths", 78)

name, subject = result
```

A `*` collects whatever is left over:

```python runnable title="Collecting the rest"
marks = (78, 85, 60, 92)

first, *rest = marks
first, *middle, last = marks

print(first, rest)
print(first, middle, last)
```

The collected part is always a **list**, even inside a tuple unpacking.

`_` is the conventional name for a value you do not want:

```python runnable title="Ignoring part of a record"
result = ("Asha", "Maths", 78)

name, _, mark = result

print(name, mark)
```

### Multiple Variable Assignment

Packing and unpacking together explain two things you have been using since lesson 2.

```python runnable title="Where multiple assignment comes from"
a, b, c = 1, 2, 3

print(a, b, c)
```

The right-hand side packs into `(1, 2, 3)`; the left-hand side unpacks it.

```python runnable title="And why swapping works"
first = 78
second = 85

first, second = second, first

print(first, second)
```

The right side is packed into a tuple **before** anything is assigned, so no temporary variable is needed and nothing is overwritten halfway.

Unpacking is what makes looping over pairs read well:

```python runnable title="Unpacking in a loop"
results = [("Asha", 78), ("Ravi", 85)]

for name, mark in results:
    print(name, mark)

for position, (name, mark) in enumerate(results, start=1):
    print(position, name, mark)
```

## Built-in Functions with Tuples

The same built-ins that work on lists work on tuples, with one surprise:

```python runnable title="The built-ins that work on a tuple"
marks = (78, 85, 60, 92)

print("len:   ", len(marks))
print("max:   ", max(marks))
print("min:   ", min(marks))
print("sum:   ", sum(marks))
print("sorted:", sorted(marks))
print("tuple: ", tuple([78, 85]))
```

`sorted()` on a tuple gives back a **list**, not a tuple:

```python runnable title="sorted gives a list"
marks = (78, 20, 85)

ordered = sorted(marks)

print(ordered, type(ordered).__name__)
print(tuple(sorted(marks)))
```

```python runnable title="zip builds tuples"
subjects = ("Maths", "Science")
marks = (78, 85)

pairs = list(zip(subjects, marks))

print(pairs)
print(type(pairs[0]).__name__)
```

Every pair `zip()` produces is a tuple, which is why unpacking in a `for` loop works so naturally.

## Tuple Methods

A tuple cannot change, so it has only two methods — the two that ask a question rather than change anything.

### count()

`count()` says how many times a value appears:

```python runnable title="count"
marks = (78, 85, 78, 60, 78)

print(marks.count(78))
print(marks.count(99))
```

### index()

`index()` says where a value first appears:

```python runnable title="index"
marks = (78, 85, 60, 85)

print(marks.index(85))
print(marks.index(85, 2))
```

```python runnable expect-error title="index raises when the value is missing"
marks = (78, 85)

print(marks.index(99))
```

```python runnable title="Checking first"
marks = (78, 85)

if 99 in marks:
    print(marks.index(99))
else:
    print("99 is not in the tuple")
```

Two methods instead of eleven is not a shortcoming. Everything a tuple leaves out is something that would change it, and not being able to change is what a tuple is for.

## Common mistakes

Before moving on, here are some of the mistakes beginners make with tuples.

Each row is one mistake. The middle column is what you will actually see on screen.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| `(78)` meant as a tuple | An `int`, and `len()` fails | Brackets alone do not make a tuple | `(78,)` with a trailing comma |
| Assigning to an item | `TypeError: 'tuple' object does not support item assignment` | Tuples are immutable | Build a new tuple |
| Calling `append()` | `AttributeError: 'tuple' object has no attribute 'append'` | A tuple has only `count` and `index` | Use a list, or rebuild |
| `del marks[0]` | `TypeError: 'tuple' object doesn't support item deletion` | Deleting would change it | `marks[1:]`, or rebuild |
| Expecting `+=` to change it | The old tuple is untouched | `+=` makes a new tuple and rebinds the name | Expect a new object |
| Expecting a nested list to be frozen | The inner list still changes | A tuple freezes its references, not its contents | Keep tuples inside tuples |
| A tuple with a list as a dict key | `TypeError: unhashable type: 'list'` | All items must be hashable | Use tuples all the way down |
| Unpacking with the wrong number of names | `ValueError: not enough values to unpack` | The counts must match | Count both sides, or use `*rest` |
| Expecting `sorted()` to give a tuple | A list comes back | `sorted()` always returns a list | `tuple(sorted(marks))` |
| Using a tuple for a growing collection | Rebuilding on every change | A tuple is for a fixed record | Use a list |

</div>

## What to Remember

- **A tuple is ordered like a list but cannot be changed after it is made.**
- **The comma makes a tuple, not the brackets.** A tuple of one needs a trailing comma: `(78,)`.
- A tuple can be a dictionary key or go in a set; a list cannot.
- A tuple freezes *which* objects it holds, not their contents — a list inside one can still change.
- There is no way to change a tuple: slice and join, or convert to a list and back.
- `+=` on a tuple builds a new tuple and moves the name to it.
- A tuple has only two methods, `count()` and `index()`, because everything else would change it.
- **Packing** happens whenever values are separated by commas; **unpacking** takes them apart.
- A function that "returns several values" returns one packed tuple.
- `a, b = b, a` works because the right side is packed before anything is assigned.
- A list is many of one thing; a tuple is one thing made of several parts.

Next: **Sets**
