Immutable Objects In Python

By | January 25, 2020

Immutable objects are useful for making sure the data they contain cannot be changed after they are created. Immutable objects can be useful for passing messages between components, and when working with multiple threads. Immutable objects can also be easier to work with and reason about, because once they are created they cannot be changed. This post describes some of the immutable objects available in python.

Immutable Objects In Python

The important thing about immutable objects is that once they have been instantiated, they cannot be modified (or ‘mutated’, hence the name).

In python, immutability can be slightly tricky because it does not make a clear distinction between private and public attributes or methods in an object, making ‘protecting’ against mutation a challenge. There are thankfully some built in data types that you can use for immutability:

  • Tuples
  • Named tuples
  • Data Classes

Tuples

The simplest of the immutable objects in this list. Tuples are probably the most well known and widely used immutable data type available in python. Tuples are often used in a similar way to a list, that is as a series of elements, but they can also be used as a single value.

Read more about tuples.

Example:

message = ('Message header', 'Message data')
print(message)
print('Header: ', message[0])
print('Content: ', message[1])

which outputs:

('Message header', 'Message data')
Header:  Message header
Content:  Message data

If we try to modify one of the values in the tuple we get this error

Traceback (most recent call last):
  File "immutable_types.py", line 8, in <module>
    message[1] = 'New data'
TypeError: 'tuple' object does not support item assignment

Named Tuples

A big drawback to using a tuple is that values stored in the tuple can only be accessed by position, rather than a key or label. An extension to the concept of a ‘tuple’, named tuples help get around the restriction that data in tuples can only be accessed by position, rather than by a key.

Read more about named tuples.

Example:

import collections
Message = collections.namedtuple('Message', 'header content')
message = Message(header='Message header', content='Message data')

print(message)
print('Header: ', message.header)
print('Content: ', message.content)

which outputs:

Whole object:
Message(header='Message header', content='Message data')
Header:  Message header
Content:  Message data

If we try to change one of the values in the named tuple, we get an error:

Traceback (most recent call last):
  File "immutable_types.py", line 20, in <module>
    message.content = 'New data'
AttributeError: can't set attribute

Data Classes

Data classes were introduced in python 3.7 as a shortcut for making ‘data objects’ – i.e. objects designed to hold data only, and no methods. To make a data class (almost) immutable it can be passed the argument ‘frozen=True‘ when it is created.

Read more about data classes.

Example:

from dataclasses import dataclass

@dataclass(frozen=True)
class Message:
    header: str
    content: str

message = Message('Message header', 'Message data')
print(message)
print('Header: ', message.header)
print('Content: ', message.content)

Which gives the output

Message(header='Message header', content='Message data')
Header:  Message header
Content:  Message data

Because we used the ‘frozen=True’ argument to the dataclass decorator, if we try to change on of the values in the data object, we get an error:

Traceback (most recent call last):
  File "immutable_types.py", line 35, in <module>
    message.header = 'New message header'
  File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'header'