3

我正在尝试将 python 代码动态添加到沙盒模块中,以便在远程机器上执行。我遇到了如何处理导入方法的问题。例如,常见的脚本编写如下:

 from test_module import g
 import other_module

 def f():
     g()
     other_module.z()

我知道我可以用 g 和可能的 z 腌制 f,但是如何保留 z 的“other_module”范围?如果我将 f 和 g 都放在沙箱中,那么在调用 f 时 z 将无法正确解析。是否可以使用某种类型的嵌入式模块来正确解析 z,即 sandbox.other_module?

我将远程代码加载到沙箱中的目的是不污染全局命名空间。例如,如果使用它自己的依赖图调用另一个远程方法,那么它不应该干扰另一组远程代码。期望 python 在沙盒模块进出使用时保持稳定是否现实?我之所以这么说是因为这篇文章: 如何卸载(重新加载)Python 模块? 这让我觉得在这种情况下删除模块(例如不同的沙箱)可能会出现问题。

4

3 回答 3

1

其他模块可以通过以下方式导入沙箱(您的意思是在运行时动态创建的模块)

    sandbox.other_module = __import__('other_module')

或者:

    exec 'import other_module' in sandbox.__dict__

如果您从其他模块或其他沙盒模块中调用“沙盒”模块,并且您想稍后重新加载一些新代码,那么只导入一个模块而不是像“from sandbox import f”这样的模块名称会更容易,然后调用“sandbox. f”不是“f”。然后很容易重新加载。(但自然地重新加载命令对它没有用)


课程

>>> class A(object): pass
... 
>>> a = A()
>>> A.f = lambda self, x: 2 * x  # or a pickled function
>>> a.f(1)
2
>>> A.f = lambda self, x: 3 * x
>>> a.f(1)
3

似乎重新加载方法很容易。我记得重新加载在修改后的源代码中定义的类可能会很复杂,因为旧的类代码可以由某个实例保存。在最坏的情况下,可以/需要单独更新实例的代码:

    some_instance.__class__ = sandbox.SomeClass  # that means the same reloaded class

我将后者与通过 win32com 自动化访问的 python 服务一起使用,并且类代码的重新加载成功而不会丢失实例数据

于 2012-04-11T08:51:28.587 回答
1

我目前的方法是启用“import x”和“from x import y”依赖捆绑。对于当前的实现,一个缺点是它会在每个使用的模块中创建方法的副本,而在代码源中,每个用法只是对内存中相同方法的引用(尽管我在这里有冲突的结果 -请参阅代码后的部分)。

/// analysis_script.py /// (为简洁起见,排除了依赖项)

import test_module
from third_level_module import z

def f():
    for i in range(1,5):
        test_module.g('blah string used by g')
        z()

/// 驱动程序.py ///

import modutil
import analysis_script

modutil.serialize_module_with_dependencies(analysis_script)

/// modutil.py ///

import sys
import modulefinder
import os
import inspect
import marshal

def dump_module(funcfile, name, module):
    functions_list = [o for o in inspect.getmembers(module) if inspect.isfunction(o[1])]
    print 'module name:' + name
    marshal.dump(name, funcfile)
    for func in functions_list:
       print func
       marshal.dump(func[1].func_code, funcfile)

def serialize_module_with_dependencies(module):

    python_path = os.environ['PYTHONPATH'].split(os.pathsep)
    module_path = os.path.dirname(module.__file__)

    #planning to search for modules only on this python path and under the current scripts working directory
    #standard libraries should be expected to be installed on the target platform
    search_dir = [python_path, module_path]

    mf = modulefinder.ModuleFinder(search_dir)

    #__file__ returns the pyc after first run
    #in this case we use replace to get the py file since we need that for our call to       mf.run_script
    src_file = module.__file__
    if '.pyc' in src_file:
        src_file = src_file.replace('.pyc', '.py')

    mf.run_script(src_file)

    funcfile = open("functions.pickle", "wb")

    dump_module(funcfile, 'sandbox', module)

    for name, mod in mf.modules.iteritems():
        #the sys module is included by default but has no file and we don't want it anyway, i.e. should
        #be on the remote systems path. __main__ we also don't want since it should be virtual empty and
        #just used to invoke this function.
        if not name == 'sys' and not name == '__main__':
            dump_module(funcfile, name, sys.modules[name])

    funcfile.close()

