0

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())
mitikawa
  • 11
  • 3

1 Answers1

2

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.

ThePyGuy
  • 17,779
  • 5
  • 18
  • 45
  • Thanks! I still can't quite wrap my mind around why is it designed to only be called once, but I'm guessing this solves other problems. This almost feels like the default value initialization is happening on ""compilation time"". I managed to reproduce this behavior on the last section of code as well. If instead of defining a=0 you use a=b, b being a global variable, even if you change the value of global variable b in between the function calls, the result of the function is the same as the id remains the same. – mitikawa Sep 22 '22 at 14:56
  • 1
    Hmm, are you not aware of [this](https://stackoverflow.com/q/1132941/12671057) or is it somehow not a duplicate? – Kelly Bundy Sep 22 '22 at 15:05
  • 1
    @KellyBundy Yes I'm aware that such contents do exist in SO but there are some discrepancies: 1. The linked thread mainly talks about list and not the dictionary. 2. This question includes a function call in default parameter. And these things make this question slightly different than the ones that already exist, or in other words, combination of multiple existing questions. Nevertheless, the underlying issue is of course the mutable object as default argument. – ThePyGuy Sep 22 '22 at 15:32