22

作为这个问题的后续: 是否有一种简单的方法来腌制 python 函数(或以其他方式序列化其代码)?

我想从上面的帖子中看到这个项目符号的一个例子:

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

我有一个简单的测试,我正在使用 marshal 将函数字节码写入文件:

def g(self,blah): 
    print blah

def f(self):
    for i in range(1,5):
        print 'some function f'
        g('some string used by g')

data = marshal.dumps(f.func_code)

file = open('/tmp/f2.txt', 'w')
file.write(data)

然后开始一个新的python实例,我这样做:

file = open('/tmp/f2.txt', 'r')
code = marshal.loads(file.read())
func2 = types.FunctionType(code, globals(), "some_func_name");
func2('blah')

这导致:

NameError: global name 'g' is not defined

这与我为包含 g 所做的不同方法无关。我已经尝试过基本相同的方法将 g 作为 f 发送,但 f 仍然看不到 g。如何让 g 进入全局命名空间,以便 f 在接收过程中使用它?

有人还建议将 pyro 视为如何执行此操作的示例。我已经尝试理解disco项目中的相关代码。我参加了他们的 dPickle 课程并尝试在独立应用程序中重新创建他们的 disco/tests/test_pickle.py 功能,但没有成功。我的实验在使用转储调用进行函数编组时遇到问题。无论如何,也许接下来是火焰兵探索。

总而言之,我所追求的基本功能是能够通过网络发送一个方法,并让所有基本的“工作区”方法随它一起发送(如 g)。

答案更改的示例:

工作函数_writer:

import marshal, types

def g(blah): 
    print blah


def f():
    for i in range(1,5):
        print 'some function f'
        g('blah string used by g')


f_data = marshal.dumps(f.func_code)
g_data = marshal.dumps(g.func_code);

f_file = open('/tmp/f.txt', 'w')
f_file.write(f_data)

g_file = open('/tmp/g.txt', 'w')
g_file.write(g_data)

工作函数_reader:

import marshal, types

f_file = open('/tmp/f.txt', 'r')
g_file = open('/tmp/g.txt', 'r')

f_code = marshal.loads(f_file.read())
g_code = marshal.loads(g_file.read())

f = types.FunctionType(f_code, globals(), 'f');
g = types.FunctionType(g_code, globals(), 'g');

f()
4

5 回答 5

32

2020 年 9 月更新: 请参阅下面@ogrisel 的评论。在我于 2013 年写下这个答案的原始版本后不久,PiCloud 的开发人员就搬到了 Dropbox,尽管七年后很多人仍在使用 cloudpickle 模块。该模块进入了 Apache Spark,并在那里继续得到维护和改进。我正在相应地更新下面的示例和背景文本。

云泡菜

cloudpickle包能够腌制一个函数、方法、类,甚至是一个 lambda,以及任何依赖项。要尝试一下,pip install cloudpickle然后:

import cloudpickle

def foo(x):
    return x*3

def bar(z):
    return foo(z)+1

x = cloudpickle.dumps(bar)
del foo
del bar

import pickle

f = pickle.loads(x)
print(f(3))  # displays "10"

换句话说,只需调用cloudpickle.dump()cloudpickle.dumps()以与您使用相同的方式pickle.*,然后使用本机pickle.load()pickle.loads()解冻。

背景

PiCcloud.com 发布了cloudLGPL 下的 python 包,其他开源项目也很快开始使用它(google forcloudpickle.py看到一些)。picloud.com 的人们有动力投入到通用代码酸洗工作上——他们的整个业务都是围绕它建立的。这个想法是,如果您有cpu_intensive_function()并且想要在 Amazon 的 EC2 网格上运行它,您只需替换:

cpu_intensive_function(some, args) 

和:

cloud.call(cpu_intensive_function, some, args)

后者用于cloudpickle提取任何依赖的代码和数据,将其发送到 EC2,运行它,并在您调用cloud.result().

Picloud 以毫秒为单位计费,它非常便宜,我一直使用它进行蒙特卡罗模拟和金融时间序列分析,当时我需要数百个 CPU 内核,每个内核只需要几秒钟。多年后,我仍然不能说足够多的好话,我什至没有在那里工作。

于 2013-06-03T06:35:41.757 回答
6

我已经尝试过基本相同的方法将 g 作为 f 发送,但 f 仍然看不到 g。如何让 g 进入全局命名空间,以便 f 在接收过程中使用它?

