jadengore
jadengore

Reputation: 473

Using a closure with multiple functions without making variable global in Python

I'm trying to make an example program in Python 2.7 which saves/shares states between two functions. You call a function, next time you call a function, it should remember the previous value. Here is my current code:

def stuff():
    global x 
    x = 100
    def f():
        global x
        x = x * 2
        return x
    def g():
        global x
        x = x * 4
        return x
    return (f, g)

a,b = stuff()
print(a());

This code works, BUT the catch is that x must not be considered as a global variable outside the scope of stuff()... (That is the whole point of embedding x within stuff() in the first place). So, would x be global, or is it local to stuff()?

Upvotes: 3

Views: 2558

Answers (4)

Lennart Regebro
Lennart Regebro

Reputation: 172229

Easiest and least hacky solution, use a class:

class stuff(object):
    x = 100
    def f(self):
        self.x = self.x * 2
        return self.x

    def g(self):
        self.x = self.x * 4
        return self.x

Result:

>>> s = stuff()
>>> s.f()
200
>>> s.g()
800

You want to prevent people from accessing x from outside the functions at all. The way you do that in Python is by prefixing it with an underscore:

class stuff(object):
    _x = 100
    def f(self):
        self._x = self._x * 2
        return self._x

    def g(self):
        self._x = self._x * 4
        return self._x

This tells other Python programmers that it is internal, and not to access it, except on their own risk. You seem to want to prevent even this, but you can't. You can even access the closure in the Python 3 nonlocal example as well:

def stuff():
    x = 100
    def f():
        nonlocal x
        x = x * 2
        return x

    return f

>>> a = stuff()
>>> a()
200
>>> a()
400
>>> a.__closure__[0].cell_contents
400

So you aren't preventing anyone from fiddling with it, you just make the fiddling it obscure and brittle. and more likely to fail. As such you just end up making things more difficult for everyone involved.

Upvotes: 3

Evan
Evan

Reputation: 2357

If you need to support python 2.X, @georgek's answer is the best, but a lot of people don't realize that you can add attributes to functions. A common idiom is to use a list of a single element to hold the variable.

def stuff():
    x = [100]
    def g():
        x[0] = x[0]*2
        return x[0]
    def h():
        x[0] = x[0]*4
        return x[0]
    return (g,h)
a, b = stuff()
print(a())

This works because you never assign to x itself in the internal scopes, so it doesn't rebind the variable and shadow the closure.

Upvotes: 3

georgek
georgek

Reputation: 877

You can use a function without global:

>>> def f():
...     f.x = 100
...     def g():
...             f.x = f.x * 2
...             return f.x
...     def h():
...             f.x = f.x * 4
...             return f.x
...     return (g, h)
...
>>> a, b = f()
>>> a()
200
>>> b()
800
>>> a()
1600
>>>

Upvotes: 0

falsetru
falsetru

Reputation: 369034

In Python 3.x, you can use nonlocal statement:

def stuff():
    x = 100
    def f():
        nonlocal x
        x = x * 2
        return x
    def g():
        nonlocal x
        x = x * 4
        return x
    return f, g

>>> a, b = stuff()
>>> a()
200
>>> b()
800
>>> a()
1600

Upvotes: 4

Related Questions