Why does using a function to define the default value of an argument causes information leak?

I was trying to set the default value of an argument with a standard dictionary that is built with another function. However, when calling the function which requires said argument a second time, the information from the previous run is leaking into the second run.

standard_list = ['a','b','c','d']
my_list = ['b','d']
my_second_list = ['a','b']

def init_dict():
    my_dict = {}
    for key in standard_list:
            my_dict[key]=0
    return my_dict

def create_shopping_dict(shopping_list, standard_dict=init_dict()):
    for key in shopping_list:
        standard_dict[key]=1
    return standard_dict

my_shopping_list = create_shopping_dict(my_list)

my_second_shopping_list = create_shopping_dict(my_second_list)
print(my_second_shopping_list)

Expected:

{'a': 1, 'b': 1, 'c': 0, 'd': 0}

Actual:

{'a': 1, 'b': 1, 'c': 0, 'd': 1}

Moving the initialization of the standard_dict argument inside the function solves the problem, but I don’t understand why.

def create_shopping_dict(shopping_list):
    standard_dict=init_dict()        
    for key in shopping_list:
        standard_dict[key]=1
    return standard_dict

If it was the case that Python stored the argument value for further runs instead of using the default value, then the following code would print 1 then 2, but it prints 1 and 1.

def test(a=0):
    a+=1
    return a

print(test())
print(test())

>Solution :

That’s because the function to get default value is executed only once, and same reference is kept for later i.e. the function doesn’t get called twice. You can define the parameter to be None as default, and inside the function check for default then call the function to initilize the value, it should work fine:

def create_shopping_dict(shopping_list, standard_dict=None):
    if standard_dict is None:
        standard_dict = init_dict() # Same as your 2nd case
    for key in shopping_list:
        standard_dict[key]=1
    return standard_dict

To prove above hypothesis, you can either add print statement inside your initializer function and see if it gets called. Or, you can just check the id of the standard_dict argument inside the create_shopping_dict function:

def init_dict():
    print("Function called")
    my_dict = {}
    for key in standard_list:
            my_dict[key]=0
    return my_dict

def create_shopping_dict(shopping_list, standard_dict=init_dict()):
    print("###ID: ", id(standard_dict))
    for key in shopping_list:
        standard_dict[key]=1
    return standard_dict

my_shopping_list = create_shopping_dict(my_list)
my_second_shopping_list = create_shopping_dict(my_second_list)

# Console output:
Function called
###ID:  2113624480328
###ID:  2113624480328
{'a': 1, 'b': 1, 'c': 0, 'd': 1}

As you can see, the initializer is called only once, and the id of standard_dict for both the calls to create_shopping_dict are identical.

Leave a Reply