假设我有一个通过闭包递归的函数:
def outer():
def fact(n):
return 1 if n == 0 else n * fact(n - 1)
return fact
我现在想要序列化该函数并使用types.FunctionType
重构它:
import pickle, marshal, copyreg, types
def make_cell(value):
return (lambda: value).__closure__[0]
def make_function(*args):
return types.FunctionType(*args)
copyreg.pickle(types.CodeType,
lambda code: (marshal.loads, (marshal.dumps(code),)))
copyreg.pickle(type((lambda i=0: lambda: i)().__closure__[0]),
lambda cell: (make_cell, (cell.cell_contents,)))
copyreg.pickle(types.FunctionType,
lambda fn: (make_function, (fn.__code__, {}, fn.__name__, fn.__defaults__, fn.__closure__)))
buf = pickle.dumps(outer())
fn = pickle.loads(buf)
这适用于普通的闭包,但是fact
会导致无限递归,因为pickle
尝试在其闭包中序列化fact
。在pickle
中处理递归数据结构的常用方法是在构造和初始化之间记忆对象,但function
对象是不可变的,fn.__closure__
(元组)和单元对象:
>>> cell = (lambda i=0: lambda: i)().__closure__[0]
>>> cell.cell_contents = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'cell_contents' of 'cell' objects is not writable
据推测,在正常代码中构造递归函数时,语言必须做类似的事情,因为函数对象在构造之前不能放在其闭包中。构建我缺少的递归函数是否有一些魔力?
答案 0 :(得分:1)
闭包绑定到自由变量,而不是它的值。对于自引用闭包,所有Python需要做的是首先为free fact
名称创建一个闭包(尚未绑定到任何东西),用闭包创建函数对象,然后绑定fact
那个对象。
因此,您需要将闭包和函数组合到同一个外部函数中,这样您就可以为函数绑定的名称创建一个闭包:
def create_closure_and_function(*args):
func = None
def create_function_closure():
return func
closure = create_function_closure.__closure__
func = types.FunctionType(*args[:-1] + [closure])
return func
为了使这个工作与unpickling你必须循环关闭参数(args[-1]
)并检测有递归的位置,并用create_function_closure.__closure__[0]
替换那个项,我想。 / p>
答案 1 :(得分:0)
这就是我在Python 3中使用nonlocal
:
def settable_cell():
if False:
x = None
def set_cell(y):
nonlocal x
x = y
return (lambda: x).__closure__[0], set_cell
在Python 2中使用生成器:
def settable_cell():
def g():
while True:
x = (yield (lambda: x).__closure__[0])
set_cell = iter(g()).send
return set_cell(None), set_cell
这允许分离创建闭包单元格而不是设置其自由变量的值;解决方案的其余部分只需要摆弄pickle
备忘录设施。