/// sandbox_reader.py ///

import marshal
import types
import imp

sandbox_module = imp.new_module('sandbox')

dynamic_modules = {}
current_module = ''
with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break

        if isinstance(code,types.StringType):
            print "module name:" + code
            if code == 'sandbox':
                current_module = "sandbox"
            else:
                current_module = imp.new_module(code)
                dynamic_modules[code] = current_module
                exec 'import '+code in sandbox_module.__dict__
        elif isinstance(code,types.CodeType):
            print "func"
            if current_module == "sandbox":
                func = types.FunctionType(code, sandbox_module.__dict__, code.co_name)
                setattr(sandbox_module, code.co_name, func)
            else:
                func = types.FunctionType(code, current_module.__dict__, code.co_name)
                setattr(current_module, code.co_name, func)
        else:
            raise Exception( "unknown type received")

#yaa! actually invoke the method
sandbox_module.f()
del sandbox_module

例如,函数图在序列化之前如下所示:

 module name:sandbox
 ('f', <function f at 0x15e07d0>)
 ('z', <function z at 0x7f47d719ade8>)
 module name:test_module
 ('g', <function g at 0x15e0758>)
 ('z', <function z at 0x7f47d719ade8>)
 module name:third_level_module
 ('z', <function z at 0x7f47d719ade8>)

具体来说,查看函数 z 我们可以看到所有引用都指向同一个地址,即 0x7f47d719ade8。

在沙盒重建后的远程进程上,我们有:

 print sandbox_module.z 
 <function z at 0x1a071b8>
 print sandbox_module.third_level_module.z 
 <function z at 0x1a072a8>
 print sandbox_module.test_module.z 
 <function z at 0x1a072a8>

这让我大吃一惊!我原以为这里的所有地址在重建后都是唯一的,但由于某种原因 sandbox_module.test_module.z 和 sandbox_module.third_level_module.z 有相同的地址?

于 2012-04-12T04:08:43.883 回答
1
  • 您可能不想序列化从 Python 库导入的函数,例如数学函数或混合 Python + C 的大包,但您的代码会将其序列化。它们没有 func_code 属性等可能会导致不必要的问题。
  • 您不需要重复序列化之前已序列化的函数。您可以发送全名并根据此导入它们。这就是您在内存中多次使用它的原因。
  • 原始格式<module_name> <serialized_func1> <serialized_func2>...不够通用。一个函数可以在本地机器上通过“... import ... as ...”子句以不同的名称导入。您可以序列化混合字符串和代码对象的元组列表。

暗示

def some_filter(module_name):
    mod_path = sys.modules[module_name].__file__
    # or if getattr(sys.modules[module_name], 'some_my_attr', None)
    return not mod_path.startswith('/usr/lib/python2.7/')

dumped_funcs = {}

def dump_module(...
    ...
    data = []
    for func_name, func_obj in functions_list:
        if some_filter(func_obj.__module__) and not func_obj in dumped_funcs and \
                    hasattr(func_obj, 'func_code'):
            data.append((func_name, func_obj.func_code))
            dumped_funcs[func_obj] = True  # maybe later will be saved package.mod.fname
        else:
            data.append((func_name, '%s.%s' % (func_obj.__module__, \
                                               func_obj.func_code.co_name)))
    marshal.dump(data, funcfile)

现在沙盒和其他序列化模块之间没有重要区别。条件“如果沙箱”可以很快被删除。

于 2012-04-12T23:20:22.773 回答