default
- just as it says: default
mutable
- a data type whose reference value can be changed without changing the pointer
arguments
- the values passed in to a method or function
def example(the_devil = []):
return the_devil
assert example() == []
assert example([1]) == [1]
This looks okay, right?
As you can see, the Example 1 specifies a default value for the parameter: the_devil
.
def oh_no_dont_do_it(your_heart = []):
your_heart.append(1)
return your_heart
but_i_did = oh_no_dont_do_it()
assert but_i_did == [1]
# Now let's update the value for our pointer
but_i_did.append(9001)
assert but_i_did == [1, 9001]
# Okay... let's do it again
oops_i_did_it_again = oh_no_dont_do_it()
assert oops_i_did_it_again == [1, 9001, 1] # Because this makes total sense
Okay, so... Let’s take a look as to why this happened... First we should rewrite this function to leverage type hints and more sensible naming.
from typing import List
def append_one(arg: List = []) -> List[int]:
arg.append(1)
return arg
first = append_one()
second = append_one()
assert first == [1, 1]
assert second == [1, 1]
assert first == second
Okay, if we look at the List type documentation we can see that it inherits from MutableSequence
. Mutable... 😈
In other words, we have specified the default value of our argument to be a child of MutableSequence.
Let’s fix this by assigning an immutable value as the default argument and then overwrite the pointer inside of the closure when we execute the function.