python散装笔记——106: 使用 exec 和 eval 动态执行代码
| Details |
expression | 表达式代码字符串或 code 对象 |
object | 语句代码字符串或 code 对象 |
globals | 用于全局变量的 dictionary。如果未指定 locals,该字典也将用于 locals。如果省略,则使用调用作用域的 globals()。 |
locals | 用于局部变量的映射对象。如果省略,则使用为 globals 传递的映射对象。如果两者都省略,则调用作用域的 globals() 和 locals() 将分别用于 globals 和 locals。 |
1: 使用 exec、eval 或 ast.literal_eval 执行未受信任用户提供的代码
无法使用 eval 或 exec 安全地执行来自不信任用户的代码。即使是 ast.literal_eval 也容易导致解析器崩溃。有时可以防范恶意代码的执行,但并不排除解析器或标记符号生成器彻底崩溃的可能性。
要对不信任的用户执行的代码进行评估,您需要使用一些第三方模块,或者用 Python 编写自己的解析器和虚拟机。
2: 使用 ast.literal_eval 评估包含 Python 字面量的字符串
如果字符串中包含 Python 字面量,如字符串、浮点数等,可以使用 ast.literal_eval 代替 eval 来求值。这样做的另一个好处是只允许使用特定的语法。
>>> import ast
>>> code = """(1, 2, {'foo': 'bar'})"""
>>> object = ast.literal_eval(code)
>>> object
(1, 2, {'foo': 'bar'})
>>> type(object)
然而,这对执行不信任用户提供的代码并不安全,而且使用精心制作的输入使解释器崩溃也是轻而易举的事
>>> import ast
>>> ast.literal_eval('()' * 1000000)
[5] 21358 segmentation fault (core dumped) python3
这里的输入是一个重复了一百万次的 () 字符串,这会导致 CPython 解析器崩溃。CPython 开发人员不会将解析器中的错误视为安全问题。
3: 使用执行程序评估语句
>>> code = """for i in range(5):\n print('Hello world!')"""
>>> exec(code)
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
4: 使用 eval 计算表达式
>>> expression = '5 + 3 * a'
>>> a = 5
>>> result = eval(expression)
>>> result
20
5: 预编译表达式以对其进行多次评估
内置函数 compile 可用于将表达式预编译为代码对象,然后将代码对象传递给 eval。这将加快重复执行已求值代码的速度。编译 “的第三个参数必须是字符串”eval"。
>>> code = compile('a * b + c', '', 'eval')
>>> code
<code object at 0x7f0e51a58830, file "", line 1>
>>> a, b, c = 1, 2, 3
>>> eval(code)
5
6: 使用自定义全局项用 eval 评估表达式
>>> variables = {'a': 6, 'b': 7}
>>> result = eval('a * b', variables)
>>> print(result)
42
此外,这样代码就不会意外地引用外部定义的名称:
>>> eval('variables')
{'a': 6, 'b': 7}
>>> eval('variables', globals=variables)
Traceback (most recent call last):
File "", line 1, in
File "", line 1, in
NameError: name 'variables' is not defined
例如,使用 defaultdict 可以将未定义的变量设置为零:
>>> from collections import defaultdict
>>> variables = defaultdict(int, {'a': 42})
>>> eval('a * c', globals=variables) # note that 'c' is not explicitly defined
0