Python 可以pickle lambdas。我们将分别介绍Python 2 和3,因为不同Python 版本中pickle 的实现是不同的。
pickle 使用 pickle registry,它只是从type 到用于序列化(酸洗)该类型对象的函数的映射。
你可以看到 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>}
为了腌制自定义类型,Python 提供了copy_reg 模块来注册我们的函数。你可以阅读更多关于它的信息here。默认情况下,copy_regmodule 支持以下附加类型的酸洗:
>> 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 函数。因此,dill、cloudpickle 等所有第三方库都会覆盖内置方法,以使用一些额外的逻辑来序列化 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>}
现在,让我们尝试腌制 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 腌制 lambda。
>> import cPickle
>> cPickle.loads(cPickle.dumps(lambda x:x))
TypeError: can't pickle function objects
出了什么问题?我们来看cPickle的调度表。
>> cPickle.Pickler.dispatch_table
AttributeError: 'builtin_function_or_method' object has no attribute 'dispatch_table'
pickle 和cPickle 的实现是不同的。 Importing dill 仅使 Python 版本的 pickle 工作。使用pickle 而不是cPickle 的缺点是它可能比cPickle 慢1000 倍。
在 Python 3 中,没有名为 cPickle 的模块。我们有 pickle 而不是默认情况下也不支持酸洗 lambda 函数。让我们看看它的调度表:
>> import pickle
>> pickle.Pickler.dispatch_table
<member 'dispatch_table' of '_pickle.Pickler' objects>
等等。我尝试查找 pickle 的 dispatch_table 而不是 _pickle。 _pickle 是 pickle 的另一种更快的 C 实现。但是我们还没有导入它!如果可用,此 C 实现会在纯 Python pickle 模块的末尾自动导入。
# 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。您将需要导入 dill 或 cloudpickle 并使用它来代替本机 pickle 模块。
>> import dill
>> dill.loads(dill.dumps(lambda x:x))
<function __main__.<lambda>>
我希望这能消除所有疑虑。