Python: Three Hidden Ways to Create Potential Bugs with Mutable Data Structures (Part II)

April 27, 2021

Today we are going to continue our three-part series of Three Hidden Ways to Create Potential Bugs with Mutable Data Structures with a focus on passing in mutable data structures as a function’s default parameters .

But wait a second. What data structures/objects are mutable and what data structures/objects are immutable in Python? Here is a quick overview:
List: mutable
Dictionary: mutable
Set: mutable
Tuple: immutable
String: immutable
Interger: immutable
Boolean: immutable
Float: immutable

The idea is that we want to avoid passing in mutable data structures as a function’s default parameters as that could create potential problems that are hard to detect. I am borrowing an example from Fluent Python by Luciano Ramalho:

#Passing in an empty list as default parameter
class Fruits:
    def __init__(self, list_of_fruits = []):
        self.list_of_fruits = list_of_fruits
    def add_fruits(self, fruit):
        self.list_of_fruits.append(fruit)

a = Fruits(['apple', 'orange'])
a.list_of_fruits
['apple', 'orange']

#This is mutating on the default list 
b = Fruits()
b.add_fruits('watermelon')
b.list_of_fruits
['watermelon']

# The default list has been mutated, every instance created without passing in 
#a parameter value will have default list parameter as ['watermelon'] rather than an
#empty list
c = Fruits()
c.list_of_fruits
['watermelon']

#This is the appropriate way of passing in a mutable data structure as functional parameter
class Fruits:
    def __init__(self, list_of_fruits = None):
        if list_of_fruits is None:
           self.list_of_fruits = [] # if it's None, then create an empty list
        else:
           self.list_of_fruits = list(list_of_fruits) #creating a copy of passed in list
    def add_fruits(self, fruit):
        self.list_of_fruits.append(fruit)
                  
a = Fruits(['apple', 'orange'])
a.list_of_fruits
['apple', 'orange']

#This doesn't affect the default value, as it's None
b = Fruits()
b.add_fruits('watermelon')
b.list_of_fruits
['watermelon']


#Now future initiated instances don't get affected
c = Fruits()
c.list_of_fruits
[]

Hope you enjoy this week’s Data Hack Tuesday. Will see you next week!