代码之家  ›  专栏  ›  技术社区  ›  Arne

如何执行嵌套的pycode对象

  •  1
  • Arne  · 技术社区  · 6 年前

    假设我们有一个这样的代码对象:

    code = '''
    x = 'unrelated'
    y = dangerous_function()
    
    def foo():
        return 'say foo'
    '''
    code_obj = compile(code, '<string>', 'exec')
    

    我不想就这么做,因为谁知道会发生什么事。 dangerous_function 尤其是打电话的时候看起来很狡猾)。但我 要用其中定义的任何函数填充当前作用域,执行以下操作似乎是可能的:

    import types    
    
    new_objects = []
    for obj in code_obj.co_consts:
        if isinstance(obj, types.CodeType):
            new_objects.append(obj.co_name)
            print(obj)           # "<code object foo at 0x7f4e255d3150, file "<string>", line 4>"
                                 # ... looks promising, so let's exec it!
            exec(obj)
    
    print(new_objects[0])        # "foo"
    print(eval(new_objects[0]))  # "NameError: name 'foo' is not defined"
    

    我本以为最后一份声明会打印出来 say foo 而不是提出 NameError . 原因一定是 exec(obj) 不执行我希望它执行的操作,即它不运行分配给该名称的代码对象 foo 在父代码对象中。

    有办法吗?

    0 回复  |  直到 6 年前
        1
  •  1
  •   blhsing    6 年前

    这个 co_const 属性只保存代码对象中定义的常量文本,因此在您的示例中,它只保存加载 'say foo' 作为return的参数,可以用 dis :

    import dis
    for obj in code_obj.co_consts:
        if isinstance(obj, types.CodeType):
            dis.dis(obj)
    

    这将输出:

      5           0 LOAD_CONST               1 ('say foo')
                  2 RETURN_VALUE
    

    所以通过执行这个代码对象,它自然不会定义任何名称。

    如果只想在给定的源代码中执行特定的函数,可以使用 ast.parse 并使用 ast.NodeVisitor 子类来提取函数节点,用 Module 节点,以便您可以单独编译和执行它:

    import ast
    
    class get_function(ast.NodeVisitor):
        def __init__(self, name):
            self.name = name
            self.code = None
    
        def visit_FunctionDef(self, node):
            if node.name == self.name:
                self.code = compile(ast.fix_missing_locations(ast.Module(body=[node])), '<string>', 'exec')
    
    func = get_function('foo')
    func.visit(ast.parse(code, '<string>'))
    exec(func.code)
    print(eval('foo'))
    

    这将输出:

    <function foo at 0x018735D0>
    

    编辑:或者更简单地说,您可以使用 ast.walk 函数来遍历节点 for 循环:

    import ast
    
    for node in ast.walk(ast.parse(code, '<string>')):
        if isinstance(node, ast.FunctionDef) and node.name == 'foo':
            code_obj = compile(ast.fix_missing_locations(ast.Module(body=[node])), '<string>', 'exec')
    
    exec(code_obj)
    print(eval('foo'))