Lists and Nonlocal in Python
A closure is a function with a parent frame that contains some data. If the parent frame can be modified, the function is said to be mutable.
For example, take the following doctest:
>>> c, d = make_counter(), make_counter() >>> c() 0 >>> c() 1 >>> c() 2 >>> [c(), d(), d(), d(), c()] [3, 0, 1, 2, 4]
c is called multiple times and returns a different value every time; but its state isn’t global as there is the
d function which also counts up but on its own tineline.
The Python programming language allows for the creation of mutable closures in two different ways: one that is traditionally considered “functional” and one that is traditionally considered “object-oriented”. Let’s take a look at them now:
Nonlocal: Functional mutable closures
The most obvious way to implement the
make_counter function is as follows:
def make_counter(): current_value = 0 def c(): nonlocal current_value result = current_value current_value += 1 return result return c
nonlocal current_value ensures that the
current_value variable assigned in the
c frame refers to the value from the parent frame. Thus we have shared state (the
current_value variable) which can be modified, the necessary components of a closure.
Lists: Object-oriented mutable closures
But do we really need anything that complicated? What if we did this?
def make_counter(): return [6, 5, 4, 3, 2, 1, 0].pop
Every time we call the resulting function, we get 0, 1, 2, 3, 4, 5, and then 6. This isn’t quite right, as it doesn’t go on forever, but it’s definitely a mutable function, and we never used nonlocal. What’s going on here? Let’s unroll the function somewhat:
def make_counter(): lst = [6, 5, 4, 3, 2, 1, 0] def c(): return lst.pop() return c
Here we have that we can modify
lst from the parent frame without assigning to it, since the contents of
lst rather than the variable itself is mutated. We can exactly duplicate the original
make_counter function as such:
def make_counter(): current_value =  def c(): result = current_value current_value += 1 # looks like an assignment return result return c
Now, this looks like there’s an assignment to
current_value on the commented line, but in fact,
current_value += 1 is equivalent to
current_value = current_value.__iadd__(1), which is equivalent (for numbers) to
current_value = current_value + 1, and that is equivalent to
current_value.__setitem__(0, current_value + 1), which is not in fact an assignment.
Complications: Nonlocal on lists
Let’s say we write a few alternatives for
def make_counter_append(): current_value =  def c(): result = current_value[-1] current_value.append(current_value[-1] + 1) return result return c def make_counter_extend(): current_value =  def c(): result = current_value[-1] current_value.extend([current_value[-1] + 1]) return result return c def make_counter_plus_equals(): current_value =  def c(): result = current_value[-1] current_value += [current_value[-1] + 1] return result return c
Interestingly, the first two work but the last does not. Why? Aren’t
a += b and
a.extend(b) equivalent? Actually, they’re almost equivalent.
a += b is equivalent to
a = a.__iadd__(b), where
__iadd__ is a special function that you can choose to implement on your class. In the case of Python,
list.__iadd__ is equivalent to
list.extend except that it returns a reference back to the same list.
Therefore, if you want to use
a += b where
a is a list, you thus need either to make
a nonlocal or use