This is the inaugural post in an occasional series introducing more-than-basic functionality in Python. It’s commonly recognized that Python is an easy language to start learning, but not an easy language to fully master due to its size and scope. My intent here isn’t to give a systematic intro to post-basic Python but to share bits and pieces of the language that I’ve stumbled upon and found quirky or neat. Today we’ll look at how functions are objects in Python, and how to create function factories (functions that create more functions). Keep exploring!
Can you return a function from a function in Python?
That’s a leading question; of course the answer is yes. But why the heck would you ever want to return a function from a function? And how might you use this functionality?
Let’s take a step back. Did you know you can define a function inside a function? It looks something like this.
def outer(param1, param2): param1 *= param1 # 2*2 = 4 param2 *= param2 # 3*3 = 9 def inner(): return param1 + param2 # 9+4 = 13 return inner obj = outer(2,3) obj() # prints 13
That’s admittedly a silly example as the inner function doesn’t add much in this case, we could have done the addition straight in outer. But there are interesting things going on here nonetheless; namely, scope and closure!
Scope and Python closure
We didn’t pass anything to inner(), so how did inner() get access to param1 and param2? And, by the time we called obj(), outer() had already finished executing, so everything in its scope should have been long gone, but we still got 13 printed.
Once scope passes to the inner function when we call obj(), we might imagine that everything in the outer function’s scope is gone and is free game for garbage collection. This would normally be true. But, Python has a special property called closure (not to be confused with Python enclosures, a Google typo I made while writing this post).
Python closures basically protect objects in the outer function’s scope from garbage collection and leave them available for the inner function. Note, though, that only the value is remembered, so the variables are read-only inside inner (you could use the global keyword to make them mutable inside inner(), but that’s controversial).
Where do the values enclosed by the inner function live? Let’s write some code to see. We’ll look at the __closures__ attribute of the object holding our function. And then we’ll look at the contents of what __closure__ is holding to see that it’s the same values we gave to variables defined in the outer function’s scope (video below).
So far, we’ve seen:
- We can define functions inside other functions
- An outer function can return an inner function
- The closure property protects the values of variables in the outer function’s scope for the inner function (these are read-only)
How is this magic possible? It’s because functions are objects in Python. Don’t believe me? Create a function and then look at its class (video below).
Next, let’s look at how to use this capability to create function factories
Returning the same inner function every time doesn’t seem to give us very much. But, what if we could use the outer function as a factory to create different flavors of our inner function? Oh my, tell me more!
def make_word_chopper(num_words_to_keep): def chopper(word): return(word[0:num_words_to_keep]) return chopper chop2 = make_word_chopper(2) print("chop2 = ", chop2("this")) // th chop3 = make_word_chopper(3) print("chop3 = ", chop3("this")) // thi
We can create a function factory that will create functions to chop a certain number of letters from a word. Wow! This magic depends on the closure property because we specify our flavor by way of arguments sent to the outer function (num_words_to_keep). These values are used by the inner function to give us a specialized function. Because functions are made in run-time in Python, we can get our flavored function.
This is called a function factory. We might use it if we don’t want our client to know how we make our functions. This allows us to change the implementation behind the scenes as necessary without breaking the code that uses chop2 or chop3.