PythonFunctionClosures

Python Function Closures

=python # Some Notes on Function Closures in Python # By Simon Cross # Consider: def make_a_nested_function(): i = 5 def nested(): return i return nested # The above will happily create a function which # returns 5. However, there is a slight subtly, # the i referenced in nested() is not a local name # which points to the value of i at the time nested() # is created, but rather a cell object which looks # up the value of the local name i in make_a_nested_function() # at the time when nested() is *called*. # # Creating functions inside a loop illustrates this better: def make_loopy_functions(): a = [] for i in range(5): def nested(): return i a.append(nested) return a # All the functions in a[:] are different but each of them returns # 4 (the value of i when make_loopy_functions returns). # # If we look at the first function in a[:], f0, we'll see that # f0.func_closure is a tuple with a single cell object. This is # the cell object which points to the name i in the make_loopy_functions # code block. # # (The Python language reference has a little bit to say about this: # http://docs.python.org/ref/naming.html) # # We can access the value of the cell from Python, but it's a little # tricky. # # (code taken from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/439096) import types def get_cell_value(cell): return types.FunctionType( (lambda x: lambda: x)(0).func_code, {}, None, None, (cell,) )() # or written out more clearly: def get_cell_value(cell): def foo(x): def foo2(): return x return foo2 cell_value_getter = types.FunctionType( foo(0).func_code, {}, None, None, (cell,) ) return cell_value_getter() # The trick used here is to create a function which has a single cell and # which returns the value of that cell (foo2 is such a function, calling foo # actually creates it). # # After calling foo(0), we have a function, foo2, with one cell # (name x in foo, value 0). We then use the function code from this function # as the code for a new function which is the same as foo2 except that the # func_closure (last argument to FunctionType) has been replaced with with # cell we passed in to get_cell_value. # # Finally we call cell_value_getter (which remember just returns the value of # "x" (i.e. the first cell in the function closure) and return the result. # # Writing to a cell is even more tricky, and requires creating a function # using Python bytecode: # # (code taken from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440515) import dis cell_changer_code = types.CodeType( 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 types.FunctionType(cell_changer_code, {}, None, (), (cell,))(newval) # This uses a similar trick to get_cell_value(..) # and creates a new function whose closure include the cell you pass in # to change_cell_value. The cell_changer_code is constructed manually from # Python bytecode. Beyond that, it's a bit of a mystery. # # You can read up on the Python bytecodes at: http://docs.python.org/lib/bytecodes.html # # LOAD_FAST 0 # put first (0) argument onto stack # DUP_TOP # duplicate top argument on stack # STORE_DEREF 0 # store value from top of stack in first (0) cell variable # RETURN_VALUE # return the value from the top of the stack (remember we duplicated it in step 2 :) # A clear / better way of doing closure in Python is to create # a callable class which takes the values that would be # put into func_closure cells as argument: class loopy_func(object): def __init__(self,i): self.i = i def __call__(self): return self.i def make_loopy_classes(): a = [] for i in range(5): a.append(loopy_func(i)) return a # Another work around is to move the function creation outside of the # loop into another function: def make_a_loop(i): def nested(): return i return nested def make_loopy_functions2(): a = [] for i in range(5): a.append(make_a_loop(i)) return a # Addendums: # # [29 May 2007]: Mitsuhiko (http://programming.reddit.com/user/mitsuhiko/) # pointed out that from Python 2.5 onwards one can # use the .cell_contents attribute of cell objects to read # a cell's current value.

Download the notes.

Edit - History - Print - Recent Changes - Search
Page last modified on May 28, 2007, at 11:57 PM