17

我想在机器 A 上序列化并在机器 B 上反序列化一个 python lambda。这样做有几个明显的问题:

  • pickle 模块不会序列化或反序列化代码。它只序列化类/方法/函数的名称
  • 我在 google 上找到的一些答案建议使用低级 marshal 模块来序列化 lambda 的 func_code 属性,但它们未能描述如何从反序列化的代码对象中重建函数对象
  • marhshal(l.func_code) 不会序列化与 lambda 关联的闭包,这会导致检测给定 lambda 何时真正需要闭包并警告用户他正在尝试序列化使用闭包的 lambda 的问题

因此,我的问题:

  • 如何从反序列化(去编组)代码对象重建一个函数?
  • 如果没有相关的闭包,如何检测到给定的 lambda 将无法正常工作?
4

2 回答 2

21

令人惊讶的是,检查 lambda 在没有相关闭包的情况下是否可以工作实际上相当容易。根据数据模型文档,您只需检查func_closure属性:

>>> def get_lambdas():
... 酒吧 = 42
...返回(lambda:1,lambda:bar)
...
>>> no_vars, vars = get_lambdas()
>>> 打印 no_vars.func_closure
没有任何
>>> 打印 vars.func_closure
(<0x1020d3d70 处的单元格:0x7fc150413708 处的 int 对象>,)
>>> 打印 vars.func_closure[0].cell_contents
42
>>>

然后序列化 + 加载 lambda 是相当直接的:

>>> 导入元帅,类型
>>> 旧 = λ: 42
>>> old_code_serialized = marshal.dumps(old.func_code)
>>> new_code = marshal.loads(old_code_serialized)
>>> new = types.FunctionType(new_code, globals())
>>> 新的()
42

值得一看的文档FunctionType

函数(代码,全局变量 [,名称 [,argdefs [,闭包]]])

从代码对象和字典创建函数对象。
可选的名称字符串覆盖代码对象中的名称。
可选的 argdefs 元组指定默认参数值。
可选的闭包元组为自由变量提供绑定。

请注意,您还可以提供一个闭包……这意味着您甚至可以序列化旧函数的闭包,然后在另一端加载它:)

于 2012-08-09T07:12:51.687 回答
4

我不确定你到底想做什么,但你可以试试dill。Dill 可以序列化和反序列化 lambda,我相信它也适用于闭包内的 lambda。pickle API 是其 API 的一个子集。要使用它,只需“将莳萝作为泡菜进口”并开始您的业务腌制。

>>> import dill
>>> testme = lambda x: lambda y:x
>>> _testme = dill.loads(dill.dumps(testme))
>>> testme
<function <lambda> at 0x1d92530>
>>> _testme
<function <lambda> at 0x1d924f0>
>>> 
>>> def complicated(a,b):
...   def nested(x):
...     return testme(x)(a) * b
...   return nested
... 
>>> _complicated = dill.loads(dill.dumps(complicated))
>>> complicated 
<function complicated at 0x1d925b0>
>>> _complicated
<function complicated at 0x1d92570>

Dill 将它的类型注册到pickle注册表中,因此如果您有一些使用的黑盒代码pickle并且您无法真正编辑它,那么只需导入 dill 就可以神奇地使其工作而无需猴子修补第 3 方代码。或者,如果您希望通过网络将整个解释器会话作为“python 图像”发送,dill 也可以这样做。

>>> # continuing from above
>>> dill.dump_session('foobar.pkl')
>>>
>>> ^D
dude@sakurai>$ python
Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
[GCC 4.2.1 (Apple Inc. build 5566)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('foobar.pkl')
>>> testme(4)
<function <lambda> at 0x1d924b0>
>>> testme(4)(5)
4
>>> dill.source.getsource(testme)
'testme = lambda x: lambda y:x\n'

您可以轻松地通过 ssh 将图像发送到另一台计算机,然后从您离开的地方开始,只要存在 pickle 的版本兼容性以及有关 python 更改和正在安装的东西的常见警告。如图所示,您还可以提取在前一个会话中定义的 lambda 的源。

Dill 还提供了一些很好的工具,可以帮助您了解在代码失败时导致酸洗失败的原因。

于 2013-05-13T21:39:34.753 回答