Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python closure vs javascript closure

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?

like image 606
thefourtheye Avatar asked Aug 29 '13 04:08

thefourtheye


People also ask

Does Python have closures like JavaScript?

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.

Do closures exist in Python?

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.

What is the advantage of using closure in JavaScript?

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.

What is a JavaScript closure?

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.


2 Answers

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:

Use a global declaration

def 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.

Use a function attribute

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.

Force the name to be assumed non-local

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.

Use a function object

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.

Use a nonlocal declaration

def 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.

Use generators

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).

like image 57
Mario Rossi Avatar answered Oct 17 '22 06:10

Mario Rossi


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
like image 25
user2357112 supports Monica Avatar answered Oct 17 '22 05:10

user2357112 supports Monica