8.9 像python表达式一样评估随意的字符串

8.9 像python表达式一样评估随意的字符串

(1)eval()函数,统一的python评估工具。

>>> eval('1 + 1 == 2')
True
>>> eval('1 + 1 == 3')
False
>>> eval('9567 + 1085 == 10652')
True

eval()函数不限于布尔表达式,它能处理任何python表达式并返回任何数据类型。

>>> eval('"A" + "B"')
'AB'
>>> eval('"MARK".translate({65: 79})')
'MORK'
>>> eval('"AAAAA".count("A")')
5
>>> eval('["*"] * 5')
['*', '*', '*', '*', '*']

>>> x = 5
>>> eval("x * 5")
25
>>> eval("pow(x, 2)")
25
>>> import math
>>> eval("math.sqrt(x)")
2.2360679774997898

>>> import subprocess
>>> eval("subprocess.getoutput('ls ~')") ①
'Desktop    Library    Pictures \
Documents    Movies    Public \
Music Sites'
>>> eval("subprocess.getoutput('rm /some/random/file')") ②

  1. subprocess模块允许你运行任何shell命令并且以字符串方式得到返回结果。
  2. 相同的命令会返回相同的结果。
    可以使用另一种复杂的方法完成上面的命令,__import__()函数使用一个模块的名字来加载它,然后可以直接调用它:
    >>>eval("__import__('subprocess').getoutput('rm /some/random/file')")

eval()是邪恶的!

邪恶的部分是eval评估任意来自未信任源的表达式。应该只在信任的输入上使用eval()函数。当然,其中的技巧就是如何找出什么是“信任的”。

>>> x = 5
>>> eval("x * 5", {}, {})          ①
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'x' is not defined
>>> eval("x * 5", {"x": x}, {})       ②
>>> eval(" x*5", {"x":5} , {})
25
>>> eval(" x * 5", {}, {"x":5})
25
>>> eval("x * 5", {"x":5}, {"x":3})
15
>>> import math
>>> eval("math.sqrt(x)", {"x": x}, {})  ③
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'math' is not defined

  1. 第二个和第三个参数传递到eval()函数中,做为全局参数和本地参数对表达式进行评估。在本例中两个都是空的,被评估的表达式字符串是“x * 5”,因为没有索引指向x,所以eval会抛出一个异常。
  2. 可以有选择性的包含指定的值在全局空间或本地空间中,然后就会用这些值去评估表达式。
  3. 虽然已经导入math模块,但是并没有包含在名字空间中传递给eval()函数,所以同样会抛出异常。

>>> eval("pow(5, 2)", {}, {})              ①
25
>>> eval("__import__('math').sqrt(5)", {}, {})  ②
2.2360679774997898

  1. 虽然在全局和本地空间都是空dictionary,但是python3的内建函数仍然可以使用。所以pow(5, 2)可以工作。
  2. 很不幸(为什么不幸呢?请继续看下去),__import__()函数同样是内建的,所以它可以工作。是的,这意味着你仍然可以做一些不好的事情(别想歪了,这里原文是that means you can still do nasty things),即使你已经明显的设置了空的全局和本地名字空间:
    >>> eval("__import__('subprocess').getoutput('rm /some/random/file')", {}, {})

>>> eval("__import__('math').sqrt(5)",
... {"__builtins__":None}, {})              ①
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name '__import__' is not defined
>>> eval("__import__('subprocess').getoutput('rm -rf /')",
... {"__builtins__":None}, {})              ②
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name '__import__' is not defined

  1. 去安全地评估一个未信任的表达式,你需要定义一个全局名字空间字典,它映射”__builtins__“到none,python的空值。built-in函数被包含在一个被叫做”__builtins__“的预定义模块里。这个预定义模块默认情况下是可以用来评估表达式的,除非显示的覆盖它。
  2. 记住是__builtins__,不要拼错了,写错了就不会达到覆盖内建模块的作用了。

那这样是不是就可以安全的使用eval()了呢?

>>> eval("2 ** 2147483647",
... {"__builtins__":None}, {}) ①

  1. 即使不去访问__builtins__,你仍然可能遭到拒绝服务的功击。如上面的例子,计算2的2147483647次平方,这将会使你的CPU利用率达到100%相当长的一段时间。技术上说这是可行的,但是你的主机将会有很长的一段时间什么都不能干。

总之使用eval要小心行事。

发表评论