I've got a question about "python binding" (suppose the question has something to do with it). I spent hours on the question below and reached the topics "closure late binding", "scope" and so on.
As far as I know, "closure" is defined as a function which contains a free variable ,and it is late binding. So, I understand the code below outputs [[0, 1], [0, 1]] instead of [[0], [0, 1]].
# expected [[0], [0, 1]] but got [[0, 1], [0, 1]]
# this is because of "closure late binding"
def my_func():
array.append(path)
return
array, path = [], []
for value in range(2):
path.append(value)
my_func()
print(array)
So, I tried to set the arguments like below to avoid free variables, in turn closure. My expected output was [[0], [0, 1]] but still [[0, 1], [0, 1]] although the function does not have a free variable in.
# expected [[0], [0, 1]] but got [[0, 1], [0, 1]]
# this function is not a closure as far as I know
def my_func(array, path):
array.append(path)
return
array, path = [], []
for value in range(2):
path.append(value)
my_func(array, path)
print(array)
Am I missing something fundamental like late binding is applied to any functions?
This is not so much about closures as about how variables work in Python (which is not that different from how they work in many other languages, like Lisp and Java).
A variable is not a box where you can put a value, it is a box that holds a reference to an object. Similarly, each item in a list is a reference to a value.
Let's look at your second program. Because my_func does not really do anything, we can simplify a bit by inlining it, like so:
array, path = [], []
for value in range(2):
path.append(value)
array.append(path)
print(array)
What happens when we run this? First, we initialize array
and path
to hold references to empty lists:
array --> []
path --> []
Then we start running the loop, and do path.append(0)
. Thing now look like this:
array --> []
path --> [*]
|
v
0
Then we append whatever path
refers to — i.e, the list object containing a reference to a "zero" object — to array
:
array --> [*]
|
v
path --> [*]
|
v
0
Then we run the next iteration of the loop, appending a one to the list referenced by path
:
array --> [*]
|
v
path --> [*, *]
| |
v v
0 1
Finally, we append the same list again to array
:
array --> [*, *]
| /
|
v
path --> [*, *]
| |
v v
0 1
So when we're done, array
holds a reference to a list, which in turn holds two references to one list, which holds references to the integers zero and one.