将其分配给全局名称g。(我看到您分配ffunc2而不是分配给f。如果您正在使用 执行类似的g操作,那么很清楚为什么f找不到g。请记住,名称解析发生在运行时 -g直到您调用时才会查找f。)

当然,我猜是因为您没有显示用于执行此操作的代码。

最好创建一个单独的字典,用于您要取消腌制的函数的全局命名空间——一个沙箱。这样,它们的所有全局变量都将与您正在执行此操作的模块分开。所以您可以执行以下操作:

sandbox = {}

with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break
        sandbox[code.co_name] = types.FunctionType(code, sandbox, code.co_name)

在此示例中,我假设您已将所有函数的代码对象一个接一个地放在一个文件中,并且在读取它们时,我获取代码对象的名称并将其用作函数对象名称的基础以及它在沙盒字典中存储的名称。

在 unpickled 函数内部,沙盒字典是它们的globals(),所以在内部f()gsandbox["g"]. 那么调用f将是:sandbox["f"]("blah")

于 2012-04-06T19:23:35.617 回答
4

每个模块都有自己的全局变量,没有通用的全局变量。我们可以将恢复的功能“植入”到某个模块中,并像使用普通模块一样使用它。

- 节省 -

import marshal
def f(x):
    return x + 1
def g(x):
    return f(x) ** 2
funcfile = open("functions.pickle", "wb")
marshal.dump(f.func_code, funcfile)
marshal.dump(g.func_code, funcfile)
funcfile.close()

- 恢复 -

import marshal
import types
open('sandbox.py', 'w').write('')  # create an empty module 'sandbox'
import sandbox
with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break
        func = types.FunctionType(code, sandbox.__dict__, code.co_name)
        setattr(sandbox, code.co_name, func)   # or sandbox.f = ... if the name is fixed
assert sandbox.g(3) == 16   # f(3) ** 2
# it is possible import them from other modules
from sandbox import g

编辑:
您也可以从外部导入一些模块。例如“sys”到“sandbox”命名空间:

sandbox.sys = __import__('sys')

或相同:

exec 'import sys' in sandbox.__dict__
assert 'sys' in sandbox, 'Verify imported into sandbox'

如果您不是在 ipython 交互中而是在 python 程序或普通 python 交互中执行,您的原始代码将起作用!!!

Ipython 使用了一些奇怪的命名空间,它不是 sys.modules 中任何模块的字典。普通 python 或任何主程序sys.modules['__main__'].__dict__用作 globals()。任何使用的模块that_module.__dict__都可以,只有 ipython 交互是一个问题。

于 2012-04-07T00:06:23.620 回答
3

当被腌制的功能与腌制一起在主模块中时,莳萝(以及其他腌制变体、cloudpickle 等)似乎可以工作。如果您要从另一个模块中提取函数,则在取消提取时必须存在该模块名称。我似乎无法找到解决此限制的方法。

于 2014-10-15T18:42:52.230 回答
3

__main__通过导入和使用该模块中可用的方法,您可以更好地处理全局对象。这就是dill所做的,以便在 python 中序列化几乎任何东西。基本上,当 dill 序列化一个交互式定义的函数时,它会__main__在序列化和反序列化端使用一些名称修饰,从而__main__生成一个有效的模块。

>>> import dill
>>> 
>>> def bar(x):
...   return foo(x) + x
... 
>>> def foo(x):
...   return x**2
... 
>>> bar(3)
12
>>> 
>>> _bar = dill.loads(dill.dumps(bar))
>>> _bar(3)
12

实际上,dill 将它的类型注册到pickle注册表中,所以如果您有一些使用的黑盒代码pickle并且您不能真正编辑它,那么只需导入 dill 就可以神奇地使其工作,而无需猴子修补第 3 方代码。

或者,如果您希望将整个解释器会话作为“python 图像”发送,dill 也可以这样做。

>>> # continuing from above
>>> dill.dump_session('foobar.pkl')
>>>
>>> ^D
dude@sakurai>$ python
Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
[GCC 4.2.1 (Apple Inc. build 5566)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('foobar.pkl')
>>> _bar(3)
12

您可以轻松地将图像通过 ssh 发送到另一台计算机,然后从您离开的地方开始,只要存在 pickle 的版本兼容性以及关于 python 更改和正在安装的东西的常见警告。

于 2013-10-16T21:17:28.653 回答