Welcome, guest | Sign In | My Account | Store | Cart

Python closures hold closed-over values in a special datatype called a "cell"; a sort of indirect pointer. It's not simple, though, to see what values are stored there. Here's the key.

Python, 46 lines
 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
39
40
41
42
43
44
45
46
def get_cell_value(cell):
    return type(lambda: 0)(
        (lambda x: lambda: x)(0).func_code, {}, None, None, (cell,)
    )()



# longer and more verbose version:
import new
def get_cell_value(cell):
    def make_closure_that_returns_value(use_this_value):
        def closure_that_returns_value():
            return use_this_value
        return closure_that_returns_value
    dummy_function = make_closure_that_returns_value(0)
    dummy_function_code = dummy_function.func_code
    our_function = new.function(dummy_function_code, {}, None, None, (cell,))
    value_from_cell = our_function()
    return value_from_cell



# examples
>>> def make_list_appender(mylist):
...   def append_to_mylist(newvalue):
...     mylist.append(newvalue)
...     return newvalue
...   return append_to_mylist
... 
>>> somelist = []
>>> somelist_appender = make_list_appender(somelist)
>>> somelist_appender(2)
2
>>> somelist_appender(3)
3
>>> somelist
[2, 3]
>>> somelist_appender
<function append_to_mylist at 0xb7df556c>
>>> somelist_appender.func_closure
(<cell at 0xb7e1d38c: list object at 0xb7e0f26c>,)
>>> cell = somelist_appender.func_closure[0]
>>> get_cell_value(cell)
[2, 3]
>>> get_cell_value(cell) is somelist
True

This recipe is mainly useful for debugging and introspection purposes. When you have a closure object, and want to get at the precise values over which it is closed, this will come in handy.

Now, how does it work? What's with all those lambdas?

The longer, more verbose version of get_cell_value should help to explain it. The first lambda is just used as an argument to type() so we can get at the function constructor. The longer version uses new.function() instead, but I think the "new" module is deprecated. We might also have used type(get_cell_value), but that would make it less convenient to rename the function. Plus, I believe the lambda:0 method is just a tiny bit faster than those others (just takes a MAKE_FUNCTION opcode instead of looking up a global or an attribute).

What we want to do with that function constructor is make a closure that simply returns the value closed over, and create a new function with that same code but closed over our cell-- the one passed to get_cell_value-- instead.

First, we make the closure that simply returns the value closed over:

(lambda x: lambda: x)(0)

That corresponds to dummy_function from the longer get_cell_value. We close it over a zero value (0) just because it's a convenient. It doesn't matter what the value is, since we only want the closure code.

We extract the code object from that closure's func_code attribute. Code objects can refer to values closed over, but not actually store them. So that code object is the first step in constructing our new function.

The function constructor takes up to five arguments (try "print type(lambda:0).__doc__" yourself).

We have the code object. The globals dictionary can be empty, since the function refers to no globals. The name can be None, meaning the name should be taken from the code object (not a very useful name, but it doesn't really matter). There are no default argument values, so the argdefs parameter is also None. Finally, we provide the closure argument, which expects a tuple of cell objects. We pass in the cell we were given.

Once the constructed function is complete, we simply call it and return the result.

3 comments

Ori Peleg 18 years, 7 months ago  # | flag

Nice!

Tom Locke 18 years, 7 months ago  # | flag

You can also do this with ctypes. With the ctypes module (at least 0.9.1), you can also do this with a simple call to PyCell_Get. Watch out though, the function needs to be told about its type signature before it works (at least that was my experience, maybe future ctypes releases will fix this)

>>> import ctypes
>>> cellget = ctypes.pythonapi.PyCell_Get
>>> cellget.restype = ctypes.py_object
>>> cellget.argtypes = (ctypes.py_object,)
>>> def f(x): return lambda: x
...
>>> l = f(10)
>>> cellget(l.func_closure[0])
10
Wi 10 years, 12 months ago  # | flag

7 years passed and still this page is the first link from google. Second google link suggests new much easier way:

def get_cell_value(cell):
    return cell.cell_contents
Created by paul cannon on Thu, 11 Aug 2005 (PSF)
Python recipes (4591)
paul cannon's recipes (5)

Required Modules

  • (none specified)

Other Information and Tasks