我试图通过网络连接(使用异步)传输一个函数。有没有一种简单的方法来序列化一个 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()
最简单的方法可能是 inspect.getsource(object)(参见 检查模块检查模块) ,它返回一个 String,其中包含函数或方法的源代码。
inspect.getsource(object)
Pyro 能够 为了你。
您可以序列化函数字节码,然后在调用者上重建它。法警模块可以用来序列化代码对象,然后这些对象可以重新组装成一个函数。即:
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文件获取对象的源,所以只有在执行之前定义的函数可以作为源检索。
.py
如果你的函数被放置在文件中,为什么不给接收者访问它们的权限,只传递模块和函数名。
我能想到的动态创建函数的唯一解决方案是在传输、传输源之前将函数构造为字符串,然后在接收端对其进行 eval()。
eval()
编辑: marshal解决方案看起来也相当聪明,不知道你可以序列化其他东西以外的内置
marshal
这个模块使用的基本函数涵盖了您的查询,另外您还可以通过连接获得最佳压缩; 请参阅具有指导意义的源代码:
使用 SQLite 的 y _ seral.py 模块: : warePython 对象
”序列化 + 持久性: : 在几行代码中,将 Python 对象压缩并注释到 SQLite 中; 然后在没有任何 SQL 的情况下按时间顺序通过关键字检索它们。对于存储无模式数据的数据库来说,最有用的“标准”模块
Http://yserial.sourceforge.net
cloud软件包(pip 安装云)可以处理任意代码,包括依赖项。
cloud
看看 迪尔,它扩展了 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)的实际定义,而不是对模块名的引用。
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进行测试