Python能否对lambda函数进行pickle序列化?

83

我在许多帖子中读到,Python pickle/cPickle 无法对 lambda 函数进行序列化。然而,以下代码在使用 Python 2.7.6 时有效:

import cPickle as pickle

if __name__ == "__main__":
    s = pickle.dumps(lambda x, y: x+y)
    f = pickle.loads(s)
    assert f(3,4) == 7

那么发生了什么?或者说,对于将lambda函数进行pickle的限制是什么?

[编辑] 我想我知道为什么这段代码可以运行。我忘记了(很抱歉!)我正在运行stackless python,它有一种称为tasklets的微线程执行函数。这些tasklets可以被暂停、pickle、unpickle并继续执行,所以我猜想(在stackless邮件列表上询问)它也提供了一种pickle函数体的方法。


4
在2.7.6(在OS X 10.9.4上)无法复制-我收到了“TypeError:can't pickle function objects”错误。 - jonrsharpe
尝试从另一个脚本中使用pickle.loads;我认为你将lambda的引用pickle化了,在同一作用域中,它被保留在内存中并被调用。 - cox
只是想知道,你使用的 cPickle 版本是哪个? - enrico.bacis
@Lars:stackless通常可以做到dill所能做的一切...主要区别在于,stackless替换了C中的调用堆栈,而dill则尝试使用ctypes注册序列化函数以尽可能地在C层工作。Stackless可以序列化所有对象。 - Mike McKerns
cloudpickle 是出路:https://github.com/cloudpipe/cloudpickle - Ufos
6个回答

94

是的,Python可以对lambda函数进行pickle(序列化)处理……但前提是你需要使用copy_reg注册程序来告诉它如何pickle lambda函数。而包dill会在你import dill时将所需的copy_reg加载到pickle registry中。

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> import dill  # the code below will fail without this line
>>> 
>>> import pickle
>>> s = pickle.dumps(lambda x, y: x+y)
>>> f = pickle.loads(s)
>>> assert f(3,4) == 7
>>> f
<function <lambda> at 0x10aebdaa0>

在这里获取dill:https://github.com/uqfoundation


7
我已经在Python3上尝试了。In [1]: import dill In [2]: import pickle In [3]: pickle.dumps(lambda x: (x+1, x+2))会出现以下错误:PicklingError: Can't pickle at 0x7f08ee40ca60>: attribute lookup on __main__ failed.只有当你将dill作为pickle导入时才能正常运行。 - Ramast
23
你说得对 - 在Python3中,你需要导入dill作为pickle。在Python2中,无论你如何操作,我上面的代码都能正常运行。 - Mike McKerns
2
@CharlieParker:你能详细说明一下吗?上述方法适用于大多数“任意”的函数。 - Mike McKerns
2
答案对于Python 3.6是不正确的,因为它已经过时了 - 使用dill.dumps()dill.loads()代替。 - Pavel Vlasov
3
在Python 2中,有pickle(Python)和cPickle(C)的区别。在Python 3中,区别是现在有pickle(Python)和_pickle(C)。然而,pickle.dump(和dumps)使用_pickle(即C),并且dill目前仅能将新方法注入到Python pickle注册表中。因此,仅仅导入dill并不能像在Python 2中那样工作。请注意,存在pickle._dump(和_dumps),它使用Python注册表,因此与Python 2类似。不幸的是,大多数软件包在dump失败时不会退回到_dump - Mike McKerns
显示剩余5条评论

48

Python可以pickle lambda函数。由于不同版本的Python实现pickle不同,我们将分别介绍Python 2和3。

  • Python 3.6

在Python 3中,没有名为cPickle的模块。相反,我们有pickle,默认情况下也不支持对lambda函数进行pickling。让我们看一下它的分派表:

>> import pickle
>> pickle.Pickler.dispatch_table
<member 'dispatch_table' of '_pickle.Pickler' objects>

等一下,我试图查找 pickledispatch_table 而不是 _pickle_pickle 是 pickle 的替代和更快的 C 实现。但是我们还没有导入它!如果纯 Python pickle 模块在末尾时可用,则会自动导入此 C 实现。

