Как создать новые объекты замыкающей ячейки?

Мне нужно исправить мою библиотеку, чтобы заменить экземпляр символа, и на него ссылаются некоторые замыкания функций. Мне нужно скопировать эти функции (поскольку мне также нужен доступ к исходной неисправленной версии функции), но __closure__ является неизменяемым, и я не могу copy.copy его, так как я могу создать новые объекты замыкающих ячеек в Python 2.7?

Я, например, дал эту функцию

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()

И я хочу видеть

0, 42

person Yaroslav Bulatov    schedule 06.06.2016    source источник
comment
Достаточно ли хорош functools.partial(f, 2)()?   -  person Ashwini Chaudhary    schedule 06.06.2016
comment
Нет, потому что я использую его для латания обезьян. IE, мне нужно запустить функцию patchit для всех символов, чтобы заменить старый символ, содержащийся в замыканиях, на символ с исправлением обезьяны   -  person Yaroslav Bulatov    schedule 06.06.2016
comment
извините, я не понял, заменил мой игрушечный пример более реалистичной версией   -  person Yaroslav Bulatov    schedule 06.06.2016


Ответы (3)


Простым способом сделать замыкающую ячейку было бы сделать замыкание:

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

Если вы хотите переназначить содержимое существующей ячейки, вам нужно создать C вызов API:

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, конечно.

person user2357112 supports Monica    schedule 06.06.2016
comment
@YaroslavBulatov: Если вам просто нужно скопировать ячейку закрытия, это легко сделать, просто сделав замыкание. - person user2357112 supports Monica; 06.06.2016
comment
Вы можете сделать ячейку доступной для записи, сделав замыкание внутри генератора, который хранит send значений в закрытой локальной переменной. - person Davis Herring; 24.03.2019
comment
@DavisHerring: я больше думал о переназначении содержимого существующей ячейки, а не о ячейке, создание которой вы контролируете. Если вы контролируете создание, то это работает. (В Python 3 это проще, где вы можете просто использовать nonlocal, но этот вопрос был задан для Python 2.7.) - person user2357112 supports Monica; 24.03.2019
comment
См. stackoverflow.com/questions/59276834/ для способа установки содержимого существующей ячейки в Python 3. - person secondperson; 11.12.2019

в лямбда:

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

получил ответ от https://github.com/nedbat/byterun/blob/master/byterun/pyobj.py#L12

person Leo Howell    schedule 21.06.2017
comment
@Davis Herring извините за это, исправлено - person Leo Howell; 27.03.2019

Если вам нужна пустая ячейка (именно для этого я нашел этот вопрос) (та, где ссылка на нее вызывает NameError: free variable '...' referenced before assignment in enclosing scope, а доступ к ней cell.cell_contents вызывает ValueError: Cell is empty), вы можете сделать значение локальной переменной, но никогда не позволяйте ей назначаться:

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

Вы можете комбинировать их следующим образом:

_SENTINEL = object()

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

Таким образом, вызов без аргументов возвращает пустую ячейку, а с любым значением — ячейку с этим значением.

Если вам не нужны пустые ячейки, вы можете сделать:

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

Обратите внимание, что в старом Python 2 это func_closure, а не __closure__.

person Artyer    schedule 13.08.2017