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.
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.
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.
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'