首页 > 解决方案 > 如何执行嵌套的 PyCode 对象

问题描述

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

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父代码对象中名称的代码对象。

有没有办法做到这一点?

标签: python

解决方案


co_const属性仅包含代码对象中定义的常量文字,因此在您的示例中,它仅包含'say foo'作为返回参数加载的代码,可以通过以下方式进行验证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'))

推荐阅读