Python Basics #9 — Tuples

7 min read

In today’s lesson we’ll cover tuples.

A tuple is a data type very similar to the list we covered in the previous lesson. So similar, in fact, that most people understand the differences between lists and tuples but aren’t sure when to use one over the other.

Let’s look at how tuples differ from lists, and when you should reach for a tuple instead of a list.

First, how to define a tuple.

Lists are defined with square brackets, but tuples are defined with parentheses. Let’s see an example.

Define a tuple using parentheses:

>>> t1 = (1, 2, 3)
>>> t1
(1, 2, 3)

Use the type function to confirm the type:

>>> type(t1)
<class 'tuple'>

Confirmed — it’s a tuple.

You can also define a tuple without parentheses:

>>> t2 = 1, 2, 3
>>> t2
(1, 2, 3)

Check the type again:

>>> type(t2)
<class 'tuple'>

Same result — a tuple was created.

There’s one thing to watch out for when defining tuples (unlike lists): when a tuple has only one item, you must put a trailing comma after it.

First, define a list with a single item:

>>> l = ['one']
>>> l
['one']

The list is fine. Let’s see what happens with a tuple:

>>> t = ('one')
>>> t
'one'

Notice anything strange? The parentheses are gone.

Let’s check the type:

>>> type(t)
<class 'str'>

A string was created instead of a tuple. As I mentioned, when defining a tuple with only one item you must include a trailing comma even though there’s no next item:

>>> t = ('one',)
>>> t
('one',)

Now the parentheses are visible. Let’s confirm the type:

>>> type(t)
<class 'tuple'>

I said tuples are very similar to lists. Let’s see how.

Tuples, like lists, are ordered sequence types. So you can use indexing and slicing on them.

Define a tuple and print the first item by indexing:

>>> t = 1, 2, 3, 4, 5
>>> t[0]
1

Slice the first 3 items:

>>> t[:3]
(1, 2, 3)

Tuples are also iterable objects (like lists), so you can use a for loop.

Use iter to confirm the tuple is iterable:

>>> iter(t)
<tuple_iterator object at 0x000002DBC4985640>

Confirmed. Now use a for loop:

>>> for i in t:
...     print(i)
...
1
2
3
4
5

Now let’s look at how tuples and lists differ.

Difference 1 #

Lists allow you to change the value of an item once defined; tuples do not. Think of tuples as similar to constants in C, C++, or Go.

First, define a list and change an item’s value:

>>> my_list = [1, 2, 3]
>>> my_list[0] = 'one'
>>> my_list
['one', 2, 3]

Now define a tuple and try to change the first item:

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

A TypeError says the tuple object does not support item assignment.

Difference 2 #

Lists let you add or remove items. Tuples don’t — once defined, a tuple’s length in memory is fixed, so you cannot add new items or remove existing ones.

Define a list and add a new item:

>>> my_list = [1, 2, 3]
>>> my_list.append(4)
>>> my_list
[1, 2, 3, 4]

The new item 4 was added.

Now remove the item we added:

>>> my_list.remove(4)
>>> my_list
[1, 2, 3]

The new item is gone.

Note: remove takes the value to remove, not an index. If you want to remove by index, use pop instead.

Now define a tuple and try to add a new item:

