Tuples

Tuple Basics

Tuples are basically immutable lists. They’re ordered groups of items, just like lists, but they cannot be modified. We use parentheses to indicate we are making a tuple and not a list (or other object).

>>> time = (10, 30)
>>> time
(10, 30)

We can see that if we try to change our tuple, an exception is raised because, just like strings, tuples are immutable:

>>> time[0] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Parentheses are usually optional when making a tuple:

>>> coordinates = 3, 4, 5
>>> coordinates
(3, 4, 5)

You can make an empty tuple with empty parentheses:

>>> empty = ()
>>> empty
()
>>> type(empty)
<class 'tuple'>

Any guesses what a single item tuple would look like?

Wrapping a single element in parentheses does not make a tuple:

>>> numbers = (3)
>>> numbers
3

This is because the system interprets the parentheses around the single element as being a precedence grouping such as we might use in arithmetic expressions, for example, 2 * (x + 1).

Adding a trailing comma in the parentheses will make it a tuple:

>>> numbers = (3,)
>>> numbers
(3,)

Again we can leave off the parentheses here if we want because there is no ambiguity:

>>> numbers = 3,
>>> numbers
(3,)
>>> type(numbers)
<class 'tuple'>

So what are tuples good for?

  • heterogenous collections (like structs in C)
  • immutable collections (often of known length)
  • used for returning multiple values from a function

Tuple Unpacking

Tuples are also useful for unpacking data. In other words, we can use it to make multiple assignments with only one statement.

Multiple assignment in Python uses a tuple notation.

>>> x, y = 4, 5
>>> x
4
>>> y
5
>>> x, y
(4, 5)

Parenthesis are optional so all of these work also:

>>> (x, y) = 4, 5
>>> x, y = (4, 5)
>>> (x, y) = (4, 5)

The left side of the assignment must have the same number of variables as the number of elements of the tuple on the right. If they don’t match, an exception will be raised:

>>> coordinates = 3, 4, 5
>>> x, y = coordinates
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)

Multiple-assignment allows variable swapping. This isn’t something you’d normally see in code, but it’s interesting nonetheless:

>>> x, y = 10, 20
>>> x, y = y, x
>>> x
20
>>> y
10

As discussed last time, tuples are also used for returning multiple values from a function.

>>> def split_name(name):
...     first, last = name.rsplit(" ", 1)
...     return first, last
...
>>> first_name, last_name = split_name("Trey Hunner")
>>> first_name
'Trey'
>>> last_name
'Hunner'
>>> split_name("Trey Hunner")
('Trey', 'Hunner')

Tuples & Truthiness

Tuples evaluate to falsey if they are empty and truthy otherwise:

>>> numbers = ()
>>> if numbers:
...     print("Non-empty")
...
>>> if not numbers:
...     print("empty")
...
empty

List & Tuple Arithmetic

We learned how the addition and multiplication operators work on strings last time. These same operators also work on lists and tuples.

Addition on tuples:

>>> numbers = (5, 8)
>>> more_numbers = numbers + (7, 9)
>>> more_numbers
(5, 8, 7, 9)

Addition on lists:

>>> [1, 2] + [3, 4]
[1, 2, 3, 4]

Multiplication with tuples:

>>> (0, 1) * 2
(0, 1, 0, 1)

We can use multiplication on lists to initialize lots of elements with the same value.

For example we can make a list with 10 zeroes in it like this:

>>> my_list = [0] * 10
>>> my_list
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

List & Tuple Comparisons

Tuples and lists both use deep equality. Two tuples are equal when they have the same number of items and the item at each index in the tuples is equal.

>>> p1 = (1, 1, 1)
>>> p2 = (1, 1, 1)
>>> p1 == p2
True

Tuples can also be compared. Tuple comparisons work by comparing each element by index.

>>> (0, 0) < (0, 1)
True
>>> (0, 1) < (1, 0)
True
>>> (1, 1) < (1, 0)
False
>>> (0, 0) < (0, 0, 0)
True

Errors can occur when comparing values between tuples:

>>> (0, 0) < ('a', 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()

This does work if items at the same index can all be compared:

>>> (0, 'b') < (0, 'a')
False

Lists can be compared in the same way as tuples:

>>> [1, 2] < [2, 1]
True

Note that tuples and lists are running these comparison checks on each element. So we can achieve deep equality on nested structures like this:

>>> [(0, 0), (0, 1)] < [(0, 0), (0, 0)]
False
>>> [(0, 0), (0, 1)] < [(0, 0), (1, 0)]
True

Tuple Exercises

Oldest

Make function get_oldest that accepts two dates in MM/DD/YYYY format and returns the date that is earliest.

>>> get_oldest("01/27/1832", "01/27/1756")
'01/27/1756'
>>> get_oldest("02/29/1972", "12/21/1946")
'12/21/1946'
>>> get_oldest("03/24/1946", "02/21/1946")
'02/21/1946'

Sort Names

The built-in sorted function accepts a key argument that determines how values should be sorted in a given iterable.

Let’s use this function to sort a list of names. Names should be sorted by last name followed by first name (when the last names are equal).

Write a function name_key that accepts a first-last name tuple and returns a last-first name tuple:

>>> suzanne = ("Suzanne", "Smith")
>>> michael = ("Michael", "Gambino")
>>> name_key(michael)
('Gambino', 'Michael')
>>> name_key(suzanne)
('Smith', 'Suzanne')

Now write a function sort_names that accepts a list of first-last name tuples and returns a sorted list (using sorted with key).

>>> evelyn = ("Evelyn", "Moore")
>>> jill = ("Jill", "Moore")
>>> tanya = ("Tanya", "Jackson")
>>> ben = ("Ben", "Speigel")
>>> names = [tanya, evelyn, jill]
>>> sort_names(names)
[('Tanya', 'Jackson'), ('Evelyn', 'Moore'), ('Jill', 'Moore')]