Lesson 6, Bit 4: Tuples

Tuples

Tuples are used to hold together multiple objects. Think of them as similar to lists, but without the extensive functionality that the list class gives you. One major feature of tuples is that they are immutable like strings i.e. you cannot modify tuples.

Tuples are defined by specifying items separated by commas within an optional pair of parentheses.

Tuples are usually used in cases where a statement or a user-defined function can safely assume that the collection of values i.e. the tuple of values used will not change.

Fun fact: The word "tuple" comes from the names given to sequences of numbers of varying lengths: single, double, triple, quadruple, quituple, sextuple, septuple, etc.

Tuples are Immutable

A tuple is a sequence of values much like a list. The values stored in a tuple can be any type, and they are indexed by integers. The important difference is that tuples are immutable. Tuples are also comparable and hashable so we can sort lists of them and use tuples as key values in Python dictionaries.

Syntactically, a tuple is a comma-separated list of values:

t = 'a', 'b', 'c', 'd', 'e'

Although it is not necessary, it is common and a best practice to enclose tuples in parentheses to help us quickly identify tuples when we look at Python code:

t = ('a', 'b', 'c', 'd', 'e')

To create a tuple with a single element, you have to include the final comma:

Code Result
t1 = ('a',)

type(t1)
<type 'tuple'>

Without the comma Python treats ('a') as an expression with a string in parentheses that evaluates to a string:

Code Result
t2 = ('a')

type(t2)
<type 'str'>

Launch Exercise

Another way to construct a tuple is the built-in function tuple. With no argument, it creates an empty tuple:

Code Output
t = tuple()

print(t)
()

If the argument is a sequence (string, list, or tuple), the result of the call to tuple is a tuple with the elements of the sequence:

Code Output
t = tuple('lupins')

print(t)
('l', 'u', 'p', 'i', 'n', 's')

Because tuple is the name of a constructor, you should avoid using it as a variable name.

Most list operators also work on tuples. The bracket operator indexes an element:

Code Output
t = ('a', 'b', 'c', 'd', 'e')

print(t[0])
a

And the slice operator selects a range of elements.

Code Output
t = ('a', 'b', 'c', 'd', 'e')

print(t[1:3])
('b', 'c')

Launch Exercise

But if you try to modify one of the elements of the tuple, you get an error:

Code Output
t = ('a', 'b', 'c', 'd', 'e')

t[0] = 'A'
TypeError: 'tuple' object does not support item assignment

You can't modify the elements of a tuple, but you can replace one tuple with another:

Code Output
t = ('a', 'b', 'c', 'd', 'e')

t = ('A',) + t[1:]

print(t)
('A', 'b', 'c', 'd', 'e')

Comparing Tuples

The comparison operators work with tuples and other sequences. Python starts by comparing the first element from each sequence. If they are equal, it goes on to the next element, and so on, until it finds elements that differ. Subsequent elements are not considered (even if they are really big).

Code Result
(0, 1, 2) < (0, 3, 4) True
(0, 1, 2000000) < (0, 3, 4) True

The sort function works the same way. It sorts primarily by first element, but in the case of a tie, it sorts by second element, and so on.

This feature lends itself to a pattern called DSU for:

Decorate: a sequence by building a list of tuples with one or more sort keys preceding the elements from the sequence,

Sort: the list of tuples using the Python built-in sort, and

Undecorate: by extracting the sorted elements of the sequence.

For example, suppose you have a list of words and you want to sort them from longest to shortest:

txt = 'Tis but a flesh wound'

words = txt.split()

t = list()

for word in words:
    t.append((len(word), word))

t.sort(reverse=True)

res = list()

for length, word in t:
    res.append(word)

print(res)
Code Output Notes
txt = 'Tis but a flesh wound' None

We create the string txt.

words = txt.split() None

The split method uses the default argument to create a list of individual words.

t = list() None

We create an empty list called t.

for word in words:
    t.append((len(word), word))
None

Iterate through the list words.  Append to the list t a tuple which contains the length of each word and the word itself.

t.sort(reverse=True) None

sort compares the first element (length) first, and only considers the second element to break ties. The keyword argument reverse=True tells sort to go in decreasing order.

res = list() None

We create an empty list called res.

for length, word in t:
    res.append(word)
None

The second loop traverses the list of tuples and builds a list of words in descending order of length. The four-character words are sorted in reverse alphabetical order, so "what" appears before "soft" in the following list.

print(res) ['wound', 'flesh', 'but', 'Tis', 'a']

We display the resulting list.

Tuple Assignment

One of the unique syntactic features of the Python language is the ability to have a tuple on the left side of an assignment statement. This allows you to assign more than one variable at a time when the left side is a sequence.

In this example we have a two-element list (which is a sequence) and assign the first and second elements of the sequence to the variables x and y in a single statement.

m = ['have', 'fun']

x, y = m
Code Output
print(x) have
print(y) fun

It is not magic, Python roughly translates the tuple assignment syntax to be the following:

m = ['have', 'fun']

x = m[0]

y = m[1]
Code Output
print(x) have
print(y) fun

It should be noted that Python does not translate the syntax literally. For example, if you try this with a dictionary, it will not work as you might expect.

Stylistically when we use a tuple on the left side of the assignment statement, we omit the parentheses, but the following is an equally valid syntax:

m = ['have', 'fun']

(x, y) = m
Code Output
print(x) have
print(y) fun

A particularly clever application of tuple assignment allows us to swap the values of two variables in a single statement:

a, b = b, a

Both sides of this statement are tuples, but the left side is a tuple of variables; the right side is a tuple of expressions. Each value on the right side is assigned to its respective variable on the left side. All the expressions on the right side are evaluated before any of the assignments.

The number of variables on the left and the number of values on the right must be the same:

Code Result
a, b = 1, 2, 3 ValueError: too many values to unpack (expected 2)

More generally, the right side can be any kind of sequence (string, list, or tuple). For example, to split an email address into a user name and a domain, you could write:

addr = 'monty@python.org'

uname, domain = addr.split('@')

The return value from split is a list with two elements; the first element is assigned to uname, the second to domain.

Code Output
print(uname) monty
print(domain) python.org