56

Is there any way to modify the bound value of one of the variables inside a closure? Look at the example to understand it better.

def foo():
    var_a = 2
    var_b = 3

    def _closure(x):
        return var_a + var_b + x

    return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4
Vicent Marti
  • 7,203
  • 6
  • 30
  • 34
  • Possible in Python 3.7+; see https://stackoverflow.com/q/55320884/4880003 or Brian Adams' answer below. – Nathan May 25 '23 at 14:50

10 Answers10

53

It is quite possible in python 3 thanks to the magic of nonlocal.

def foo():
        var_a = 2
        var_b = 3

        def _closure(x, magic = None):
                nonlocal var_a
                if magic is not None:
                        var_a = magic

                return var_a + var_b + x

        return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6
print(a)

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
localClosure(0, 0)

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4
print(b)
recursive
  • 83,943
  • 34
  • 151
  • 241
  • i would too, it's certainly better than the other way. i just meant that by default it should be nonlocal – Claudiu Dec 30 '08 at 22:16
  • 4
    Doh! I meant to say I wouldn't. It still looks weird and hacky to me. The whole bit about the optional parameter to change value. Whole thing should be a class. But anyway, I digress. – recursive Dec 30 '08 at 22:35
  • for future readers, this became possible with no code changes required in 2017 with Python 3.7.0 alpha 1, see my post for details – Brian Adams Nov 30 '22 at 03:41
22

I don't think there is any way to do that in Python. When the closure is defined, the current state of variables in the enclosing scope is captured and no longer has a directly referenceable name (from outside the closure). If you were to call foo() again, the new closure would have a different set of variables from the enclosing scope.

In your simple example, you might be better off using a class:

class foo:
        def __init__(self):
                self.var_a = 2
                self.var_b = 3

        def __call__(self, x):
                return self.var_a + self.var_b + x

localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?
localClosure.var_a = 0

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4

If you do use this technique I would no longer use the name localClosure because it is no longer actually a closure. However, it works the same as one.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
11

We've done the following. I think it's simpler than other solutions here.

class State:
    pass

def foo():
    st = State()
    st.var_a = 2
    st.var_b = 3

    def _closure(x):
        return st.var_a + st.var_b + x
    def _set_a(a):
        st.var_a = a

    return _closure, _set_a


localClosure, localSetA = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
localSetA(0)

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4

print a, b
Tim James
  • 1,513
  • 1
  • 14
  • 15
11

I've found an alternate answer answer to Greg's, slightly less verbose because it uses Python 2.1's custom function attributes (which conveniently enough can be accessed from inside their own function).

def foo():
    var_b = 3

    def _closure(x):
        return _closure.var_a + var_b + x

    _closure.func_dict['var_a'] = 2
    return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?
# apparently, it is
localClosure.var_a = 0

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4

Thought I'd post it for completeness. Cheers anyways.

Vicent Marti
  • 7,203
  • 6
  • 30
  • 34
5

I worked around a similar limitation by using one-item lists instead of a plain variable. It's ugly but it works because modifying a list item doesn't get treated as a binding operation by the interpreter.

For example:

def my_function()
    max_value = [0]

    def callback (data)

        if (data.val > max_value[0]):
            max_value[0] = data.val

        # more code here
        # . . . 

    results = some_function (callback)

    store_max (max_value[0])
Justin Grant
  • 44,807
  • 15
  • 124
  • 208
2

Question

Is there any way to modify the bound value of one of the variables inside a closure?

TLDR

Yes, this is possible starting in Python 3.7.0 alpha 1:

localClosure.__closure__[0].cell_contents = 0

Details

In Python, a closure remembers the variables from the scope in which it was defined by using a special __closure__ attribute. The __closure__ attribute is a tuple of cell objects representing the variables from the outer scope, and the values of those variables are stored in the cell_contents attribute of each cell.

Given the code from the question, this can be seen by running the following:

# print the list of cells
print(localClosure.__closure__)
# (<cell at 0x7f941ca27a00: int object at 0x7f941a621950>, <cell at 0x7f941ca27eb0: int object at 0x7f941a621970>)

