Happy Yoon
Happy Yoon

Reputation: 57

is my understanding correct about the following code?

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

Answers (1)

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95948

You are incorrect about this:

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().

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

Related Questions