欢迎来到赛兔子家园

Python eval()和exec()函数

eval() 和 exec() 函数都属于 Python 的内置函数,由于这两个函数在功能和用法方面都有相似之处,所以将它们放到一节进行介绍。
eval() 和 exec() 函数的功能是相似的,都可以执行一个字符串形式的 Python 代码(代码以字符串的形式提供),相当于一个 Python 的解释器。二者不同之处在于,eval() 执行完要返回结果,而 exec() 执行完不返回结果。

eval()和exec()的用法

eval() 函数的语法格式:

eval(expression, globals=None, locals=None, /)

exec()函数的语法格式:

exec(expression, globals=None, locals=None, /)

可以看到,二者的语法格式除了函数名,其他都相同,其中各个参数的具体含义如下:

  expression:这个参数是一个字符串,代表要执行的语句 。该语句受后面两个字典类型参数 globals 和 locals 的限制,只有在 globals 字典和 locals 字典作用域内的函数和变量才能被执行。

  globals:这个参数管控的是一个全局的命名空间,即 expression 可以使用全局命名空间中的函数。如果只是提供了 globals 参数,而没有提供自定义的 __builtins__,则系统会将当前环境中的 __builtins__ 复制到自己提供的 globals 中,然后才会进行计算;如果连 globals 这个参数都没有被提供,则使用 Python 的全局命名空间。

  locals:这个参数管控的是一个局部的命名空间,和 globals 类似,当它和 globals 中有重复或冲突时,以 locals 的为准。如果 locals 没有被提供,则默认为 globals。

注意,__builtins__ 是 Python 的内建模块,平时使用的 int、str、abs 都在这个模块中。通过 print(dic["__builtins__"]) 语句可以查看 __builtins__ 所对应的 value。

首先,通过如下的例子来演示参数 globals 作用域的作用,注意观察它是何时将 __builtins__ 复制 globals 字典中去的:

dic = {}  # 定义一个字典
dic['b'] = 3  # 在 dic 中加一条元素,key 为 b
print(dic.keys())  # 先将 dic 的 key 打印出来,有一个元素 b
exec("a = 4", dic)  # 在 exec 执行的语句后面跟一个作用域 dic
print(dic.keys())  # exec 后,dic 的 key 多了一个
print(dic.get("a"))

运行结果:

dict_keys(['b'])
dict_keys(['b', '__builtins__', 'a'])
4

上面的代码是在作用域 dic 下执行了一句 a = 4 的代码。可以看出,exec() 之前 dic 中的 key 只有一个 b。执行完 exec() 之后,系统在 dic 中生成了两个新的 key,分别是 a 和 __builtins__。其中,a 为执行语句生成的变量,系统将其放到指定的作用域字典里;__builtins__ 是系统加入的内置 key。

locals参数用法示例:

a = 10
b = 20
c = 30
g = {"a": 6, "b": 8}  # 定义一个字典
t = {"b": 100, "c": 10}  # 定义一个字典
print(eval('a+b+c', g, t))  # 定义一个字典116

运行结果:

116

exec()和eval()区别

它们的区别在于,eval() 执行完会返回结果,而 exec() 执行完不返回结果。

例子:

a = 1
exec("a = 2") #相当于直接执行 a=2
print(a)
a = exec("2+3") #相当于直接执行 2+3,但是并没有返回值,a 应为 None
print(a)
a = eval('2+3') #执行 2+3,并把结果返回给 a
print(a)

运行结果:

2
None
5

可以看出,exec() 中最适合放置运行后没有结果的语句,而 eval() 中适合放置有结果返回的语句。
如果 eval() 里放置一个没有结果返回的语句会怎样呢?例如下面代码:

a= eval("a = 2")

这时 Python 解释器会报 SyntaxError 错误,提示 eval() 中不识别等号语法。

exec() 和 eval()方法应用场景

在使用 Python 开发服务端程序时,这两个函数应用得非常广泛。例如,客户端向服务端发送一段字符串代码,服务端无需关心具体的内容,直接跳过 eval() 或 exec() 来执行,这样的设计会使服务端与客户端的耦合度更低,系统更易扩展。

需要注意的是,在使用 eval() 或是 exec() 来处理请求代码时,函数 eval() 和 exec() 常常会被黑客利用,成为可以执行系统级命令的入口点,进而来攻击网站。解决方法是:通过设置其命名空间里的可执行函数,来限制 eval() 和 exec() 的执行范围。

exec()和eval()的使用注意事项

使用 exec() 和 eval() 函数时,一定要记住,它们的第一个参数是字符串,而字符串的内容一定要是可执行的代码。

以 eval() 函数为例,用代码演示常犯的错误:

s="hello"
print(eval(s))

输出结果:

NameError: name 'hello' is not defined

上面例子出错的地方在于,字符串的内容是 hello,而 hello 并不是可执行的代码(除非定义了一个变量叫作 hello)。

如果要将字符串 hello 通过 print 函数打印出来,可以写成如下的样子:

s = "hello"
print(eval('s'))

输出结果:

hello

这种写法是要 eval() 执行 "hello" 这句代码。这个 hello 是有引号的,在代码中代表字符串的意思,所以可以执行。

注意,虽然函数 eval() 与 str() 的返回值都是字符串。但是使用 str() 函数对 s 进行转化,程序同样会报错。

例如:

s = "hello"
print(eval(str(s)))

输出结果:

NameError: name 'hello' is not defined

为什么会有这个区别呢?同样对带字符串 s 的转化,使用 repr() 与 str() 得到的结果是有差别的,直接将二者的结果打印出来,就可以很明显地看出不同。见下面代码:

s="hello"
print(repr(s))
print(str(s))

输出结果:

'hello'
 hello

可见使用 repr() 返回的内容,输出后会在两边多一个单引号。

注意,在编写代码时,一般会使 repr() 函数来生成动态的字符串,再传入到 eval() 或 exec() 函数内,实现动态执行代码的功能。

posted on 2024-03-28 11:11  赛兔子  阅读(21)  评论(0编辑  收藏  举报

导航