Reputation: 57
def outer(l):
def inner(n):
return l * n
return inner
l = [1, 2, 3]
f = outer(l)
print(f(3)) # => [1, 2, 3, 1, 2, 3, 1, 2, 3]
l.append(4)
print(f(3)) # => [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
Because function outer
returns the function inner
, inner()
is now bound to the name f
. (f
is a closure)
When f()
is called, since l
can't be found in the local namespace, but can be found in the global namespace, l(=[1, 2, 3])
is passed to f()
.
The first f(3)
duplicates the list l
3 times, therefore, returns [1, 2, 3, 1, 2, 3, 1, 2, 3]
.
However, right before the second f(3)
, integer 4
is appended to the list l
. Now, l = [1, 2, 3, 4]
.
As a result, the second f(3)
duplicates the updated list l
3 times, therefore, returns [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
.
Am I correct? Or am I wrong?
Thank You.
Upvotes: 2
Views: 108
Reputation: 95948
You are incorrect about this:
When
f()
is called, sincel
can't be found in the local namespace, but can be found in the global namespace,l(=[1, 2, 3])
is passed tof()
.
When f
is called, it does a lookup in its closure. It is closed over the l
that is local to outer
.
Consider:
>>> def outer(l):
... def inner(n):
... return l * n
... return inner
...
>>> l = [1, 2, 3]
>>> f = outer(l)
>>> f.__closure__
(<cell at 0x7fb3ddb1af10: list object at 0x7fb3ddaa51c0>,)
Consider what happens when you change what the global l
is bound to:
>>> l = ["hello"]
>>> f(2)
[1, 2, 3, 1, 2, 3]
>>>
In this case, the global l
and the local l
in outer
are no longer referring to the same object, but the variable still correctly finds the name that it is closed over.
And also:
>>> import dis
>>> dis.dis(f)
3 0 LOAD_DEREF 0 (l)
2 LOAD_FAST 0 (n)
4 BINARY_MULTIPLY
6 RETURN_VALUE
Note, it uses LOAD_DEREF
to find l
, this is the op-code that is use to lookup names in a closure (LOAD_FAST
is for local variable lookups).
This is what you would see if it were using a global l
:
>>> def foo():
... print(l)
...
>>> foo()
['hello']
>>> dis.dis(foo)
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_GLOBAL 1 (l)
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
>>> l.append("world")
>>> foo()
['hello', 'world']
>>> l = ["something", "else", "entirely"]
>>> foo()
['something', 'else', 'entirely']
More explicitly:
>>> def with_closure(l):
... def inner():
... return l
... return inner
...
>>> def global_lookup():
... return l
...
>>> l = []
>>> f = with_closure(l)
>>> f()
[]
>>> global_lookup()
[]
>>> l.append("hi")
>>> f()
['hi']
>>> global_lookup()
['hi']
>>> l = ['bye']
>>> f()
['hi']
>>> global_lookup()
['bye']
So, notice the way that the two different functions behave differently when you re-assign the global variable (instead of just mutating the object the global variable refers to)
Upvotes: 3