Home > Software design >  Python dictionary empties in some cases when passed between functions
Python dictionary empties in some cases when passed between functions

Time:03-27

I have a dictionary that needs to be referred to by other functions. Rather than make it global, I pass the dictionary as an argument.

def functionA(dictionary):
    dictionary = {}
    functionB(dictionary)
    print(f"In A, after B, length: {len(dictionary)}")

def functionB(dictionary):
    dictionary["A"] = "some value"
    dictionary["B"] = "another value"

def main():
    dictionary = {}
    functionA(dictionary)
    print(f"Back in main. Length: {len(dictionary)}")

if __name__ == "__main__":
    """ This is executed when run from the command line """
    main()

In the above code, main() initializes dictionary as empty and passes it to functionA(), which also (redundantly) sets it to empty and passes it to functionB(), which adds a couple of entries.

What I expected: After main() returns from functionA(), I expected dictionary to have the two entries added by functionB(). However, instead, it was empty. The output was:

In A, after B, length: 2
Back in main. Length: 0

If, however, I remove the redundant dictionary = {} in functionA (i.e., the second line of the above code block), the problem goes away and I get the following output:

In A, after B, length: 2
Back in main. Length: 2

I don't see why the dictionary = {} statement in functionA() causes the subsequently added items to not get transferred back to main(). Any insights? I feel like there's something fundamental here that I'm clueless about.

CodePudding user response:

functionA reassigns the dictionary name to a new piece of memory that is distinct from the memory referred to in the parameter.

Your code is logically equivalent to:

def functionA(dictionary):
    dictionary_a = {}
    functionB(dictionary_a)
    print(f"In A, after B, length: {len(dictionary_a)}")

def functionB(dictionary):
    dictionary["A"] = "some value"
    dictionary["B"] = "another value"

def main():
    dictionary = {}
    functionA(dictionary)
    print(f"Back in main. Length: {len(dictionary)}")

if __name__ == "__main__":
    """ This is executed when run from the command line """
    main()

which should make the issue more readily apparent.

You can think of the dictionary in main() and the dictionary in functionA as two separate references to the same dictionary. When dictionary in functionA is reassigned, it doesn't affect the value of dictionary in main(). As a rule of thumb, reassignment only affects the name that you put on the left-hand side of the statement.

CodePudding user response:

The critical thing is that dictionary = {} does not empty the original dictionary. It instead creates a new dictionary. This is more easily seen if we bind the original dictionary to a different name:

def functionA(dictionary):
    original = dictionary
    dictionary = {}
    functionB(dictionary)
    print(f"In A, after B, length: {len(dictionary)}")
    print(f"Original argument: {len(original)}")
In A, after B, length: 2
Original argument: 0
Back in main. Length: 0

The original dictionary that was created in main and passed to functionA is never modified, and so it is still empty. The dictionary inside functionA which was passed to functionB is an entirely new object (separate from original) that main has no way of accessing because it was never returned.

If you want to modify the original dictionary by passing around references to it, you need to make sure that you use mutator methods instead of rebinding the name, e.g.:

def functionA(dictionary):
    dictionary.clear()
    functionB(dictionary)
    print(f"In A, after B, length: {len(dictionary)}")
In A, after B, length: 2
Back in main. Length: 2

You can also avoid this type of confusion by always returning whatever data you want the caller to receive:

def functionA(dictionary):
    dictionary = {}
    dictionary = functionB(dictionary)
    print(f"In A, after B, length: {len(dictionary)}")
    return dictionary

def functionB(dictionary):
    dictionary["A"] = "some value"
    dictionary["B"] = "another value"
    return dictionary

def main():
    dictionary = {}
    dictionary = functionA(dictionary)
    print(f"Back in main. Length: {len(dictionary)}")
In A, after B, length: 2
Back in main. Length: 2
  • Related