如何序列化递归函数?

eca*_*mur 5 python reflection recursion closures introspection

假设我有一个通过其闭包递归的函数:

def outer():
    def fact(n):
        return 1 if n == 0 else n * fact(n - 1)
    return fact
Run Code Online (Sandbox Code Playgroud)

我现在想序列化该函数并使用以下命令重建它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)
Run Code Online (Sandbox Code Playgroud)

这对于普通闭包来说效果很好,但是当尝试在其闭包内进行序列化fact时,它会导致无限递归。处理递归数据结构的通常方法是在构造和初始化之间记忆对象,但对象是不可变的,如(元组)和单元对象:picklefactpicklefunctionfn.__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
Run Code Online (Sandbox Code Playgroud)

想必该语言在正常代码中构造递归函数时必须执行类似的操作,因为在构造函数对象之前,函数对象无法放置在其闭包中。我缺少构建递归函数的魔力吗?

Mar*_*ers 1

闭包绑定到自由变量,而不是它的值。对于自引用闭包,Python 需要做的就是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
Run Code Online (Sandbox Code Playgroud)

为了使这项工作与 unpickling 一起工作,您必须循环遍历闭包参数 ( args[-1]) 并检测哪里有递归,并将该一项替换为create_function_closure.__closure__[0],我想。