# print the values in the cells
print(', '.join(str(cell.cell_contents) for cell in localClosure.__closure__))
# 2, 3

# print the value in the first cell (var_a)
print(localClosure.__closure__[0].cell_contents)
# 2

The cell_contents attribute of the cell objects first became writable with bpo-30486 which was first included in Python 3.7.0 alpha 1

Complete working example:

def foo():
    var_a = 2
    var_b = 3

    def _closure(x):
        return var_a + var_b + x

    return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?

# the magic
# this changes the value in the cell representing var_a to be 0
localClosure.__closure__[0].cell_contents = 0

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 + 1 == 4

Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • I was excited that Python 3.7 had made this possible, until I read the details. I don't want to put this in my code or see it in anyone else's. It violates the expectation that the only direct access to a variable is through its name. And the expectation that the order of definition of variables shouldn't in general matter. – nclark Apr 10 '23 at 21:02
  • @nclark multiple Python core devs thought it was a reasonable thing to add so I'm sure there must be valid use cases that they considered, but yeah in general I agree that directly manipulating `__closure__` is something to avoid (unless you are doing something like "(un)pickling recursive closures" like was mentioned in the github issue) – Brian Adams Apr 12 '23 at 03:46
  • Remark: it's possible to access the `var_a` by name I believe, refer to [closures - Reflect / Inspect closed-over variables in Python - Stack Overflow](https://stackoverflow.com/questions/19416876/reflect-inspect-closed-over-variables-in-python) – user202729 Jun 04 '23 at 07:32
1

slightly different from what was asked, but you could do:

def f():
    a = 1
    b = 2
    def g(x, a=a, b=b):
        return a + b + x
    return g

h = f()
print(h(0))
print(h(0,2,3))
print(h(0))

and make the closure the default, to be overridden when needed.

Lars
  • 1,869
  • 2
  • 14
  • 26
1

Maybe there's a further approach (even if it seems to be some years too late for my proposal :-)

def foo():
    def _closure(x):
        return _closure.var_a + _closure.var_b + x
    _closure.var_a = 2
    _closure.var_b = 3
    return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1)  # 2 + 3 + 1 == 6
print(a)

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?
localClosure.var_a = 0

# Local closure is now "return 0 + 3 + x"
b = localClosure(1)  # 0 + 3 +1 == 4
print(b)

From my point of view the class solution proposed is easier to read. But if you try to modiy a free variable inside a decorator this solution might come in handy: In comparison to a class based solution it's easier to work with functools.wraps to preserve the meta data of the decorated function.

Volker
  • 453
  • 1
  • 4
  • 6
0

Why not make var_a and var_b arguments of the function foo?

def foo(var_a = 2, var_b = 3):
    def _closure(x):
        return var_a + var_b + x
    return _closure

localClosure = foo() # uses default arguments 2, 3
print localClosure(1) # 2 + 3 + 1 = 6

localClosure = foo(0, 3)
print localClosure(1) # 0 + 3 + 1 = 4
titaniumdecoy
  • 18,900
  • 17
  • 96
  • 133
  • That's doesn't quite answer my problem, because I need to modify the values of closures which are already created. I.e. your answer requires me to create a new closure everytime I need to change the bound variables. – Vicent Marti Dec 24 '08 at 23:57
0
def foo():
    var_a = 2
    var_b = 3

    def _closure(x):
            return var_a + var_b + x

    return _closure

def bar():
        var_a = [2]
        var_b = [3]

        def _closure(x):
                return var_a[0] + var_b[0] + x


        def _magic(y):
            var_a[0] = y

        return _closure, _magic

localClosureFoo = foo()
a = localClosureFoo(1)
print a



localClosureBar, localClosureBarMAGIC = bar()
b = localClosureBar(1)
print b
localClosureBarMAGIC(0)
b = localClosureBar(1)
print b
slothy
  • 363
  • 1
  • 3
  • 13