Incorrect scoping inside function and persistence of memory reference

Hi,

I have observed an issue with reference to the below code. If you can notice the last statement of the code, the ans is not as expected, and reason for the same is the that the reference of previous call to the function persists (A(2)). I do understand that a function object is evaluated only once, but this seems to break the scoping rule, as new_list is local to the function and hence should be destroyed after the execution is complete. I also know the construct, is new_list is passed from the caller then that shall work as pass by reference, but in case when it is not passed the new_list should not refer to the old reference.

def A(x,new_list=[]):
    print(id(new_list))
    for i in range(x):
        new_list.append(i*i)
    print(new_list)

A(2)
A(3,[3,2,1])
A(3) 

result

4324678656
[0, 1]
4322385472
[3, 2, 1, 0, 1, 4]
4324678656
[0, 1, 0, 1, 4]

kindly, help me to understand why python complier should not delete the old reference of new_list. ?

Don’t use mutable types (like a list) as a default value.

https://docs.python.org/3/faq/programming.html#why-are-default-values-shared-between-objects

1 Like

The default value for new_list is stored alongside the function, and will not disappear until the function itself does.

>>> def A(new_list=[]):
...     pass
...
>>> A.__defaults__
([],)

new_list is a local variable, but it’s not a copy of the default value, it’s just a reference to the same underlying list, so mutating it will affect A.__defaults__, which is why the changes persist between calls.

Just to clarify, if you were to reassign to new_list instead (as in new_list = new_list + [i * i]), it wouldn’t affect A.__defaults__ and thus wouldn’t persist between calls, because that’s actually creating a new list, not mutating the one you already have.

Just in case this isn’t a theoretical question, I’ll mention that the typical idiom if you want to do something like this is to use the value None as your default.

def A(x,new_list=None):
    if new_list is None:
        new_list = []
    ...