# Use the faster _pickle if possible
try:
    from _pickle import (
        PickleError,
        PicklingError,
        UnpicklingError,
        Pickler,
        Unpickler,
        dump,
        dumps,
        load,
        loads
    )
except ImportError:
    Pickler, Unpickler = _Pickler, _Unpickler
    dump, dumps, load, loads = _dump, _dumps, _load, _loads

我们仍然面临着在Python 3中如何对lambda函数进行序列化的问题。答案是你无法使用本地的pickle_pickle来实现。你需要导入dillcloudpickle,并使用它们来替代本地的pickle模块。
>> import dill
>> dill.loads(dill.dumps(lambda x:x))
<function __main__.<lambda>>
  • Python 2.7

pickle使用的是pickle registry,它实际上是从type到用于序列化(pickling)该类型对象的函数的映射。 你可以将pickle registry视为:

>> pickle.Pickler.dispatch

{bool: <function pickle.save_bool>,
 instance: <function pickle.save_inst>,
 classobj: <function pickle.save_global>,
 float: <function pickle.save_float>,
 function: <function pickle.save_global>,
 int: <function pickle.save_int>,
 list: <function pickle.save_list>,
 long: <function pickle.save_long>,
 dict: <function pickle.save_dict>,
 builtin_function_or_method: <function pickle.save_global>,
 NoneType: <function pickle.save_none>,
 str: <function pickle.save_string>,
 tuple: <function pickle.save_tuple>,
 type: <function pickle.save_global>,
 unicode: <function pickle.save_unicode>}

为了pickle自定义类型,Python提供了copy_reg模块来注册我们的函数。您可以在此处阅读更多信息。默认情况下,copy_reg模块支持以下附加类型的pickling:
>> import copy_reg
>> copy_reg.dispatch_table

{code: <function ipykernel.codeutil.reduce_code>,
 complex: <function copy_reg.pickle_complex>,
 _sre.SRE_Pattern: <function re._pickle>,
 posix.statvfs_result: <function os._pickle_statvfs_result>,
 posix.stat_result: <function os._pickle_stat_result>}

现在,lambda函数的类型是types.FunctionType。然而,这种类型的内置函数function: <function pickle.save_global>无法序列化lambda函数。因此,所有第三方库,如dillcloudpickle等都会覆盖内置方法,以某些附加逻辑来序列化lambda函数。让我们导入dill并看看它做了什么。

>> import dill
>> pickle.Pickler.dispatch

{_pyio.BufferedReader: <function dill.dill.save_file>,
 _pyio.TextIOWrapper: <function dill.dill.save_file>,
 _pyio.BufferedWriter: <function dill.dill.save_file>,
 _pyio.BufferedRandom: <function dill.dill.save_file>,
 functools.partial: <function dill.dill.save_functor>,
 operator.attrgetter: <function dill.dill.save_attrgetter>,
 operator.itemgetter: <function dill.dill.save_itemgetter>,
 cStringIO.StringI: <function dill.dill.save_stringi>,
 cStringIO.StringO: <function dill.dill.save_stringo>,
 bool: <function pickle.save_bool>,
 cell: <function dill.dill.save_cell>,
 instancemethod: <function dill.dill.save_instancemethod0>,
 instance: <function pickle.save_inst>,
 classobj: <function dill.dill.save_classobj>,
 code: <function dill.dill.save_code>,
 property: <function dill.dill.save_property>,
 method-wrapper: <function dill.dill.save_instancemethod>,
 dictproxy: <function dill.dill.save_dictproxy>,
 wrapper_descriptor: <function dill.dill.save_wrapper_descriptor>,
 getset_descriptor: <function dill.dill.save_wrapper_descriptor>,
 member_descriptor: <function dill.dill.save_wrapper_descriptor>,
 method_descriptor: <function dill.dill.save_wrapper_descriptor>,
 file: <function dill.dill.save_file>,
 float: <function pickle.save_float>,
 staticmethod: <function dill.dill.save_classmethod>,
 classmethod: <function dill.dill.save_classmethod>,
 function: <function dill.dill.save_function>,
 int: <function pickle.save_int>,
 list: <function pickle.save_list>,
 long: <function pickle.save_long>,
 dict: <function dill.dill.save_module_dict>,
 builtin_function_or_method: <function dill.dill.save_builtin_method>,
 module: <function dill.dill.save_module>,
 NotImplementedType: <function dill.dill.save_singleton>,
 NoneType: <function pickle.save_none>,
 xrange: <function dill.dill.save_singleton>,
 slice: <function dill.dill.save_slice>,
 ellipsis: <function dill.dill.save_singleton>,
 str: <function pickle.save_string>,
 tuple: <function pickle.save_tuple>,
 super: <function dill.dill.save_functor>,
 type: <function dill.dill.save_type>,
 weakcallableproxy: <function dill.dill.save_weakproxy>,
 weakproxy: <function dill.dill.save_weakproxy>,
 weakref: <function dill.dill.save_weakref>,
 unicode: <function pickle.save_unicode>,
 thread.lock: <function dill.dill.save_lock>}

