如何从 Python exec()/eval() 调用中获取结果?

How to get results out of a Python exec()/eval() call?

我想在 Python 中编写一个工具来准备模拟研究,方法是为每个模拟 运行 创建一个文件夹和一个包含一些 运行 特定参数的配置文件。

study/
  study.conf
  run1
    run.conf
  run2
    run.conf

该工具应从文件中读取整个研究配置,包括 (1) 静态参数(键值对),(2) 迭代参数列表,以及 (3) 一些小代码片段以从中计算更多参数以前的。后者 运行 具体取决于所用迭代参数的排列。

在从模板编写 run.conf 文件之前,我需要 运行 一些这样的代码来确定 运行[的代码片段中的特定键值对=16=]

code = compile(code_str, 'foo.py', 'exec')
rv=eval(code, context, { })

但是,正如 Python 文档所确认的那样,这只会导致 None 作为 return 值。

示例中的代码串和上下文字典在别处填写。对于这个讨论,这个片段应该做到:

code_str="""import math
math.sqrt(width**2 + height**2)
"""

context = {
    'width' : 30,
    'height' : 10
}

我以前在 Perl 和 Java+JavaScript 中做过这个。在那里,您只需将代码片段提供给某个评估函数或脚本引擎,然后从上次执行的语句中获取 return 一个值(对象)——这不是什么大问题。

现在,在 Python 中,我纠结于以下事实:eval() 过于狭窄,只允许一个语句,而 exec() 一般不会 return 值。我需要导入模块,有时会做一些稍微复杂的计算,例如 5 行代码。

难道就没有更好的解决方案吗?

在我的研究过程中,我发现了一些关于 Pyhton eval() and exec() and also some tricky solutions to circumvent the issue by going via the stdout 和从那里解析 return 值的非常好的讨论。后者会做,但不是很好而且已经5岁了。

exec函数将修改传递给它的全局参数(dict)。所以你可以使用下面的代码

code_str="""import math
Result1 = math.sqrt(width**2 + height**2)
"""
context = {
    'width' : 30,
    'height' : 10
}

exec(code_str, context)

print (context['Result1']) # 31.6

创建的每个变量 code_str 将在 context 字典中以 key:value 对结束。所以字典就是你在 JavaScript.

中提到的 "object"

编辑 1:

如果您只需要 code_str 中最后一行的结果并尝试阻止类似 Result1=... 的结果,请尝试以下代码

code_str="""import math
math.sqrt(width**2 + height**2)
"""

context = { 'width' : 30, 'height' : 10 }

lines = [l for l in code_str.split('\n') if l.strip()]
lines[-1] = '__myresult__='+lines[-1] 

exec('\n'.join(lines), context)
print (context['__myresult__'])

这种方法不如前一种方法稳健,但应该适用于大多数情况。如果您需要以复杂的方式操作代码,请查看 Abstract Syntax Trees

因为 exec() / eval() Python 中的整个东西有点奇怪......我选择根据评论中的建议重新设计整个东西我的问题(感谢@jonrsharpe)。

现在,整个学习规范是一个用户可以编辑的 .py 模块。从那里,配置设置被直接写入整个包的中心对象。在工具运行时,使用下面的代码导入配置模块

import imp

# import the configuration as a module
(path, name) = os.path.split(filename)
(name, _) = os.path.splitext(name)
(file, filename, data) = imp.find_module(name, [path])

try:
    module = imp.load_module(name, file, filename, data)
except ImportError as e:
    print(e)
    sys.exit(1)
finally:
    file.close()

我也遇到过类似的需求,最后通过玩ast找到了一个方法:

import ast
code = """
def tf(n):
  return n*n
r=tf(3)
{"vvv": tf(5)}
"""
ast_ = ast.parse(code, '<code>', 'exec')
final_expr = None
for field_ in ast.iter_fields(ast_):
    if 'body' != field_[0]: continue
    if len(field_[1]) > 0 and isinstance(field_[1][-1], ast.Expr):
        final_expr = ast.Expression()
        final_expr.body = field_[1].pop().value
ld = {}
rv = None
exec(compile(ast_, '<code>', 'exec'), None, ld)
if final_expr:
    rv = eval(compile(final_expr, '<code>', 'eval'), None, ld)
print('got locals: {}'.format(ld))
print('got return: {}'.format(rv))

如果最后一个子句是表达式,它会评估而不是执行它,或者全部执行并且 return None.

输出:

got locals: {'tf': <function tf at 0x10103a268>, 'r': 9}
got return: {'vvv': 25}