PythonFunctionClosures

HstarTalks.PythonFunctionClosures History

Hide minor edits - Show changes to output

May 28, 2007, at 11:57 PM by hodge - From Python 2.5 one has access to cell_contents.
Added lines 150-156:
# 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.

May 25, 2007, at 10:39 PM by hodge - Fix typo (to celebrate making the Daily Python URL).
Changed line 34 from:
# All the functions in a[:] are different but each of the returns
to:
# All the functions in a[:] are different but each of thethem returns
May 14, 2007, at 10:28 AM by hodge - Notes from function closure talk.
Changed lines 150-152 from:
=]
to:
=]

[[Attach:function-closure-notes.py|Download the notes]].

May 14, 2007, at 10:21 AM by hodge - Talk notes.
Added lines 1-150:
!!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 the 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

=]

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