Programmer's Python - Closure |
Written by Mike James | |||
Monday, 19 April 2021 | |||
Page 2 of 2
It is possible to create more than one inner function at a time. The question is, do they each get a unique closure or do they share the same variable? The key to understanding this is that both functions share the same execution context and so they share the variables in it. For example: def MyFunction(): myVar=1 def myInnerFunction1(): nonlocal myVar myVar=myVar+1 print(myVar) def myInnerFunction2(): nonlocal myVar myVar=myVar+1 print(myVar) return myInnerFunction1,myInnerFunction2 This is just two inner functions each adding one to myVar when called. The only new thing in this example is the use of a tuple to return multiple results – this is explained in detail in the next chapter. Now we can call both functions: Closure1,Closure2 = MyFunction() Closure1() Closure2() The result is that you see 1 followed by 2 displayed. The two functions are incrementing the same copy of myVar captured by the closure. The final detail is to determine what value is captured by the closure. Consider the following example: def MyFunction(): myVar=1 def myInnerFunction1(): nonlocal myVar print(myVar) myVar=2 def myInnerFunction2(): nonlocal myVar print(myVar) return myInnerFunction1,myInnerFunction2 In this case myVar is set to 1 when myInnerFunction1 is defined and to 2 when myInnerFunction2 is defined. What values will be printed the functions are called: Closure1,Closure2 = MyFunction() Closure1() Closure2() The answer is you will see 2 displayed twice. There is only one captured variable per execution context and when the functions are called it has the final value that the containing function assigned to it.
A Common ErrorNot understanding that there is just one execution context leads to the following common problem with closures. Suppose you want to create ten functions each with its own identifying state variable. You might try to do it something like: def MyFunction(): f=[] for i in range(0,10): myVar=i def myInnerFunction(): nonlocal myVar print(myVar) f.append(myInnerFunction) return f This creates a list of ten functions, and you can store references to functions in a list because they are objects just like any other. Each function is created with myVar as a closure and the idea is that each function gets its own closure and own value i.e. the current value of i. However, you should know that this is not what happens and when the functions are used: f = MyFunction() for i in range(0,10): f[i]() what you see is 9 printed ten times because the closure that all of the function share captured the final value of the variable. If you want to make this work you have to either make sure that each function has a different variable in the closure or make sure that each function is created in a different execution context – neither option is easy. To create a new execution context you have to define a function to wrap the function you really want to create: def MyFunction(): f=[] def temp(): myVar=i def myInnerFunction(): print(myVar) f.append(myInnerFunction) for i in range(0,10): temp() return f The temp function is in the loop to create a new execution context complete with a new local variable myVar. Notice that temp is called ten times in the loop, each time it creates a separate local variable referenced by myVar and hence a new execution context for myInnerFunction to be defined in. Notice also that temp gets access to the list f via a closure. This sort of coding is best avoided if possible. Not included in this extract:
Summary
Programmer's Python
|
|||
Last Updated ( Wednesday, 04 May 2022 ) |