Closures
Closures are another feature that are heavily used in functional programming. A closure is the fact that a function can capture the value of a variable in one of its parent scopes. Let's see what this means in the following example:
def action():
print(value)
value = "hello"
action()
First, the action function is declared. This function prints the value of value even though value is neither a parameter nor a local variable. So how can this work? When the action function is being called and the print expression is executed, the interpreter searches for a reference of the value variable from the inner scope, up to the outermost scope. The interpreter searches in this order:
- Local variables
- Function arguments
- Global variables
There is no local variable or function argument that can be used here. However, when action is called, the global variable value exists and can be used. Running this snippet prints the "hello" string.
This example is not really useful but it provides you with an idea of how closures work. In real code, closures are mostly used to capture values in nested functions. A nested function is a function which is declared inside a function. Let's see this in the following example:
def act_on(what):
def exec(f):
f(what)
return exec
run1 = act_on("Hello")
run2 = act_on("World")
run1(print)
run2(lambda i: print(i.upper()))
run2(print)
run1(lambda i: print(i.upper()))
The act_on function takes a value as an argument. The act_on function contains a nested function named exec and returns it. The exec function takes a function as an argument and calls it with what as an input. In this example, when the exec function is returned by the act_on function, then what is captured as a closure. It is not a local variable, and not an argument of exec but an argument of act_on. So, the value of what when act_on is being called is captured in the function object being returned.
This way of capturing values can be seen with the code that uses the act_on function. Two variables, run1 and run2, are created with different values in the what argument. Then they are called twice, with different actions, in an interleaved way:
- Display print on run1
- Print upper case on run2
- Print on run2
- Print upper case on run1
Running this code gives the following result:
Hello WORLD World HELLO
We can see that the what values provided on the calls to act_on have been captured in the function that was returned. This captured value is correctly used in subsequent calls without having to provide it as a parameter.
Closures are used a lot in functional programming, and in Python generally. This allows you to capture behavior configuration directly in a function instead of storing this as a state in a class of another location.