4

I need to monkey-patch my library to replace an instance of a symbol, and it's getting referenced by some function closures. I need to copy those functions (since I also need access to original unpatched version of the function as well), but __closure__ is immutable, and I can't copy.copy it, so how can I create new closure cells objects in Python 2.7?

I for example given this function

def f():
    def incorrectfunction():
        return 0
    def g():
        return incorrectfunction()
    return g

def correctfunction():
    return 42

func = f()
patched_func = patchit(f)   # replace "incorrectfunction"
print func(), patched_func()

And I want to see

0, 42
Yaroslav Bulatov
  • 57,332
  • 22
  • 139
  • 197

3 Answers3

9

The simple way to make a closure cell would be to make a closure:

def make_cell(val=None):
    x = val
    def closure():
        return x
    return closure.__closure__[0]

If you want to reassign an existing cell's contents, you'll need to make a C API call:

import ctypes
PyCell_Set = ctypes.pythonapi.PyCell_Set

# ctypes.pythonapi functions need to have argtypes and restype set manually
PyCell_Set.argtypes = (ctypes.py_object, ctypes.py_object)

# restype actually defaults to c_int here, but we might as well be explicit
PyCell_Set.restype = ctypes.c_int

PyCell_Set(cell, new_value)

CPython only, of course.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 1
    @YaroslavBulatov: If you just need to copy a closure cell, that's easy just by making a closure. – user2357112 Jun 06 '16 at 20:20
  • You can make a writable cell by making the closure inside a generator that stores `send` values into the local variable closed over. – Davis Herring Mar 23 '19 at 23:36
  • @DavisHerring: I was thinking more of reassigning an existing cell's contents, not a cell you control the creation of. If you control the creation, then that works. (It's easier on Python 3, where you can just use `nonlocal`, but this question was asked for Python 2.7.) – user2357112 Mar 23 '19 at 23:54
  • See https://stackoverflow.com/questions/59276834/how-to-set-the-content-of-a-closure-cell/59276835#59276835 for a way to set an existing cell's content in Python 3. – secondperson Dec 10 '19 at 23:30
2

in lambda:

def make_cell(value):
    fn = (lambda x: lambda: x)(value)
    return fn.__closure__[0]

got anwer from https://github.com/nedbat/byterun/blob/master/byterun/pyobj.py#L12

Leo Howell
  • 71
  • 6
0

If you want an empty cell (which is what I found this question for) (One where referencing it raises a NameError: free variable '...' referenced before assignment in enclosing scope and accessing it's cell.cell_contents raises a ValueError: Cell is empty), you can make a value a local variable, but never let it be assigned:

def make_empty_cell():
    if False:
        # Any action that makes `value` local to `make_empty_cell`
        del value
    return (lambda: value).__closure__[0]

You can combine them like this:

_SENTINEL = object()

def make_cell(value=_SENTINEL):
    if value is not _SENTINEL:
        x = value
    return (lambda: x).__closure__[0]

So calling with no arguments returns an empty cell, and with any value, a cell with that value.

If you don't care about empty cells, you can do:

def make_cell(value):
    return (lambda: value).__closure__[0]

Note that it is func_closure in older Python 2, instead of __closure__.

Artyer
  • 31,034
  • 3
  • 47
  • 75