4

クロージャーを通じて再帰的な関数があるとします。

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、無限再帰が発生します。で再帰的なデータ構造を処理する通常の方法は、構築と初期化の間でオブジェクトを記憶することですが、オブジェクトは(タプル) のように不変であり、セル オブジェクト: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

おそらく、言語は通常のコード内で再帰関数を構築するときに同様のことを行う必要があります。これも、構築されるまで関数オブジェクトをそのクロージャーに配置することはできません。私が見逃している再帰関数を構築するための魔法はありますか?

4

2 に答える 2

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

アンピクルでこれを機能させるには、クロージャー引数 ( args[-1]) をループして、再帰がある場所を検出し、その 1 つの項目を に置き換える必要があるcreate_function_closure.__closure__[0]と思います。

于 2013-08-13T17:11:27.327 に答える
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メモ化機能をいじるだけです。

于 2013-08-14T13:31:14.503 に答える