I stumbled upon a strange behavior I like to get explained. To illustrate, consider the following function (written inside a Jupyter cell):
def testfunc(res = [], a = {}, i = 0):
if i < 3:
b = dict(a) # make a local copy to keep in this recursion level
b[str(chr(97+i))] = i # fill some values
res.append(b) # push into result
res = testfunc(res,b,i+1) # recursion
return res
print(testfunc())
Now the output is, as expected:
[{'a': 0}, {'a': 0, 'b': 1}, {'a': 0, 'b': 1, 'c': 2}]
and if I output 'res', it is as expected not in scope anymore:
print(res)
NameError: name 'res' is not defined
So, far so good. Now however, if I call the function again in a new cell:
print(testfunc())
this happens:
[{'a': 0}, {'a': 0, 'b': 1}, {'a': 0, 'b': 1, 'c': 2}, {'a': 0}, {'a': 0, 'b': 1}, {'a': 0, 'b': 1, 'c': 2}]
Which leads to the assumption, that res was not destroyed at the end of the function and was not reassigned with an empty array as the default parameter exacts. Instead, the parameter with the content of the former call is reused which I consider a very odd and unexpected behavior. Strangely, this is only happening with the object (list), not the integer, probably because it's a primitive data type.
Is this supposed to be? Why is the explicit assignment of the default parameter ignored, when the function is called with no parameter and instead the variable of a former function call is used? What is the benefit of such a behavior?
I understand that I can work around this issue by forwarding an empty list like this
testfunc([])
and not using the default parameter, which makes default parameters basically not only useless but dangerous, as their usage seems to be unpredictable. Or do I miss something?