关于python:如何执行嵌套的PyCode对象

How to execute nested PyCode objects

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

1
2
3
4
5
6
7
8
code = '''
x = 'unrelated'
y = dangerous_function()

def foo():
    return 'say foo'
'''

code_obj = compile(code, '<string>', 'exec')

我不想只执行它,因为谁知道会发生什么(特别是dangerous_function调用看起来很诡异)。但我想用其中定义的任何函数填充我当前的作用域,通过这样做,这些函数看起来是可能的:

1
2
3
4
5
6
7
8
9
10
11
12
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的代码对象。

有办法吗?


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

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

此输出:

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

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

如果您只想在给定的源代码中执行一个特定的函数,您可以使用ast.parse解析代码,并使用ast.NodeVisitor子类提取函数节点,用Module节点包装它,这样您就可以单独编译和执行它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'))

此输出:

1
<function foo at 0x018735D0>

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

1
2
3
4
5
6
7
8
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'))