Just 2 notes about how closure in Python 3 captures variables.
Note 1.
The wrong way
def make_multipiler_the_wrong_way(): multipilers = [] # Tries to remember each i for i in range(5): # All remember same last i multipilers.append(lambda x: i * x) return multipilers if __name__ == "__main__": m = make_multipiler_the_wrong_way() for i in range(5): print(m[i](3))
The output is
12 12 12 12 12
So the right way should be
def make_multipiler_the_right_way(): def make_lambda(i): # when the closure is made # i is captured/bound in the scope of `make_lambda(i)` # so it won't change anymore return lambda x: i * x multipilers = [] for i in range(5): multipilers.append(make_lambda(i)) return multipilers if __name__ == "__main__": m = make_multipiler_the_right_way() for i in range(5): print(m[i](3))
And it outputs what we desired
0 3 6 9 12
Why? See locals()
between these 2 versions
def make_multipiler_the_wrong_way(): multipilers = [] # Tries to remember each i for i in range(5): # All remember same last i multipilers.append(lambda x: print("i: {}/({}), x: {}, local: {}".format(i, hex(id(i)), x, locals()))) return multipilers if __name__ == "__main__": m = make_multipiler_the_wrong_way() for i in range(5): print(m[i](3))
The locals()
in the wrong version
i: 4/(0x1003f5f70), x: 3, local: {'x': 3, 'i': 4} i: 4/(0x1003f5f70), x: 3, local: {'x': 3, 'i': 4} i: 4/(0x1003f5f70), x: 3, local: {'x': 3, 'i': 4} i: 4/(0x1003f5f70), x: 3, local: {'x': 3, 'i': 4} i: 4/(0x1003f5f70), x: 3, local: {'x': 3, 'i': 4}
As you can see, the captured variable i
points to a same address. That's the problem.
As for the right one
def make_multipiler_the_right_way(): def make_lambda(i): # when the closure is made # i is captured/bound in the scope of `make_lambda(j)` # so it won't change anymore return lambda x: print("i: {}/({}), x: {}, local: {}".format(i, hex(id(i)), x, locals())) multipilers = [] for i in range(5): multipilers.append(make_lambda(i)) return multipilers if __name__ == "__main__": m = make_multipiler_the_right_way() for i in range(5): print(m[i](3))
Its locals
go as below
i: 0/(0x108bf1ef0), x: 3, local: {'x': 3, 'i': 0} i: 1/(0x108bf1f10), x: 3, local: {'x': 3, 'i': 1} i: 2/(0x108bf1f30), x: 3, local: {'x': 3, 'i': 2} i: 3/(0x108bf1f50), x: 3, local: {'x': 3, 'i': 3} i: 4/(0x108bf1f70), x: 3, local: {'x': 3, 'i': 4}
The underlying memory addresses for each i
in these closures are different.
Note 2.
The wrong way goes
def counter(): # init count to 0 count = 0 # the closure def add_one(): # increase count by 1 count += 1 # return the result return count # return the closure return add_one if __name__ == "__main__": count_whatever = counter() print(count_whatever()) print(count_whatever()) print(count_whatever())
However, this code won't compile
Traceback (most recent call last): File "counter.py", line 17, in <module> print(count_whatever()) File "counter.py", line 8, in add_one count += 1 UnboundLocalError: local variable 'count' referenced before assignment
As the error message suggests, variable count
in the closure add_one()
is a local variable, which implies that the closure didn't capture the variable count
in counter()
The right way is
def counter(): # init count to 0 count = 0 # the closure def add_one(): # declare that `count` is a nonlocal variable nonlocal count # increase count by 1 count += 1 # return the result return count # return the closure return add_one if __name__ == "__main__": count_whatever = counter() print(count_whatever()) print(count_whatever()) print(count_whatever())
Now we have the expected output
1 2 3
Why?
Because objects of built-in types like (int
, float
, bool
, str
, tuple
, unicode
, frozenset
) are immutable in Python.
The value of them didn't changed, not really. The fact is that the memory address the variable referred to has been changed.
>>> num = 1 >>> hex(id(num)) '0x10159ef10' >>> num += 1 >>> hex(id(num)) '0x10159ef30'
def counter(): # init count to 0 count = 0 s = "1" # the closure def add_one(): # declare that `count` is a nonlocal variable nonlocal count # increase count by 1 count += 1 # debug print("count: {}/({}), local: {}".format(count, hex(id(count)), locals())) # return the result return count # return the closure return add_one if __name__ == "__main__": count_whatever = counter() print(count_whatever()) print(count_whatever()) print(count_whatever())
The code above outputs
count: 1/(0x10397cf10), local: {'count': 1} 1 count: 2/(0x10397cf30), local: {'count': 2} 2 count: 3/(0x10397cf50), local: {'count': 3} 3