Round brackets are required in cases of ambiguity (if the tuple is part of a larger expression):
> Note that it is actually the comma which makes a tuple, not the parentheses. The parentheses are optional, except in the empty tuple case, or when they are needed to avoid syntactic ambiguity. For example, `f(a, b, c)` is a function call with three arguments, while `f((a, b, c))` is a function call with a 3-tuple as the sole argument.
```shell
>>> print(1,2,3,4,) # Calls print with 4 arguments: 1, 2, 3, and 4
>>> b_tuple = tuple(a_tuple) # If the constructor is called with a tuple for
the iterable,
>>> a_tuple is b_tuple # the tuple argument is returned.
True
```
**Accessing elements of a `tuple`:**
Elements of `tuples` are accessed and index the same way that `lists` are.
```shell
>>> my_tuple = 1, 2, 9, 16, 25
>>> print(my_tuple)
(1, 2, 9, 16, 25)
```
_Zero indexed_
```shell
>>> my_tuple[0]
1
>>> my_tuple[1]
2
>>> my_tuple[2]
9
```
_Wrap around indexing_
```shell
>>> my_tuple[-1]
25
>>> my_tuple[-2]
16
```
**Packing and Unpacking:**
The statement `t = 12345, 54321, 'hello!'` is an example of tuple packing: the values `12345`, `54321` and `'hello!'` are packed together in a tuple. The reverse operation is also possible:
```shell
>>> x, y, z = t
```
This is called, appropriately enough, sequence unpacking and works for any sequence on the right-hand side. Sequence unpacking requires that there are as many variables on the left side of the equals sign as there are elements in the sequence. Note that multiple assignment is really just a combination of tuple packing and sequence unpacking.
```shell
>>> t = 1, 2, 3 # Tuple packing.
>>> print(t)
(1, 2, 3)
>>> a, b, c = t # Sequence unpacking.
>>> print(a)
1
>>> print(b)
2
>>> print(c)
3
>>> d, e, f = 4, 5, 6 # Multiple assignment combines packing and unpacking.
>>> print(d)
4
>>> print(e)
5
>>> print(f)
6
>>> a, b = 1, 2, 3 # Multiple assignment requires each variable (right)
have a matching element (left).
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
```
**Immutable:**
`tuples` are immutable containers, guaranteeing **which** objects they contain will not change. It does **not** guarantee that the objects they contains will not change:
```shell
>>> a_list = []
>>> a_tuple = (a_list,) # A tuple (immutable) with a list (mutable) element.
>>> print(a_tuple)
([],)
>>> a_list.append("Hello campers!")
>>> print(a_tuple) # Element of the immutable is mutated.
(['Hello campers!'],)
```
**Uses:**
Functions can only return a single value, however, a heterogenuous `tuple` can be used to return multiple values from a function. One example is the built-in `enumerate` function that returns an iterable of heterogenuous `tuples`: