Topics

Using None for default argument values is especially important when the function args can be mutated in python.

The default value is evaluated only once when the function is defined, not each time the function is called. All calls to the function will share the same default object or the same initial dynamic value, which can lead to unexpected behavior.

def decode(data, default={}):
    try:
        return json.loads(data)
    except ValueError:
        return default
 
foo = decode("bad data")
foo["stuff"] = 5
bar = decode("also bad")
bar["meep"] = 1
 
print("Foo:", foo) #Foo: {'stuff': 5, 'meep': 1}
print("Bar:", bar) #Bar: {'stuff': 5, 'meep': 1}

In above, foo and bar are both equal to the default parameter to the decode function. They are the same dictionary object: assert foo is bar.

The fix is to use None and explicitly check inside the function:

def decode(data, default=None):
    """Load JSON data from a string.
 
    Args:
        data: JSON data to decode.
        default: Value to return if decoding fail
        Defaults to an empty dictionary.
    """
    try:
        return json.loads(data)
    except ValueError:
        if default is None: # Check here
        default = {}
    return default

Now, running the same test code as before produces the expected result:

Foo: {'stuff': 5}
Bar: {'meep': 1}