Function Closures
00:00 In the previous lesson, I introduced you to the basics of inner functions and talked about how you decide to use them. In this lesson, I’ll build on the basics by showing you function closures.
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?
01:48
It creates a function that does exponent math. When you call generate_power()
, you pass in the base
—a number that will be the base of the exponent in question.
01:58
generate_power()
returns a reference to to_the_power_of()
, which is what does the actual math. Let’s try it out. First, I’ll import the function.
02:11
Now I call generate_power()
with an argument of 2
.
02:18
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.
02:35
The base2
closure is self-contained. I can use generate_power()
again,
02:43
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.
03:17
Line 3 defines a list where the values will be stored. Line 6 inside the closure adds the number
passed in to the sample
list and then line 7 calculates the mean and returns it.
03:29
Let’s see this in action. Importing the closure creator, defining a closure called height
,
03:40
and when I call the closure, it stores 100
and then calculates the average. The average of one item, of course, is itself. Let’s add some more data, and more,
03:56 and you could keep going! The contents of the closure are being updated and the changes reflected in the returned result.
04:05
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.
04:35
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.
04:52
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 nonlocal
keyword, 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.
05:22
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.
05:43 Let me just scroll down a bit.
05:48
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.
06:02
Let’s see this in practice. I’ll import even_meaner
,
06:13
and add a temperature. And another one. One more. Now I can call the value()
method on the closure. It returns the current average without having to change it.
06:32
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 value()
.
06:50
Then on line 33, I associate that inner function with the closure so that it can be called just like value()
. Let me call reset()
,
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.
07:17
The value()
method here really is a getter. The reset()
method is a setter—a very specific setter that can only set to zero, but a setter nonetheless.
07:26 In a language like LISP, you don’t have a choice. You would have to do it this way. In Python, this would be the point where I would just switch to using a different method.
07:35
That being said, the power’s here if you want to use it. One last thing. The nonlocal
keyword was introduced in Python 3. If you need to do this in Python 2, you can’t do it this way.
07:48
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?
08:11 Decorate your code! Next up, closures on data functions, a.k.a. decorators.
Christopher Trudeau RP Team on July 13, 2021
Hi @prasadzende,
You either have to use nonlocal
or the function value assignment mechanism I mentioned in the lesson. The global
keyword indicates that the value is part of the module namespace and would not give you the result of the item inside of the closure.
If you used a global variable instead and you had two instances of the closure, you’d be overwriting each other’s values.
Hope that provides some clarity.
prasadzende on July 22, 2021
Thanks for the clarification.
Tony Florido on Dec. 27, 2021
Hi, first of all congrats for your tutorials I’m really having fun with them. I’ve a question about lines 32 & 33. What is the need of them? If I comment those lines it works as expected, for sure I’m missing something here.
Christopher Trudeau RP Team on Dec. 27, 2021
Hi Tony,
I’m glad you’re enjoying the course. Lines 32 and 33 make the value()
and reset()
functions available to the closure. The closure itself will still work – things get added to the mean, but you won’t be able to do temps.value()
or temps.reset()
.
Without 32 and 33, the value
and reset
functions are defined under even_meaner()
, but not available as part of the inner_mean
function, and thus not part of the closure.
Try commenting them out, then call temps.value()
and you should get a not-defined exception.
Tony Florido on Dec. 27, 2021
Ok, now I see it :)
Thanks for your quickly response
Become a Member to join the conversation.
prasadzende on July 13, 2021
Can I use ‘Global’ instead of ‘Nonlocal’ ?