Daniil Grankin
Daniil Grankin

Reputation: 3933

Closure in python. Can I make closure on local context of function?

In javascript I can write function with closure like this

function getUniqueIDfunction() { 
    var id = 0;                          
    return function() { return id++; }; 
};

And then use it

uniqueID = getUniqueIDfunction();
uniqueID(); //return 0
uniqueID(); //return 1
...

Can I perform the same in Python (if it depends with different version let me know) ?

def getUniqueIDfunction():
    x = -1
    def foo():
        #And I know that it doesn't work with row bellow and without it    
        #global x  
        x += 1
        return x
    return foo

It's just a sample. I want know about closure in Python.

Upvotes: 2

Views: 241

Answers (4)

Aaron Hall
Aaron Hall

Reputation: 395633

This works, but doesn't do exactly what you want:

def getUniqueIDfunction():
    x = -1
    def foo(x=x):
        x += 1
        return x
    return foo
f() # returns 0
f() # returns 0 again!

Because the integer datatype is immutable. If instead you use a mutable datatype:

def counter():
    x = [0]
    def enc():
        x[0] = x[0] + 1
        return x[0]
    return enc
f = counter()
f() # returns 1
f() # returns 2
f() # returns 3

Another more complicated example from my own usage:

def enumerate_dupes_in_column():
    '''
    provides a dict for counting in the namespace and a function for the
    operation, thus avoiding global variable naming
    '''
    countdict = {}
    def countfunction(arg):
        countdict[arg] = countdict.get(arg, 0) + 1
        if countdict[arg] == 1: 
            return arg
        else: 
            return arg + ', ' + str(countdict[arg])
    return countfunction

f = enumerate_dupes_in_column()
f('foo') # returns foo
f('bar') # returns bar
f('foo') # returns foo, 2

Upvotes: 2

Kevin Ji
Kevin Ji

Reputation: 10499

If all you want is a unique ID, just use the following:

def uniqueID():
    x = 0
    while True:
        yield x  
        x += 1

id = next(uniqueID)

You can rewrite this with a closure (as poke mentions in his answer), if you wish to:

def getUniqueIDfunction():
    x = -1
    def uniqueID():
        nonlocal x
        x += 1
        return x
    return uniqueID

uniqueID = getUniqueIDfunction()
id = uniqueID()

This has the caveat that it only works in Python 3+. For Python 2, you can simulate this behavior by attaching the value x to a class.

Upvotes: 2

poke
poke

Reputation: 388163

Python 3 introduced this kind of scoping behavior with PEP 3104 and the nonlocal statement:

>>> def uniqueId ():
        x = -1
        def inner ():
            nonlocal x
            x += 1
            return x
        return inner

>>> f = uniqueId()
>>> f()
0
>>> f()
1
>>> f()
2

Other than that, in previous versions, closures do exist, but you have only a read-only access. So changing x will not work. What you can do however is use a mutable object, like a list, and change that one:

>>> def uniqueId ():
        x = [-1]
        def inner ():
            x[0] += 1
            return x[0]
        return inner

>>> f = uniqueId()
>>> f()
0
>>> f()
1

As you can make any kind of object callable, you can also do something more fancy by defining your own type that has a __call__ method:

>>> class UniqueId:
        def __init__ (self):
            self.x = -1
        def __call__ (self):
            self.x += 1
            return self.x

>>> f = UniqueId()
>>> f()
0
>>> f()
1

Upvotes: 5

abarnert
abarnert

Reputation: 365975

If you want to explicitly specify that something is a closure variable, not a local or global, you use the nonlocal statement. So:

def foo():
    nonlocal x  
    x += 1
    return x

In Python 2.x, there is no nonlocal statement. Your best bet is to upgrade to a modern version of the language. If you can't do that, there are ways to fake it, which are explained in the FAQ, and in PEP 3104 (which introduced nonlocal).

Upvotes: 1

Related Questions