Python、cPickle、酸洗 lambda 函数
Posted
技术标签:
【中文标题】Python、cPickle、酸洗 lambda 函数【英文标题】:Python, cPickle, pickling lambda functions 【发布时间】:2013-05-13 15:33:16 【问题描述】:我必须像这样腌制一组对象:
import cPickle as pickle
from numpy import sin, cos, array
tmp = lambda x: sin(x)+cos(x)
test = array([[tmp,tmp],[tmp,tmp]],dtype=object)
pickle.dump( test, open('test.lambda','w') )
它给出了以下错误:
TypeError: can't pickle function objects
有办法解决吗?
【问题讨论】:
似乎是一件奇怪的事情。用例是什么? SymPy 中的 @Aya lambdify 使得创建 lambda 函数非常方便。我想使用 Cython 评估它们。你可以refer to this other question for further information 好吧,我对 Cython 了解不多,但 Martijn 的解决方案只有在 Cython 可以导入定义了tmp(x)
函数的 Python 文件时才有效。
【参考方案1】:
内置的pickle模块无法序列化多种python对象(包括lambda函数、嵌套函数和命令行定义的函数)。
picloud 包包含一个更强大的pickler,它可以pickle lambda 函数。
from pickle import dumps
f = lambda x: x * 5
dumps(f) # error
from cloud.serialization.cloudpickle import dumps
dumps(f) # works
PiCloud 序列化对象可以使用普通的 pickle/cPickle load
和 loads
函数进行反序列化。
Dill 也提供类似的功能
>>> import dill
>>> f = lambda x: x * 5
>>> dill.dumps(f)
'\x80\x02cdill.dill\n_create_function\nq\x00(cdill.dill\n_unmarshal\nq\x01Uec\x01\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00C\x00\x00\x00s\x08\x00\x00\x00|\x00\x00d\x01\x00\x14S(\x02\x00\x00\x00Ni\x05\x00\x00\x00(\x00\x00\x00\x00(\x01\x00\x00\x00t\x01\x00\x00\x00x(\x00\x00\x00\x00(\x00\x00\x00\x00s\x07\x00\x00\x00<stdin>t\x08\x00\x00\x00<lambda>\x01\x00\x00\x00s\x00\x00\x00\x00q\x02\x85q\x03Rq\x04c__builtin__\n__main__\nU\x08<lambda>q\x05NNq\x06tq\x07Rq\x08.'
【讨论】:
谢谢!使用 picloud 包,它起作用了!莳萝我还没有测试...创建的泡菜可以使用传统的泡菜或cPickle模块加载 有没有办法将此pickler 与多处理库一起使用? 答案是:有点像***.com/questions/19984152/… 为清楚起见,dill
可以与multiprocessing
的分叉一起使用。 picloud
序列化程序目前还不能,我相信目前不受支持或已被买断或其他什么。 blog.picloud.com/2013/11/17/picloud-has-joined-dropbox【参考方案2】:
你必须使用一个实际的函数,一个可导入的函数(不嵌套在另一个函数中):
import cPickle as pickle
from numpy import sin, cos, array
def tmp(x):
return sin(x)+cos(x)
test = array([[tmp,tmp],[tmp,tmp]],dtype=object)
pickle.dump( test, open('test.lambda','w') )
函数对象仍然可以由lambda
表达式生成,但前提是您随后为生成的函数对象赋予相同的名称:
tmp = lambda x: sin(x)+cos(x)
tmp.__name__ = 'tmp'
test = array([[tmp, tmp], [tmp, tmp]], dtype=object)
因为pickle
只存储函数对象的模块和名称;在上面的例子中,tmp.__module__
和 tmp.__name__
现在指向在解酸时可以再次找到相同对象的位置。
【讨论】:
我猜这种答案不能用于基于 C 的模块的内置函数(即使 ᴏꜱ 和架构保持不变)。 @user2284570:pickle 具有存储对 C 结构的引用的特定工具。但是,要“腌制”一个函数,所存储的只是一组字符串(模块加上模块内的名称),这些字符串在再次取消腌制时会被取消引用。 那么你的意思是可以保存但不能恢复可执行文件吗?我只对恢复 感兴趣(不必在 python 中创建转储)。我不在乎使用什么 (marshal 或 cPickle),只要不使用第三方模块,除了 numpy。 @user2284570:是的,可以保存对尝试恢复时不再可用的函数的引用。 通过引用,你是指pythonic函数名还是函数地址?我有兴趣在动态链接不可用的平台上恢复函数本机 ᴄᴘᴜ 代码。如果只能恢复它的引用,我想答案是否定的。【参考方案3】:还有另一种解决方案:将函数定义为字符串,pickle/un-pickle 然后使用 eval,例如:
import cPickle as pickle
from numpy import sin, cos, array
tmp = "lambda x: sin(x)+cos(x)"
test = array([[tmp,tmp],[tmp,tmp]],dtype=object)
pickle.dump( test, open('test.lambda','w') )
mytmp = array([[eval(x) for x in l] for l in pickle.load(open('test.lambda','r'))])
print mytmp
# yields : [[<function <lambda> at 0x00000000033D4DD8>
# <function <lambda> at 0x00000000033D4E48>]
# [<function <lambda> at 0x00000000033D4EB8>
# <function <lambda> at 0x00000000033D4F28>]]
这对于其他解决方案可能更方便,因为腌制表示将完全自包含,而无需使用外部依赖项。
【讨论】:
以上是关于Python、cPickle、酸洗 lambda 函数的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Python 中解释 Dill 的酸洗跟踪输出? (分析(非)酸洗/(反)序列化瓶颈)