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)
此处可能不需要 exec
或 eval
。如果您的表达式都很简单,就像在您的示例代码中一样,那么我们可以使用 ast.literal_eval
,它(顾名思义)可以评估包含有效 Python 文字的字符串。这使得它比普通的 eval
或 exec
安全得多。当然,您的代码仅尝试执行批准的功能,因此它应该是安全的,但仍然...
无论如何,这里有一个解决方案。我们将批准的函数存储在字典中,以函数名称为键。我们假设包含函数参数列表的字符串是一个元组,并获取 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'")
我正在写一个命令行计算器。我掌握了所有数学知识,但我想添加自己的函数,例如 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)
此处可能不需要 exec
或 eval
。如果您的表达式都很简单,就像在您的示例代码中一样,那么我们可以使用 ast.literal_eval
,它(顾名思义)可以评估包含有效 Python 文字的字符串。这使得它比普通的 eval
或 exec
安全得多。当然,您的代码仅尝试执行批准的功能,因此它应该是安全的,但仍然...
无论如何,这里有一个解决方案。我们将批准的函数存储在字典中,以函数名称为键。我们假设包含函数参数列表的字符串是一个元组,并获取 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'")