22

有没有办法使用标准库在 Python 中序列化词法闭包?pickle 和 marshal 似乎不适用于词法闭包。我并不真正关心二进制与字符串序列化等的细节,它只需要工作。例如:

def foo(bar, baz) :
    def closure(waldo) :
        return baz * waldo
    return closure

我希望能够将关闭实例转储到文件中并将它们读回。

编辑:可以解决此问题的一种相对明显的方法是使用一些反射技巧将词法闭包转换为类对象,反之亦然。然后可以转换为类,序列化,反序列化,转换回闭包。哎呀,鉴于 Python 是鸭子类型的,如果你重载类的函数调用运算符以使其看起来像一个函数,你甚至不需要将它转换回闭包并且使用它的代码不会知道区别。如果有任何 Python 反射 API 专家,请说出来。

4

5 回答 5

20

PiCloud 发布了一个开源(LGPL)pickler,它可以处理函数闭包和更多有用的东西。它可以独立于他们的云计算基础设施使用——它只是一个普通的pickler。整个shebang都记录在这里,您可以通过“pip install cloud”下载代码。无论如何,它会做你想做的事。让我们通过酸洗一个闭包来证明这一点:

import pickle
from StringIO import StringIO

import cloud

# generate a closure
def foo(bar, baz):
    def closure(waldo):
        return baz * waldo
    return closure
closey = foo(3, 5)

# use the picloud pickler to pickle to a string
f = StringIO()
pickler = cloud.serialization.cloudpickle.CloudPickler(f)
pickler.dump(closey)

#rewind the virtual file and reload
f.seek(0)
closey2 = pickle.load(f)

现在我们有了closey原始闭包和closey2从字符串序列化恢复的闭包。让我们测试一下。

>>> closey(4)
20
>>> closey2(4)
20

美丽的。该模块是纯 Python 的——您可以打开它并轻松查看是什么使魔法起作用。(答案是很多代码。)

于 2010-11-08T14:46:46.813 回答
18

如果您只是简单地使用带有__call__方法的类,那么它应该与pickle.

class foo(object):
    def __init__(self, bar, baz):
        self.baz = baz
    def __call__(self,waldo):
        return self.baz * waldo

另一方面,由于pickle处理类和实例的方式,将闭包转换为在运行时创建的新类的实例的 hack 是行不通的。 pickle不存储类;只有一个模块名和类名。当读回一个实例或类时,它会尝试导入模块并在其中找到所需的类。如果您使用即时创建的类,那么您就不走运了。

于 2009-02-22T11:42:04.690 回答
1

是的!我明白了(至少我认为)——也就是说,酸洗函数的更一般的问题。Python 太棒了 :),我通过 dir() 函数和一些网络搜索发现了其中的大部分内容。[希望] 解决它也很棒,我也需要它。

我还没有对这个 co_code 东西的健壮性(嵌套 fcns 等)进行大量测试,如果有人可以查看如何挂钩 Python 以便可以自动腌制函数(例如,它们有时可能是闭包参数)。

Cython 模块 _pickle_fcn.pyx

# -*- coding: utf-8 -*-

cdef extern from "Python.h":
    object PyCell_New(object value)

def recreate_cell(value):
    return PyCell_New(value)

Python 文件

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author gatoatigrado [ntung.com]
import cPickle, marshal, types
import pyximport; pyximport.install()
import _pickle_fcn

def foo(bar, baz) :
    def closure(waldo) :
        return baz * waldo
    return closure

# really this problem is more about pickling arbitrary functions
# thanks so much to the original question poster for mentioning marshal
# I probably wouldn't have found out how to serialize func_code without it.
fcn_instance = foo("unused?", -1)
code_str = marshal.dumps(fcn_instance.func_code)
name = fcn_instance.func_name
defaults = fcn_instance.func_defaults
closure_values = [v.cell_contents for v in fcn_instance.func_closure]
serialized = cPickle.dumps((code_str, name, defaults, closure_values),
    protocol=cPickle.HIGHEST_PROTOCOL)

code_str_, name_, defaults_, closure_values_ = cPickle.loads(serialized)
code_ = marshal.loads(code_str_)
closure_ = tuple([_pickle_fcn.recreate_cell(v) for v in closure_values_])
# reconstructing the globals is like pickling everything :)
# for most functions, it's likely not necessary
# it probably wouldn't be too much work to detect if fcn_instance global element is of type
# module, and handle that in some custom way
# (have the reconstruction reinstantiate the module)
reconstructed = types.FunctionType(code_, globals(),
    name_, defaults_, closure_)
print(reconstructed(3))

干杯,
尼古拉斯

编辑- 对于现实世界的案例,更强大的全局处理是必要的。fcn.func_code.co_names 列出全局名称。

于 2009-04-01T05:01:25.410 回答
1
#!python

import marshal, pickle, new

def dump_func(f):
    if f.func_closure:
        closure = tuple(c.cell_contents for c in f.func_closure)
    else:
        closure = None
    return marshal.dumps(f.func_code), f.func_defaults, closure


def load_func(code, defaults, closure, globs):
    if closure is not None:
        closure = reconstruct_closure(closure)
    code = marshal.loads(code)
    return new.function(code, globs, code.co_name, defaults, closure)


def reconstruct_closure(values):
    ns = range(len(values))
    src = ["def f(arg):"]
    src += [" _%d = arg[%d]" % (n, n) for n in ns]
    src += [" return lambda:(%s)" % ','.join("_%d"%n for n in ns), '']
    src = '\n'.join(src)
    try:
        exec src
    except:
        raise SyntaxError(src)
    return f(values).func_closure




if __name__ == '__main__':

    def get_closure(x):
        def the_closure(a, b=1):
            return a * x + b, some_global
        return the_closure

    f = get_closure(10)
    code, defaults, closure = dump_func(f)
    dump = pickle.dumps((code, defaults, closure))
    code, defaults, closure = pickle.loads(dump)
    f = load_func(code, defaults, closure, globals())

    some_global = 'some global'

    print f(2)
于 2010-03-19T13:04:14.103 回答
0

配方 500261:命名元组包含一个动态定义类的函数。而且这个类支持酸洗。

这是本质:

result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')

结合@Greg Ball 建议在运行时创建一个新类,它可能会回答您的问题。

于 2009-02-24T23:38:42.110 回答