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