In python, mutable objects are those that can change after they are created. In contrast, immutable objects can’t change after they are created. Examples of mutable objects include: list
and dict
. Examples of immutable objects include: tuple
and None
.
So what happens when we set a mutable object to be a default value, and why is this bad?
Let’s find out.
Consider the following class:
class ProtocolDroid: def __init__(self, name, languages=list()): self.name = name self.languages = languages def add_language(self, language): self.languages.append(language) def intro(self): num_langs = len(self.languages) print(f"Hello, I am {self.name}.") print(f"I am fluent in {num_langs} forms of communication!") print(self.languages)
Here we have a class representing a protocol droid, which is a robot that can speak multiple languages. We initialise the class with a name and a list of languages. The default for the list of languages is an empty list
. If we want to add more languages to the droid’s repertoire, we can use the add_language
method. If we then call the intro
method, our droid will introduce him/herself and tell us how many languages they speak, and which ones they are.
So let’s go ahead and try this, and see what happens.
First we’ll create a droid called D-4PO, and add some languages to them:
d4po = ProtocolDroid('D-4PO') d4po.add_language('French') d4po.add_language('Bocce') d4po.add_language('Basic')
Then we’ll create a droid called E-5PO, and also give them some languages:
e5po = ProtocolDroid('E-5PO') e5po.add_language('The Binary language of moisture vaporators') e5po.add_language('Huttese')
Great, now let’s get them to introduce themselves:
d4po.intro() e5po.intro()
And here is what they say:
Hello, I am D-4PO. I am fluent in 3 forms of communication! ['French', 'Bocce', 'Basic']
Hello, I am E-5PO. I am fluent in 5 forms of communication! ['French', 'Bocce', 'Basic', 'The Binary language of moisture vaporators', 'Huttese']
Wait a minute! What’s going on here? D-4PO speaks the three languages we added to them, but E-5PO speaks the languages we added to them and the languages we added to D-4PO! How has this happened?
The answer is down to that default value we set for languages in __init__
:
def __init__(self, name, languages=list()):
The list
object is mutable in python, which means it can change after it is created. But why does that cause an issue here? When we instantiate a new instance of ProtocolDroid
, don’t we get a new, fresh empty list? Actually, no. The list is created once when the ProtocolDroid
class is defined. Even if we instantiate a new instance of the class, we will be using the reference to the same initial list
object. Therefore, we will be updating the same list
object from different instances of the class. The upshot is that D-4PO and D-5PO share the same language list
object. Any languages that D-4PO learns, E-5PO also learns!
Sharing the same mutable object instance could be useful in some cases. Maybe you want all droids to know all the languages of all other droids. But mostly we don’t want this behaviour, as it can get messy and cause unintended side-effects.
So how do we fix this?
Thankfully, the solution is simple. Use an immutable default (such as None
) for the languages parameter, and set it to an empty list inside the __init__
method. This way, the list only gets created when the class is instantiated, not when it’s defined, so each instantiated droid has its own instance of a list
of languages.
class ProtocolDroid: def __init__(self, name, languages=None): self.name = name self.languages = list() def add_language(self, language): self.languages.append(language) def intro(self): num_langs = len(self.languages) print(f"Hello, I am {self.name}.") print(f"I am fluent in {num_langs} forms of communication!") print(self.languages)
Now we create the droids and assign them the same languages as before. This time their introductions are:
Hello, I am D-4PO. I am fluent in 3 forms of communication! ['French', 'Bocce', 'Basic']
Hello, I am E-5PO. I am fluent in 2 forms of communication! ['The Binary language of moisture vaporators', 'Huttese']
That’s better. Each droid speaks their own list of languages. We get to keep our job at the droid factory.
Mutable defaults are a bit of a gotcha in python, so it’s good to be aware of the trouble they can cause. If you find yourself trying to track down a mysterious issue whereby a list
or dict
contains entries that shouldn’t be there… a mutable default could well be to blame.
1 Response
[…] that are not best practice and could therefore lead to bugs. For example, if someone has used a mutable default in python, it would be worth checking if that was intended, due to the (possibly) unexpected […]