Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Python Tuples: The Myth Of Immutability

DZone 's Guide to

Python Tuples: The Myth Of Immutability

Does immutability extend to their components?

· Big Data Zone ·
Free Resource

baby-python-on-forest-floor

Slithery boi

Along with lists, tuples are some of the most common collections used in Python. Mainly, they have two essential use cases: 

  • As records.

  • As immutable lists. 

Below, we will discuss the functionality of both, with an emphasis on the immutability of tuples in practical application. 

You may also like: Map and Filter Function in Python.

Tuples as Records

As records, tuples hold data, and every one of its items represents a field. In this case, the order of items is very important, as every position within the tuple represents a particular field.

The signification of a field depends on its position. Since position is important, the size of tuples used as records of data must be fixed and known, along with the significance of every item.

t_movie_1 = ('The Godfather', 1972, " Francis Ford Coppola")
# Tuple as a record of movie details of form (title, year of production, director)
title, year, director = t_movie_1
# tuple unpacking
t_movie_2 = ('Pulp fiction', 1994, " Quentin Tarantino")
list_movies = (t_movie_1, t_movie_2)
#a list of tuple movie records
for movie_title, movie_year, movie_director in list_movies:
    print(movie_title)
#The Godfather
#Pulp fiction


In the previous code block, note line three: the tuple unpacking affects the items of a tuple to separate them on different variables in just one expression.

This mechanism (unpacking) can be used with every iterable object and not only tuples. The main point to respect is that the number of items in the tuple must be the same as the number of variables in the left part of the expression unless you use * to catch additional items in a list.

The below code block shows the use of * with unpacking tuples.

t_movie_1 = ('The Godfather', 1972, "Francis Ford Coppola")
# Tuple as a record of movie details of form (title, year, director)
title, year, director = t_movie_1
print ("%s, %s, %s" %(title, year, director))
#The Godfather, 1972, Francis Ford Coppola
title, *rest = t_movie_1
print (title, rest)
#The Godfather , [1972, 'Francis Ford Coppola']


In some cases, to interpret a field signification in a record (tuple) only by its position is not very readable and practical. That is why the package collection in the python standard library offers the namedtuple object. The collections.namedtuple is a factory function that creates a subclass of tuple with field names and a class name.

from collections import namedtuple

Movie = namedtuple("Movie", ["title", "year", "director"])
#creation of the namedtuple subclass with Movie as the name of the class and a list of 
#fileds
m1 = Movie('The Godfather', 1972, " Francis Ford Coppola")
#instanciation of the named tuple m1
print (m1)
#Movie(title='The Godfather', year=1972, director=' Francis Ford Coppola')
print(m1.title)
#The Godfather


Many prefer dictionaries to use as data record containers rather than tuples. Although dictionaries are handfull within this scope, they consume too much memory in comparison with tuples and even with namedtuple.

Tuples as "Immutable Lists"

Many introductory tutorials to Pyhton mention that tuples are immutable lists. It is not wrong, but it is not also 100% accurate.

When exploring these containers, you may be surprised to find out that immutable tuples are in some cases mutable.

Let us start directly with an example:

t_movie_1 = ('The Godfather', 1972, "Francis Ford Coppola")
# Tuple as a record of movie details of form (title, year, director)
t_movie_2 = (['The Godfather', 'le parrain'], 1972, "Francis Ford Coppola")
#same movie tuple but with a list that contains the name of the movie in different lanquages
t_movie_1[0] = "le parrain"
#TypeError: 'tuple' object does not support item assignment
t_movie_2[0].append("el padrino")
print(t_movie_2)
#(['The Godfather', 'le parrain', 'el padrino'], 1972, 'Francis Ford Coppola')


The first tuple t_movie_1 is a tuple with only immutable objects as items (int, str, ..), as, by definition, tuples are immutable. Trying to affect another value to the first item raises a TypeError exception.

However, note the second tuple behavior. Appending a new value to the first item works fine, and the new value is added to the list within the tuple t_movie_2.

The first item of t_movie_2 is a mutable list. Why was no TypeError exception raised?

Well, the tuple contains a reference to the list and not the items of the list themselves. The list method  append does not change that reference, but it does add a new item to the list. So, the tuple item (reference to a list) didn't really change.

t_movie_2 = (['The Godfather', 'le parrain'], 1972, "Francis Ford Coppola")
#same movie tuple but with a list that contains the name of the movie in different lanquages
print( id(t_movie_2[0]))
#2704848
t_movie_2[0].append("el padrino")
print(t_movie_2)
#(['The Godfather', 'le parrain', 'el padrino'], 1972, 'Francis Ford Coppola')
print( id(t_movie_2[0]))
#2704848


In the previous code block , the id of the list object (the reference in a sort) within the tuple didn't change after the append method call.

Technically, the tuple didn't change (it is really immutable). It is the content of its mutable list which did change.

If you try to affect a new list in place of the old list within the tuple, a TypeError will be raised, as the affectation consists of replacing the existing list reference with a new one of the new list to have the same behavior as the mutable objects.

t_movie_2 = (['The Godfather', 'le parrain'], 1972, "Francis Ford Coppola")
t_movie_2[0] = ['The Godfather', 'le parrain', 'el padrino']
#TypeError: 'tuple' object does not support item assignment


The following picture describes what happens when appending a new item to the list within the tuple.

tuple immutabilityThere is a very typical case that occurs when you use the operator += on a mutable container within a tuple. Let us explore it with the example below:

t_movie_2 = (['The Godfather', 'le parrain'], 1972, "Francis Ford Coppola")
t_movie_2[0] += 'el padrino'
#TypeError: 'tuple' object does not support item assignment


The += operator raises a TypeError. You can say that this is normal, and I agree with you — after all += is an assignment operator similar to the = operator, so why expect a different behaviour different of this one! 

What is really mind blowing is that even with the exception raised the, affectation did occur; see the code below.

t_movie_2 = (['The Godfather', 'le parrain'], 1972, "Francis Ford Coppola")
try:
    t_movie_2[0] += ['el padrino']
except TypeError as ex:
    print (str(ex))
#'tuple' object does not support item assignment
print (t_movie_2)
#(['The Godfather', 'le parrain', 'el padrino'], 1972, 'Francis Ford Coppola')


This is weird, although, it can be explained. The += is not an elementary operation; if you check the assembler code for this operation, you will realize that, at first the list was appended and later on the exception is raised, so even with exception, the append is already done.

Conclusion 

To summarize, tuples are very easy to use and very helpful. They are mainly used as data records or as immutable lists. The immutability of tuples depends on the type of items within it. To generalize, a tuple is immutable. However, the mutable items within it are not. 


Related Articles

Topics:
python ,development ,tutorial ,big data ,tuples ,list ,dictionaries ,collections

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}