In most languages with closures, it is possible to change a closed-over value and have the change visible in other closures which share a reference to the same variable. Python's syntax makes this impossible.
Even if you're content (I am) with the work-around-- putting your changeable values inside a mutable object like a list-- it may occasionally happen that you wish you could change the closed-over values, found in "cell" objects in a function's func_closure tuple. Another recipe I submitted shows how to get at the values in standard Python; this one will demonstrate a way to actually change that value, so that functions which also close over that value (share a reference to the same cell) will see the change.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | import new, dis
cell_changer_code = new.code(
1, 1, 2, 0,
''.join([
chr(dis.opmap['LOAD_FAST']), '\x00\x00',
chr(dis.opmap['DUP_TOP']),
chr(dis.opmap['STORE_DEREF']), '\x00\x00',
chr(dis.opmap['RETURN_VALUE'])
]),
(), (), ('newval',), '<nowhere>', 'cell_changer', 1, '', ('c',), ()
)
def change_cell_value(cell, newval):
return new.function(cell_changer_code, {}, None, (), (cell,))(newval)
"""
Example use:
>>> def constantly(n):
... def return_n():
... return n
... return return_n
...
>>> f = constantly("Hi, Mom")
>>> f()
'Hi, Mom'
>>> f()
'Hi, Mom'
>>> f.func_closure
(<cell at 0xb7e1d56c: str object at 0xb7ded620>,)
>>> change_cell_value(f.func_closure[0], 46)
46
>>> f()
46
"""
|
So, how the monkey does this work?
There's no way I know of in Python to write a function that modifies one of its free variables. I'm not sure exactly why, but it's a syntactical restriction. Probably the BDFL wants to keep the rules about variable scope consistent (if you assign to a variable in a function, and the variable wasn't declared global, it's always local to the function).
Still, though, free variables go in "cell" objects, same as cellvars (variables shared by functions under the scope of a function f are cellvars in f). And the same bytecode ops work on cells either way. So until the necessary syntax is around, using straight bytecode is one way to get a cell value changed.
cell_changer_code is a code object. These can be hairy to get right, so if you're going to play around with that bit, note that you can make CPython segfault. I won't go into details here on the different values, but I will note that we create the bytecode string explicitly by concatenating the appropriate values and using the dis module to look up the right opcodes.
The change_cell_value function, then, creates a _new_ function object with cell_changer_code as its code, and puts the cell you provided in the new function's func_closure tuple. The new function needs to have a func_closure tuple of length exactly 1, by the way, since the code object's co_freevars tuple is of that length. All we have to do then is call the function and pass in the new value. The value in the cell in the closure is changed.