在`exec`调用中获取最后一个表达式的值

时间:2015-11-25 05:03:36

标签: python python-3.x

假设我在字符串中有一些python代码

code = """
a = 42
a
"""

和我exec那段代码:

result = exec(code)

然后result始终为None。是否有任何方法可以获得最后一个表达式的值?在这种情况下,那将是5,因为a是最后一个表达式。

编辑:这是我要问的功能的另一个例子。假设我们有python代码(存储在变量code

a = 100
sqrt(a)

那么如何执行代码以便给我结果10 - 即sqrt(a)

编辑编辑:另一个例子:我希望exec的代码是

function_a()
function_b()
function_c()

我有什么方法可以定义某种magic_exec函数,以便

magic_exec(code)

会为我提供function_c()的价值吗?

5 个答案:

答案 0 :(得分:3)

请求肯定是有效的,因为在创建基于Python的环境中我也需要这样的功能。我使用以下使用Python ast机制的代码解决了这个问题:

def my_exec(script, globals=None, locals=None):
    '''Execute a script and return the value of the last expression'''
    stmts = list(ast.iter_child_nodes(ast.parse(script)))
    if not stmts:
        return None
    if isinstance(stmts[-1], ast.Expr):
        # the last one is an expression and we will try to return the results
        # so we first execute the previous statements
        if len(stmts) > 1:
            exec(compile(ast.Module(body=stmts[:-1]), filename="<ast>", mode="exec"), globals, locals)
        # then we eval the last one
        return eval(compile(ast.Expression(body=stmts[-1].value), filename="<ast>", mode="eval"), globals, locals)
    else:
        # otherwise we just execute the entire code
        return exec(script, globals, locals)

代码应该是不言自明的,基本上是

  1. 将脚本分成多个语句
  2. 如果最后一个是表达式,则将第一部分作为语句执行,将最后一部分作为表达式执行。
  3. 否则,请将整个脚本作为语句执行。

答案 1 :(得分:0)

exec('a = 4')
print a % prints 4

>>> code = """
... a = 42
... b = 53"""
>>> exec(code)
>>> a
42
>>> b
53

或者,如果你说你不知道最后一件事是b,那么你可以这样:

code = """  
a = 4
b = 12
abc_d=13
"""
t = re.findall(r'''.*?([A-Za-z0-9_]+)\s*?=.*?$''', code)
assert(len(t)==1)
print t[0] % prints 13

答案 2 :(得分:0)

这并不能获得最后一次评估的值,但会得到整个局部变量列表。

>>> loc = {}
>>> exec(code, {}, loc)
>>> loc
{'a': 42}

答案 3 :(得分:0)

说实话,我不能说我对此非常满意。感觉非常hacky,我还没有对它进行过多次测试。另一方面,我对它很满意。这很有意思。无论如何,希望这可以帮助你或至少接近你想要的。 locals()给出了一个dict,因此输出列表顺序与第一个eval失败的项的输入顺序不匹配。如果您不希望';'作为分隔符,则可以将其更改为'\n'

import math

def magic_exec(_command):
    _command = _command.split(';')
    _result = None
    _before = list(locals()) # Get list of current local variables

    for _code in _command:
        _code = _code.strip() # .strip() prevent IndentationError
        try:
            if eval(_code) != None: # For functions with no return
                _result = eval(_code)
        except (NameError, SyntaxError):
            try:
                _before = list(locals())
                exec(_code)
            except NameError as e: # For undefined variables in _command
                print("An Error Occurred with line ' {0} ' as was skipped: {1}".format(_code, e))

    del _code # delete temp var _code
    # Get new list of locals that didn't exist at the start
    _after = [val for val in list(locals()) if val not in _before]

    if _after:
        return eval(_after[0])
    else:
        return _result

#Dummy class and functions
class Class1(object):
    def __init__(self, x):
        self._x = x

    def get_val(self):
        return self._x

    def __repr__(self):
        return type(self).__name__

def func1(x):
    return x + x

def func2(x):
    print(x*x)

if __name__ == '__main__':
    code = \
"""
a = 42; a; v; y = 2; b = func1(5); s = 'Hello'; func2(10); c = 25; l = []; l.append('Value');
t = math.sqrt(c); pass; 20*10; print('TEST'); math.sqrt(c); d = Class1('World'); d.get_val(); 
def func3(x): return x ** 2; s = func3(15)
"""
    values = magic_exec(code)
    print(values)

答案 4 :(得分:0)

我想补充一下user2283347的excellent answer,它仅适用于Python 3.7。在Python 3.8中,ast.Module.__init__的签名已更改。现在,它需要第二个参数,在我们的例子中可以是一个空列表。

详细信息:{p中的ast.Module(body=stmts[:-1])

    if len(stmts) > 1:
        exec(compile(ast.Module(body=stmts[:-1]), filename="<ast>", mode="exec"), globals, locals)

必须更改为

ast.Module(stmts[:-1], [])(如果您使用Python 3.8或更高版本)(请注意第二个参数[])。否则,将引发以下TypeError

TypeError: required field "type_ignores" missing from Module

很遗憾,此更改没有得到很好的记录。经过广泛的Google搜索之后,我找到了解决方案:"IPython broken on 3.8-dev"