Python - Mutable Data Types

Python - Mutable Data Types

Many people would think this is too easy for them, but believe me, it is a trick. The Python program allows you to create variables without declaring their type. This is great for beginners, but it also causes a lot of pain in maintenance, and the mutable type is one of them.

Let’s start with the basic immutable types Tuple and Set, we create a Tuple type variable called “fruit” and assign two values to it and then try to update it.

>>> fruit = ("banana", "grape")
>>> fruit[0] = "orange"

The following error will occur because it is an immutable type. In addition, it happens in the Set type variable as well.

>>> Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

So what would happen if we try to do the same thing in a mutable type variable like a List, we can update the value successfully.

>>> names = ["Nick", "Eric"]
>>> names[0] = "Francis"
>>> names
['Francis', 'Eric']

In the above examples, we know List type variable can change the value after creation, as a result, let’s try something interesting now

def update_first_value(names: List[str]) -> List[str]:
    names[0] = "Francis"
    return names


student_names = ["Nick", "Eric"]
updated_student_names = update_first_value(student_names)

print(f"original list: {student_names}")
print(f"updated list: {updated_student_names}")
original list: ['Francis', 'Eric']
updated list: ['Francis', 'Eric']

It works as expected, the list has been updated correctly, but you would notice that the original list has been edited together. What happened?

Since variables are still pointing to the same memory when they are created and assigned to a new variable, if we update one of them, then all other variables will be updated as well.

You can use a built-in function id to find out


print(f"original list: {id(student_names)}")
print(f"updated list: {id(updated_student_names)}")
original list: 4503145536
updated list: 4503145536

As a result, even if the function didn’t return anything, the result would remain the same.

def update_first_value(names: List[str]):
    names[0] = "Francis"

student_names = ["Nick", "Eric"]
update_first_value(student_names)

assert student_names == ["Francis", "Eric"]

Another common mistake is the default argument

def update_value(name: str, new_names: List[str] = []):
    new_names.append(name)
    return new_names

first_names = update_value("Nick")
print(first_names)
second_names = update_value("Eric")
print(second_names)

What would you expect to be like

['Nick']
['Eric']

However, the actual result is

['Nick']
['Nick', 'Eric']

As a result, the mutable type is not recommended to be a default argument.

In the above, I only take List type as examples, but you can apply the concept to all mutable types, such as dictionary and customised object.

Conclusion

Python gives us the flexibility to manage variable creation, but we must understand carefully when we play around, otherwise, it might lead us down a rabbit hole.

Happy coding!