The following closure function works fine in javascript.
function generateNextNumber(startNumber) {
var current = startNumber;
return function(){
return current += 1;
}
}
var getNextNumber = generateNextNumber(10);
for (var i = 0; i < 10; i++) {
console.log(getNextNumber());
}
I tried to do the same in Python
def generateNextNumber(startNumber):
current = startNumber
def tempFunction():
current += 1
return current
return tempFunction
getNextNumber = generateNextNumber(10)
for i in range(10):
print (getNextNumber())
I get the following error
Traceback (most recent call last):
File "/home/thefourtheye/Desktop/Test1.py", line 10, in <module>
print (getNextNumber())
File "/home/thefourtheye/Desktop/Test1.py", line 4, in tempFunction
current += 1
UnboundLocalError: local variable 'current' referenced before assignment
When I printed the vars()
and locals()
inside tempFunction
, they confirm that current
is present.
({'current': 10}, {'current': 10})
But when I modified the program a little like this
def generateNextNumber(startNumber):
current = {"Number" : startNumber}
def tempFunction():
current["Number"] += 1
return current["Number"]
return tempFunction
it works. I can't reason why this works. Can anyone explain please?
Python closuresA closure is a nested function which has access to a free variable from an enclosing function that has finished its execution. Three characteristics of a Python closure are: it is a nested function. it has access to a free variable in outer scope.
Python Decorators make an extensive use of closures as well. On a concluding note, it is good to point out that the values that get enclosed in the closure function can be found out. All function objects have a __closure__ attribute that returns a tuple of cell objects if it is a closure function.
Advantages of closuresThey allow you to attach variables to an execution context. Variables in closures can help you maintain a state that you can use later. They provide data encapsulation. They help remove redundant code.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.
Python assumes that all variables in a function are local. This is to avoid accidental use of a global variable of the same name, or in an enclosing scope. In some important way, this difference is consequence of the fact that in Python local variable declaration is automatic/implicit while in JavaScript it is not (you have to use var
). Solutions:
global
declarationdef generateNextNumber(startNumber):
global current
current= startNumber
def tempFunction():
global current
current += 1
return current
return tempFunction
Valid in some cases, but in yours only one instance of tempFunction
could be active at the same time.
def generateNextNumber(startNumber):
def tempFunction():
tempFunction.current += 1
return tempFunction.current
tempFunction.current= startNumber
return tempFunction
Uses the fact that functions are objects (and thus can have attributes), that they are instantiated when they are declared, and that they become local to the enclosing function (or module, in which case they are really global). This also works because the name tempFunction
is used for the first time inside its own definition with a "member access" .
operator and thus not assumed local. Something similar happens with the "call" ()
, and "element access" []
operators. The later case explains why your code works.
def generateNextNumber(startNumber):
current= type("OnTheFly",(),{})()
current.value= startNumber
def tempFunction():
current.value += 1
return current.value
return tempFunction
This was already explained in the previous section. By using the member access operator .
we are saying "current
already exists", and thus it's searched in the enclosing scope. In this particular case, we are creating a class using the type
function and immediately creating an instance of it (with the second set of parantheses). Instead of a general object, we could have also used a list or a dictionary. The second case was a very common solution.
def generateNextNumber(startNumber):
class TempFunction:
def __call__(self):
self.current += 1
return self.current
tempFunction= TempFunction()
tempFunction.current= startNumber
return tempFunction
Any object whose class has a call method is a function and thus can be called with the function call operator ()
. This is extremely related to the two previous cases.
nonlocal
declarationdef generateNextNumber(startNumber):
current= startNumber
def tempFunction():
nonlocal current
current += 1
return current
return tempFunction
In the same way that global
means... well, global, nonlocal
means "in the immediately preceding scope". Valid in Python 3 and maybe later versions of Python 2.
def generateNextNumber(current):
while True :
current+= 1
yield current
This is probably the most "Pythonic" way to approach not the general problem of nonlocal variable access, but the specific case you used to explain it. I couldn't finish without mentioning it. You need to call it with a minor change, though:
getNextNumber = generateNextNumber(10)
for i in range(10):
print (getNextNumber.next())
When driving a for
the call to next()
is implicit (but the generator can not be infinite as in my example).
Python decides what a function's local variables are by defining that any variable a function contains an assignment for is local unless declared nonlocal
or global
. Thus,
current += 1
creates a local variable named current
that hides the nonlocal variable. If you're on Python 2, the standard solution (besides trying not to do this) is to make current
a 1-element list and use
current[0] += 1
For reference, "trying not to do this" might look something like the following:
class Counter(object):
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
c = Counter()
c() # Returns 1
c() # Returns 2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With