Python exec 不访问变量,exec 替代品

Python exec not accessing variable, exec alternatives

我正在写一个命令行计算器。我掌握了所有数学知识,但我想添加自己的函数,例如 solve("x^3 = 8", x)。我的数学工作正常,但我使用 exec() 的方式阻止它把结果写入局部变量 finalAnswer。

我包含了 foo() 来显示我想要的东西

def foo():
    return 5
exec("a = foo()")
print(a)

result = "addAB(15,8)"

def addAB(A, B):
    print(A+B)
    return A+B

def __runFunc(fn, param):
    exec("finalAnswer = fn(" + param + ")")

__approvedFunctions = set(["addAB", "subtractAB"])

funcName = result[:result.index('(')]
if(funcName in __approvedFunctions):
    param = result[result.index('(')+1 : result.index(')')]
    if callable(globals()[funcName]):
        __runFunc(globals()[funcName], param)
print(finalAnswer)

执行时我的输出是:

addAB(15,8)
5
23
Traceback (most recent call last):
  File "C:/Users/mikeo/OneDrive/Documents/function filter.py", line 45, in 
<module>
    print(finalAnswer)
NameError: name 'finalAnswer' is not defined

输出的前三行告诉我脚本的每个部分都执行了,但是 finalAnswer 没有被初始化。我是否错过了使用 exec() 的一些细微差别。

此外,如果您帮助我删除 exec 而不更改调用参数化函数的字符串输入格式并将返回值存储在本地,则可以获得加分。

def foo():
    return 5
exec("a = foo()")
print(a)

result = "addAB(15,8)"

def addAB(A, B):
    print(A+B)
    return A+B

def __runFunc(fn, param):

    exec("globals()['finalAnswer']= fn(" + param + ")")

__approvedFunctions = set(["addAB", "subtractAB"])

funcName = result[:result.index('(')]
if(funcName in __approvedFunctions):
    param = result[result.index('(')+1 : result.index(')')]
    if callable(globals()[funcName]):
        __runFunc(globals()[funcName], param)
print(finalAnswer)

此处可能不需要 execeval。如果您的表达式都很简单,就像在您的示例代码中一样,那么我们可以使用 ast.literal_eval ,它(顾名思义)可以评估包含有效 Python 文字的字符串。这使得它比普通的 evalexec 安全得多。当然,您的代码仅尝试执行批准的功能,因此它应该是安全的,但仍然...

无论如何,这里有一个解决方案。我们将批准的函数存储在字典中,以函数名称为键。我们假设包含函数参数列表的字符串是一个元组,并获取 literal_eval 为我们构建该元组,因此我们可以使用 * 序列解包将参数传递给函数。

from ast import literal_eval

def addAB(A, B):
    print(A+B)
    return A+B

def subAB(A, B):
    print(A-B)
    return A-B

funcs = (addAB, subAB)
approved_functions = {func.__name__: func for func in funcs}

result = "addAB(15,8)"
i = result.index('(')
func_name, func_args = result[:i], literal_eval(result[i:])
print(func_name, func_args)

if func_name in approved_functions:
    func = approved_functions[func_name]
    final_answer = func(*func_args)
    print(final_answer)

输出

addAB (15, 8)
23
23

这是安全的,因为 ast.literal_eval 非常 严格限制它在字符串中接受的内容。来自文档:

Safely evaluate an expression node or a string containing a Python literal or container display. The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None.

容器显示是列表、元组、集合或字典文字,例如(2,3,4){'one':1, 'two': 2}。如果您尝试传递 ast.literal_eval 包含函数调用甚至算术表达式的内容,例如

"(15, f(8))"

"(15, 2 * 4)"

它将提高 ValueError: malformed node or string

这有一个小例外。 ast.literal_eval 接受仅使用 +- 的算术表达式。那是因为它需要能够计算复数文字,而这些文字包含 +-。实现者决定处理这个问题的简单方法是简单地允许 .literal_eval 计算包含 +- 的算术表达式;这样做不会造成安全风险。这只适用于算术表达式,你不能用 + 进行字符串连接,所以

a = ast.literal_eval("'ab' + 'cd'")

会加注ValueError; OTOH,它 确实 接受相邻字符串文字的通常自动连接,所以这没问题:

a = ast.literal_eval("'ab' 'cd'")