Photo by Baher Khairy on Unsplash

6 Things to Understand Python Data Mutability

Immutable vs. Mutable

Yong Cui
Yong Cui
Apr 8, 2020 · 8 min read

Data mutability isn’t a topic that we encounter early in our Python learning course. However, understanding this topic is a crucial step to becoming a more seasoned Python programmer.

In this article, I’d like to share six fundamental notions that can help you get some insights into this topic. Notably, we can’t fully clarify data mutability in isolation with other crucial concepts, such as objects and introspections, and thus we’ll begin our discussion on these related topics first.

As an object-oriented programming language, Python uses objects everywhere. Everything in Python is an object, including built-in data types (e.g., integers, strings), functions, classes, and even modules.

But what are objects in Python? In essence, objects represent tangible data containers that form a human-computer interface. Programmers write code to directly manipulate these containers to implement specific functionalities in a way that computers can understand.

  • Identity. Under the hood, you can consider that objects are blocks of data occupying somewhere in the memory. In Python, we use the memory addresses of these blocks to identify objects. More specifically, the identity of a particular Python object is its memory address expressed as an integer (more on this in the next section).
  • Type. When an object is created, its type is defined and set in the memory. The specific block of data (i.e., the object) doesn’t only have its identity created, but also has its type (e.g., integers, strings) specified when the block of memory is tied to the object.
  • Value. You can think of values as human-readable representations of the underlying data in the memory. What value an object can have is determined by its type. For example, if an object is of the type integer, it can’t hold data in the type of dictionary and vice versa.

As discussed in the above section, each object uses its memory address as its identity. We can get the address using the built-in id() function. Here are some examples in the below code snippet. As you can see, each object has an identity expressed as an integer value, representing its memory address.

>>> # an integer
>>> number = 5
>>> id(number)
4397770000
>>> # a string
>>> hello = 'Hello World!'
>>> id(hello)
4401627376
>>> # a list
>>> numbers = [3, 5, 8, 9]
>>> id(numbers)
4401653152

One thing to note is that when we declare variables, taking the variable number as an example, we create an integer object in the memory and assign it to the variable with the name number. We should understand that objects in the memory and their corresponding variables (or names) are different things. When used, these variables refer to the underlying data that they hold, which is termed that the variable has a reference to the underlying object in the memory.

Each object has a type associated with the data when it’s created. The type determines what values the object can hold. To know the type of a particular object, we can use the type() function. Here are some examples. For simplicity, we’re still using the variables created above.

>>> # check the number's type
>>> type(number)
<class 'int'>
>>> # check the string's type
>>> type(hello)
<class 'str'>
>>> # check the list's type
>>> type(numbers)
<class 'list'>

Both id() and type() functions belong to the topic of introspection, which refers to the ability of an object to find out its type, identity, and available attributes at runtime. If you’re interested in learning more about this topic, you can refer to my previous article provided at the end.

What’s mutability in terms of Python objects? In general, mutability refers to whether an object can be changed or not after its creation. In the first section, you learned the concepts of identity, type, and value for an object. Relating these concepts to mutability, we understand that an object has its identity and type determined when it’s created. Thus more precisely, the mutability of an object refers to whether the object’s memory block can hold different data or not after its allocation to the object.

The most common immutable objects in Python are the built-in data types, including integers, floating-point numbers, booleans, strings, and tuples, with the latter two sequence data types (e.g., strings being a sequence of characters). Data of these types can’t be changed after their creation. Here are some examples showing the immutability of sequence data types.

>>> # Create a string and change the last letter afterwards
>>> hello_world = 'Hello World!'
>>> hello_world[-1] = '?'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> # Create a tuple and change one item afterwards
>>> student = ('John', 'Boy', 95)
>>> student[2] = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

To show the immutability of numeric data types (e.g., integers and floats), consider the following example. We first declare a variable thousand with an integer object assigned to it. We then increment this variable by 2. If this incrementation happens in situ, we should expect to see this variable (actually the underlying integer object 1002) should have the same identity as the initial one (i.e., 1000). However, that’s not happening. In other words, trying to changing the integer object is creating another integer object.

>>> # Create an integer and increment by 2
>>> thousand = 1000
>>> print(f'thousand id before: {id(thousand)}')
thousand id before: 4482502000
>>> thousand += 2
>>> print(f'thousand id after: {id(thousand)}')
thousand id after: 4482498736

The most used mutable object are lists, dictionaries, sets, and custom classes in most cases. For the purpose of a general discussion, I’ll just focus on the built-in data types, namely list, dict, and set. The following code snippet shows you how we can mutate the data by adding items to these data. For the sake of clarity, I’ll just use the list data type in the example because of the dict and set data types following the same in situ mutability rule.

