00:12 Quoting from Wikipedia, “A closure is a technique for implementing lexically scoped name binding in a language with first-class functions.” Well, that’s nice and clear, isn’t it? Closures, also known as function closures or lexical closures, are an instance of a function that stores an environment.
00:31 This is usually accomplished by a function returning a reference to a function, and that returned function having access to some state. This state is called a captured variable—or captured variables.
00:45 You’ve already seen how an inner function in Python has access to containing state. The only added step to making a closure is having the containing function return a reference to the inner function. Fancy term, simple idea—an outer function that returns a reference to an inner function, and because it is an inner function, it can contain captured state by way of scope variables. Closures, that’s it. The concept of closures is a key tool for functional programming, which was first introduced in LISP in the 1960s.
01:17 Python is considered a multi-paradigm language because it handles procedural, object-oriented, and functional programming styles. To its benefit, this means you can use the right style for the job in question. To its detriment, critics might accuse it of being a jack-of-all-trades and a master of none.
01:34 Here is a closure in action. As promised, there is an outer function and an inner function. The containing function returns a reference to the inner function on line 7, hence closure. So, what does this one do?
The result, which I’ve called
base2, is a reference to a function which when called will raise 2 to the power of its argument. Calling it with
2 and you get 2 squared. Doing it again with
4, and you get the idea.
and I’ve created another closure without affecting the first. This one’s
base3. Calling it with the argument of
2 and you get 3 squared. To show that
base2 is unaffected, let me call it again. 8 is great! There you have it, your first closure.
03:05 The captured variables in a closure aren’t static. You can change them as a side-effect of calling the closure. In the top area, I have defined a closure that stores values and returns their mean on each call.
Because functions are just objects like everything else in Python, you can add attributes to them. You want functions on your functions? No problem! In fact, this is how most functional programming languages implement object-oriented style coding. Here, on lines 13 and 14, I’ve defined the environment for the closure. Like before, I’m tracking a list of numbers, but this time I’m also going to track the current value of the mean. The closure part is almost the same, defining
inner_mean() on line 16.
Instead of just calculating the result, this time I’m storing it in
current. Notice the declaration of
nonlocal on line 17. Without this, Python has no way of knowing that you’re trying to use the value in the enclosing scope rather than defining a new variable.
This can be a bit tricky. Why wasn’t this needed for
sample? Well, it has to do with how the variable is used. The
sample object is referenced, not assigned. On line 20, though,
current is assigned to. Python’s default behavior here is to override the variable in the outer scope. Without the
current would only be in scope inside of
inner_mean(), which would work for the calculation but the result wouldn’t be stored in the closure.
On line 23, I’ve defined another inner function. This one is called
value(). It also uses the
nonlocal keyword and returns the contents of
current. In the old version of
inner_mean(), you could only get the value of the mean by adding something to the closure. With this
value() method, you can see the average without changing the closure.
Line 32 is where I associate the
value() inner function with the closure object. By assigning the
value inner function to the
inner_mean object—functions are just objects—you can now call a function on this function.
On line 27, in the area in the top, I define another inner function, this one called
reset(). It resets the
sample list to empty and sets the average to
0. This time, because I’m assigning
sample, I have to use the
nonlocal keyword like I did with
current inside of
07:06 The average tracker is back to empty. At this point, you’re getting to a place where you’re not far from object-oriented coding. You’re likely better off just declaring a class and using objects.
But just like I’ve set the attribute of
inner_mean.value as a function, I could set
inner_mean.current as an integer. Once again, I’d be using the closure as an object, Instead of declaring
nonlocal, you just use
inner_mean.current everywhere where you currently are using
current. Feeling festive?
Become a Member to join the conversation.