>>> my_tuple = 1, 2, 3
>>> my_tuple.append(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'

The error says the tuple object has no append attribute. Tuples don’t have append or remove because items can’t be added or removed.

So what methods does the tuple class have? Let’s check with help:

>>> help(tuple)
Help on class tuple in module builtins:

class tuple(object)
|  tuple(iterable=(), /)
|
|  Built-in immutable sequence.
|
|  If no argument is given, the constructor returns an empty tuple.
|  If iterable is specified the tuple is initialized from iterable's items.
|
|  If the argument is a tuple, the return value is the same object.
|
|  Built-in subclasses:
|      asyncgen_hooks
|      UnraisableHookArgs
|
|  Methods defined here:
|
|  __add__(self, value, /)
|      Return self+value.
|
|  __contains__(self, key, /)
|      Return key in self.
|
|  __eq__(self, value, /)
|      Return self==value.
|
|  __ge__(self, value, /)
|      Return self>=value.
|
|  __getattribute__(self, name, /)
|      Return getattr(self, name).
|
|  __getitem__(self, key, /)
|      Return self[key].
|
|  __getnewargs__(self, /)
|
|  __gt__(self, value, /)
|      Return self>value.
|
|  __hash__(self, /)
|      Return hash(self).
|
|  __iter__(self, /)
|      Implement iter(self).
|
|  __le__(self, value, /)
|      Return self<=value.
|
|  __len__(self, /)
|      Return len(self).
|
|  __lt__(self, value, /)
|      Return self<value.
|
|  __mul__(self, value, /)
|      Return self*value.
|
|  __ne__(self, value, /)
|      Return self!=value.
|
|  __repr__(self, /)
|      Return repr(self).
|
|  __rmul__(self, value, /)
|      Return value*self.
|
|  count(self, value, /)
|      Return number of occurrences of value.
|
|  index(self, value, start=0, stop=9223372036854775807, /)
|      Return first index of value.
|
|      Raises ValueError if the value is not present.
|
|  ----------------------------------------------------------------------
|  Class methods defined here:
|
|  __class_getitem__(...) from builtins.type
|      See PEP 585
|
|  ----------------------------------------------------------------------
|  Static methods defined here:
|
|  __new__(*args, **kwargs) from builtins.type
|      Create and return a new object.  See help(type) for accurate signature.

At the top, you’ll see the important note: tuple is a built-in immutable sequence. At the bottom, only count and index methods are defined.

Let’s use both. First, count to find how many items in a tuple have the same value:

>>> t = 1, 2, 2, 2, 3
>>> t.count(2)
3

There are 3 items with value 2.

Now index to find the index of an item with value 3:

>>> t.index(3)
4

The item with value 3 is at index 4.

Difference 3 #

Tuples use less memory than lists. Let’s store the same items in both and compare:

my_list = ['one', 1, 1.2345, True]
my_tuple = 'one', 1, 1.2345, True

Memory used by the list:

>>> import sys
>>> sys.getsizeof(my_list)
120

Memory used by the tuple:

>>> sys.getsizeof(my_tuple)
72

Even with just a few items the difference is significant. Imagine the difference with millions of items.

Difference 4 #

Tuples are created faster than lists. A tuple is stored as a single memory block, while a list stores object metadata and data in two separate blocks — so list creation is slower.

Let’s compare creation times:

# Run the code below in IPython or Jupyter Notebook.
In [1]: %timeit my_list = [1, 2, 3, 4, 5]
46.9 ns ± 0.933 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Now tuple creation:

# Run the code below in IPython or Jupyter Notebook.
In [2]: %timeit my_tuple = 1, 2, 3, 4, 5
15.5 ns ± 0.65 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

Tuples are about 3x faster to create.

Difference 5 #

Tuples have slightly faster indexed access than lists.

Let’s measure:

In [3]: %timeit my_list[0]
37.7 ns ± 1.93 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [4]: %timeit my_tuple[0]
36.3 ns ± 1.16 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Indexing speeds are nearly identical between lists and tuples.

Summary: when to use a tuple vs. a list #

Tuples use less memory, are created faster, and have slightly faster indexed access than lists. But you can’t change item values, add or remove items, or sort them with sorted.

So when should you use a tuple instead of a list?

  • Use a list when you’ll mutate the data: change item values, add new items, remove items, or sort them.
  • Use a tuple when the data shouldn’t change after definition (or doesn’t need to). Tuples always use less memory and perform better.

That wraps up this lesson. In the next lesson we’ll cover a very important data type: dictionaries.

X