使用 exec 和 eval 执行字符串及计算其结果

有时候,我们想动态地编写 Python 代码,并将其作为语句进行执行或作为表达式进行计算。这可能犹如黑暗魔法,一定要小心。

exec 和 eval 现在都是函数,但 exec 以前是一种语句,而 eval 与它紧密相关。

下面介绍如何执行存储在字符串中的 Python 代码,这样做可能带来严重的安全隐患,请谨慎使用。

exec

函数 exec 将字符串作为代码执行。

>>> exec("print('Hello, world!')")
Hello, world!

然而,调用函数 exec 时只给它提供一个参数绝非好事。在大多数情况下,还应向它传递一个命名空间——用于放置变量的地方;否则代码将污染你的命名空间,即修改你的变量。例如,假设代码使用了名称 sqrt。

>>> from math import sqrt
>>> exec("sqrt = 1")
>>> sqrt(4)
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in ?
    sqrt(4)
TypeError: object is not callable: 1

为了安全起见,要添加第二个参数,即提供一个字典充当代码字符串的命名空间。

>>> from math import sqrt
>>> scope = {}
>>> exec('sqrt = 1', scope)
>>> sqrt(4)
2.0
>>> scope['sqrt']
1

如你所见,可能带来破坏的代码并非覆盖函数 sqrt。函数 sqrt 该怎样还怎样,而通过 exec 执行赋值语句创建的变量位于 scope 中。

请注意,如果你尝试将 scope 打印出来,将发现它包含很多内容,这是因为自动在其中添加了包含所有内置函数和值的字典 __builtins__。

>>> len(scope)
2
>>> scope.keys()
['sqrt', '__builtins__']

eval

eval 是一个类似于 exec 的函数。exec 执行一系列 Python 语句,而 eval 计算用字符串表示的 Python 表达式的值,并返回结果(exec 什么都不返回,因为她本身是条语句)。

与 exec 一样,也可向 eval 提供一个命名空间,虽然表达式通常不会像语句那样给变量重新赋值。