有没有简单的方法来 pickle python 函数(或者序列化它的代码) ?

我试图通过网络连接(使用异步)传输一个函数。有没有一种简单的方法来序列化一个 python 函数(至少在这种情况下,这个函数不会有任何副作用) ,以便像这样进行传输?

我希望有一对类似的函数:

def transmit(func):
obj = pickle.dumps(func)
[send obj across the network]


def receive():
[receive obj from the network]
func = pickle.loads(s)
func()
74514 次浏览

最简单的方法可能是 inspect.getsource(object)(参见 检查模块检查模块) ,它返回一个 String,其中包含函数或方法的源代码。

您可以序列化函数字节码,然后在调用者上重建它。法警模块可以用来序列化代码对象,然后这些对象可以重新组装成一个函数。即:

import marshal
def foo(x): return x*x
code_string = marshal.dumps(foo.__code__)

然后在远程进程中(在传输 code _ string 之后) :

import marshal, types


code = marshal.loads(code_string)
func = types.FunctionType(code, globals(), "some_func_name")


func(10)  # gives 100

以下是一些注意事项:

  • Marshall 的格式(任何 Python 字节码)在主要的 Python 版本之间可能不兼容。

  • 将只适用于 cpython 实现。

  • 如果函数引用了需要拾取的全局变量(包括导入的模块、其他函数等) ,那么您也需要序列化这些变量,或者在远程端重新创建它们。我的示例只是给出了远程进程的全局名称空间。

  • 您可能需要做更多的工作来支持更复杂的情况,比如闭包或生成器函数。

这完全取决于是否在运行时生成函数:

如果你这样做-inspect.getsource(object)不会为动态生成的函数工作,因为它从 .py文件获取对象的源,所以只有在执行之前定义的函数可以作为源检索。

如果你的函数被放置在文件中,为什么不给接收者访问它们的权限,只传递模块和函数名。

我能想到的动态创建函数的唯一解决方案是在传输、传输源之前将函数构造为字符串,然后在接收端对其进行 eval()

编辑: marshal解决方案看起来也相当聪明,不知道你可以序列化其他东西以外的内置

这个模块使用的基本函数涵盖了您的查询,另外您还可以通过连接获得最佳压缩; 请参阅具有指导意义的源代码:

使用 SQLite 的 y _ seral.py 模块: : warePython 对象

”序列化 + 持久性: : 在几行代码中,将 Python 对象压缩并注释到 SQLite 中; 然后在没有任何 SQL 的情况下按时间顺序通过关键字检索它们。对于存储无模式数据的数据库来说,最有用的“标准”模块

Http://yserial.sourceforge.net

cloud软件包(pip 安装云)可以处理任意代码,包括依赖项。

看看 迪尔,它扩展了 Python 的 pickle 库,以支持更多种类的类型,包括函数:

>>> import dill as pickle
>>> def f(x): return x + 1
...
>>> g = pickle.dumps(f)
>>> f(1)
2
>>> pickle.loads(g)(1)
2

它还支持对函数闭包中对象的引用:

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3
code_string = '''
def foo(x):
return x * 2
def bar(x):
return x ** 2
'''


obj = pickle.dumps(code_string)


现在

exec(pickle.loads(obj))


foo(1)
> 2
bar(3)
> 9

Cloudpickle 可能就是您正在寻找的。 Cloudpickle 的描述如下:

Cloudpickle 对于集群计算尤其有用 代码通过网络传送到远程主机上执行,可能 接近数据。

用法例子:

def add_one(n):
return n + 1


pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)

你可以这样做:

def fn_generator():
def fn(x, y):
return x + y
return fn

现在,transmit(fn_generator())将发送 fn(x,y)的实际定义,而不是对模块名的引用。

您可以使用相同的技巧跨网络发送类。

下面是一个助手类,您可以使用它来包装函数,从而使函数变得可以 picklable。对于 marshal已经提到的警告将适用,但是尽可能使用腌菜。不需要跨序列化保留全局或闭包。

    class PicklableFunction:
def __init__(self, fun):
self._fun = fun


def __call__(self, *args, **kwargs):
return self._fun(*args, **kwargs)


def __getstate__(self):
try:
return pickle.dumps(self._fun)
except Exception:
return marshal.dumps((self._fun.__code__, self._fun.__name__))


def __setstate__(self, state):
try:
self._fun = pickle.loads(state)
except Exception:
code, name = marshal.loads(state)
self._fun = types.FunctionType(code, {}, name)

在现代的 Python 中,您可以 pickle 函数和许多变体

import pickle, time
def foobar(a,b):
print("%r %r"%(a,b))

你可以腌了它

p = pickle.dumps(foobar)
q = pickle.loads(p)
q(2,3)

你可以用腌菜封口

import functools
foobar_closed = functools.partial(foobar,'locked')
p = pickle.dumps(foobar_closed)
q = pickle.loads(p)
q(2)

即使闭包使用局部变量

def closer():
z = time.time()
return functools.partial(foobar,z)
p = pickle.dumps(closer())
q = pickle.loads(p)
q(2)

但如果你用内部函数关闭它,它就会失败

def builder():
z = 'internal'
def mypartial(b):
return foobar(z,b)
return mypartial
p = pickle.dumps(builder())
q = pickle.loads(p)
q(2)

错误

PicklingError: Can’t pickle < function mypart at 0x7f3b6c885a50 > : 它不在 _ _ main _ _. mypart 中

使用 Python 2.7和3.6进行测试