现在,让我们尝试pickle一个lambda函数。
>> pickle.loads(pickle.dumps(lambda x:x))
<function __main__.<lambda>>

它成功了!

在Python 2中,我们有两个版本的 pickle -

import pickle # pure Python version
pickle.__file__ # <install directory>/python-2.7/lib64/python2.7/pickle.py

import cPickle # C extension
cPickle.__file__ # <install directory>/python-2.7/lib64/python2.7/lib-dynload/cPickle.so

现在,让我们尝试使用C实现的 cPickle 来pickle一个lambda函数。
>> import cPickle
>> cPickle.loads(cPickle.dumps(lambda x:x))
TypeError: can't pickle function objects

发生了什么问题?让我们来看看的调度表。
>> cPickle.Pickler.dispatch_table
AttributeError: 'builtin_function_or_method' object has no attribute 'dispatch_table'

picklecPickle的实现方式不同。导入dill只能让Python版本的pickle工作。相比于cPickle,使用pickle的缺点是它可能比cPickle1000倍

希望这能解决所有疑惑。


3
这个答案应该被接受。它很好地解释了每个Python版本中dill包的可用性和限制。干得好! - EliadL

34
不可以,Python不能pickle匿名函数:
>>> import cPickle as pickle
>>> s = pickle.dumps(lambda x,y: x+y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy_reg.py", line 70, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle function objects

不确定你成功的是什么操作...


7
我不知道为什么这个评论被踩了。pickle无法序列化lambda表达式,只有dill包可以。 - Ramast
14
为什么Python无法pickle(腌制)lambda函数? - naught101

2

虽然可能显而易见,但我想再添加另一种可能的解决方案。 您可能已经知道Lambda函数只是匿名函数声明。如果您没有使用仅一次的Lambda函数,并且这不会在您的代码中增加太多噪音,则可以将Lambda函数命名并传递其名称(无需括号),如下所示:

最初的回答:

即使很明显,我还想添加另一个可能的解决方案。您可能已经知道Lambda函数只是匿名函数声明。如果您没有使用仅一次的Lambda函数,并且这不会在您的代码中增加太多噪音,则可以将Lambda函数命名并传递其名称(无需括号),如下所示:
import cPickle as pickle

def addition(x, y):
    return x+y


if __name__ == "__main__":
    s = pickle.dumps(addition)
    f = pickle.loads(s)
    assert f(3,4) == 7

该名称还增加了更多的语义,您不需要像Dill一样的额外依赖。但是只有在这超过了额外函数添加的噪音时才这样做。"最初的回答"

2

对我有用的方法(Windows 10,Python 3.7)是传递一个函数而不是lambda函数:

def merge(x):
    return Image.merge("RGB", x.split()[::-1])

transforms.Lambda(merge)

替代:

transforms.Lambda(lambda x: Image.merge("RGB", x.split()[::-1]))

不需要任何腌制或咸菜。

1
安装 dill。
$ pip install dill

触摸一个文件

touch yeah.p

现在运行这个 Python3 脚本,

import dill

dill.dump(lambda x:x+1, open('yeah.p', 'wb'))
my_lambda = dill.load(open('yeah.p', 'rb'))
print(my_lambda(2))  # 3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接