>>> # Create a list and add an item
>>> pets = ['dogs', 'cats', 'birds']
>>> print(f'pets id before: {id(pets)}')
pets id before: 4483238000
>>> pets.append('hamsters')
>>> print(f'pets id after: {id(pets)}')
pets id after: 4483238000
>>> pets.remove('dogs')
>>> print(f'pets id after: {id(pets)}')
pets id after: 4483238000

In the last two sections, we discussed immutable and mutable objects separately. What will happen if they’re mixed? For example, can we change the value of the string as an element in a mutable data type, such as a list? The answer is negative. The outside container being mutable (e.g., list) doesn’t change the mutability of the inside elements (e.g., string).

>>> # Create a list of strings, and change one string's value
>>> fruits = ['apples', 'oranges', 'bananas']
>>> fruits[0][0] = 'A'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

How about the opposite? Can we change the value of a mutable object inside an immutable object? The answer is positive. Consider the example below. We first create a tuple called english and get the second itemvowels by unpacking. As you can see, we’re able to change the values of the list in situ — essentially the same mutable object. However, having a mutable object (i.e., the list) doesn’t change the immutability of the outside tuple. The opposite is true too. The immutability of the outside data type (i.e., the tuple) doesn’t change the mutability of the inside list.

Another thing to note regarding the following example is that it appears that the value of the tuple has changed after updating the contained list, which may suggest that we could mutate the tuple’s value. The key here is that the tuple always refers to the same container — the list. In other words, the value that the tuple holds is the reference to the underlying object, which stays the same, as reflected by the consistent identity of the list.

>>> # Create a tuple and unpack it to access the list
>>> english = (26, ['a', 'i', 'u'])
>>> _, vowels = english
>>> print(f'before vowels: {vowels}; id: {id(vowels)}')
before vowels: ['a', 'i', 'u']; id: 4483237360
>>> # Change the list
>>> vowels.append('o')
>>> print(f'after vowels: {vowels}; id: {id(vowels)}')
after vowels: ['a', 'i', 'u', 'o']; id: 4483237360
>>> # Change the tuple
>>> english[0] = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Taken the above two code snippets together, we can see that the objects’ mutability only depends on the types of these objects. Being embedded in or containing another object of different mutability doesn’t change its own mutability.

Another important topic related to data mutability pertains to the different behaviors of mutable and immutable objects when they’re passed to functions as arguments. Let’s consider the following function that can take one immutable data type (i.e., integer) and the other using mutable data type (i.e., list).

>>> # Define a function that changes a number or a list
>>> def addFive(in_var):
... print(f'before value: {in_var} & id: {id(in_var)}')
... if type(in_var) == int:
... in_var += 5
... elif type(in_var) == list:
... in_var.append(5)
... print(f'after value: {in_var} & id: {id(in_var)}')
...
>>> # pass in a number
>>> addFive(9)
before value: 9 & id: 4479354256
after value: 14 & id: 4479354416
>>> # pass in a list
>>> addFive([1, 2])
before value: [1, 2] & id: 4482494208
after value: [1, 2, 5] & id: 4482494208

As you can see, for the integer data type, we’re sending the integer object to the function and getting a new integer with a different identity. Essentially, we’re passing the values for immutable data types used in function calls, known as pass by value. For the list data type, we’re sending the list object to the function and getting the same list with updated items, and its identity stays the same. Essentially, we’re passing the references for mutable data types used in function calls, known as pass by reference.

Thus, in terms of their usage in function calls, the mutability of the data types determines whether the values or the references of the objects are sent to the functions. We should know this nuance between immutable and mutable objects.

Takeaways

In this article, we studied what immutable and mutable objects are in Python. Particularly, we reviewed the most used immutable and mutable data types and how they exert their immutability and mutability by themselves and when they’re used in function calls. Here are some key takeaways.

  • When objects are created in the memory, their identities and types are determined.
  • Under the hood, mutability refers to whether the block of the memory where the object occupies allows the data to change or not.
  • Generally speaking, when we say the objects are immutable, we usually mean that we can’t the objects’ values after the creation of these objects.
  • An object’s mutability is solely determined by its type and has nothing to do with its surrounding objects (e.g., in a container or being a container itself).
  • In function calls, immutable objects are passed by their values, while mutable objects are passed by their references to the underlying memory blocks.

The Startup

Get smarter at building your thing. Join The Startup’s +793K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Yong Cui

Written by

Yong Cui

Work at the nexus of biomedicine, data science & mobile dev. Love to write on these technological topics. Follow me @ycui01 on Twitter to get latest articles.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +793K followers.

Yong Cui

Written by

Yong Cui

Work at the nexus of biomedicine, data science & mobile dev. Love to write on these technological topics. Follow me @ycui01 on Twitter to get latest articles.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +793K followers.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store