如何使用依赖项来挑选python函数?

时间:2012-04-06 19:07:50

标签: python function pickle

作为这个问题的后续行动: Is there an easy way to pickle a python function (or otherwise serialize its code)?

我想从上面的帖子中看到这个子弹的一个例子:

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

我有一个简单的测试,我正在使用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。如何进入全局命名空间,以便f在接收过程中可以使用它?

有人还建议将pyro作为如何执行此操作的示例。我已经尝试了解迪斯科项目中的相关代码。我拿了他们的dPickle类并尝试在独立应用程序中重新创建他们的disco / tests / test_pickle.py功能但没有成功。我的实验在使用转储调用执行函数编组时遇到了问题。无论如何,下一步可能是火探探险。

总之,我所遵循的基本功能是能够通过网络发送方法,并将所有基本的“工作空间”方法与其一起发送(如g)。

来自答案的更改示例:

工作函数编写:

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)

使用function_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()

5 个答案:

答案 0 :(得分:18)

云包执行此操作 - 只需“pip install cloud”然后:

import cloud, pickle
def foo(x): 
    return x*3
def bar(z): 
    return foo(z)+1
x = cloud.serialization.cloudpickle.dumps(bar)
del foo 
del bar
f = pickle.loads(x)
print f(3)  # displays "10"

换句话说,只需调用cloudpickle.dump()或cloudpickle.dumps()就像使用pickle。*一样,然后使用原生的pickle.load()或pickle.loads()来解冻。

Picloud在LGPL下发布了'cloud'python包,其他开源项目已经在使用它(google为“cloudpickle.py”看一些)。 picloud.com上的文档让您了解这段代码的强大之处,以及为什么他们有动力将精力投入到通用代码酸洗工作中 - 他们的整个业务都围绕着它构建。这个想法是,如果你有cpu_intensive_function()并想在亚马逊的EC2网格上运行它,你只需要替换:

cpu_intensive_function(some, args) 

with:

cloud.call(cpu_intensive_function, some, args)

后者使用cloudpickle来挑选任何相关代码和数据,将其发送到EC2,运行它,并在调用cloud.result()时将结果返回给您。 (Picloud以毫秒为单位计算,它很便宜,而且我一直用它来进行蒙特卡罗模拟和金融时间序列分析,当我需要数百个CPU核心时,每个只需几秒钟。我不能说足够好关于它的事情,我甚至不在那里工作。)

答案 1 :(得分:4)

  

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

将其分配给全局名称g。 (我看到你将f分配给func2而不是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)

在这个例子中,我假设您将所有函数中的代码对象一个接一个地放在一个文件中,当读取它们时,我得到代码对象的名称并将其用作两者的基础。函数对象的名称以及存储在沙箱字典中的名称。

在未修改的函数中,沙箱字典是他们的globals(),因此在f()内,gsandbox["g"]获取其值。拨打f即可:sandbox["f"]("blah")

答案 2 :(得分: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就可以神奇地制作它没有monkeypatching第三方代码的工作。

或者,如果您希望将整个解释器会话作为“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更改和正在安装的内容的常见警告。

答案 3 :(得分:2)

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

- 保存 -

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

编辑


你也可以导入一些模块.e.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中任何模块的 dict 。普通python或任何主程序使用sys.modules['__main__'].__dict__作为globals()。任何模块都使用that_module.__dict__,这也没关系,只有ipython交互是一个问题。

答案 4 :(得分:0)

当被腌制的功能与酸洗一起进入主模块时,Dill(以及其他泡菜变体,云雀等)似乎起作用。如果您从另一个模块中挑选一个函数,那么当发生unpickling时,该模块名称必须存在。我似乎找不到解决这个限制的方法。