Python eval()函数

原文: https://thepythonguru.com/python-builtin-functions/eval/


于 2020 年 1 月 7 日更新


eval()允许我们执行任意字符串作为 Python 代码。 它接受源字符串并返回一个对象。

其语法如下:

语法

  1. eval(expr, globals=None, locals=None)
参数 描述
expr(必填) expr可以是任何有效的 Python 表达式
globals(可选) 执行源时要使用的全局名称空间。 它必须是字典。 如果未提供,则将使用当前的全局名称空间。
locals(可选) 执行源时要使用的本地名称空间。 它可以是任何映射。 如果省略,则默认为globals字典。

如果同时省略globalslocals,则使用当前的全局和局部名称空间。

这是一个演示eval()如何工作的示例:

  1. >>>
  2. >>> eval("5 == 5")
  3. True
  4. >>>
  5. >>>
  6. >>> eval("4 < 10")
  7. True
  8. >>>
  9. >>>
  10. >>> eval("8 + 4 - 2 * 3")
  11. 6
  12. >>>
  13. >>>
  14. >>> eval("'py ' * 5")
  15. 'py py py py py '
  16. >>>
  17. >>>
  18. >>> eval("10 ** 2")
  19. 100
  20. >>>
  21. >>>
  22. >>> eval("'hello' + 'py'")
  23. 'hellopy'
  24. >>>

试试看:

  1. print(eval("5 == 5"))
  2. print(eval("4 < 10"))
  3. print(eval("8 + 4 - 2 * 3"))
  4. print(eval("'py ' * 5"))
  5. print(eval("10 ** 2"))
  6. print(eval("'hello' + 'py'"))

eval()不仅限于简单表达。 我们可以执行函数,调用方法,引用变量等。

  1. >>>
  2. >>> eval("abs(-11)")
  3. 11
  4. >>>
  5. >>>
  6. >>> eval('"hello".upper()')
  7. 'HELLO'
  8. >>>
  9. >>>
  10. >>> import os
  11. >>>
  12. >>>
  13. >>> eval('os.getcwd()') # get current working directory
  14. '/home/thepythonguru'
  15. >>>
  16. >>>
  17. >>> x = 2
  18. >>>
  19. >>> eval("x+4") # x is referenced inside the expression
  20. 6
  21. >>>

试一试:

  1. print(eval("abs(-11)"))
  2. print(eval('"hello".upper()'))
  3. import os
  4. # get current working directory
  5. print(eval('os.getcwd()'))
  6. x = 2
  7. print(eval("x+4")) # x is referenced inside the expression

请注意,eval()仅适用于表达式。 尝试传递语句会导致SyntaxError

  1. >>>
  2. >>> eval('a=1') # assignment statement
  3. Traceback (most recent call last):
  4. File "<stdin>", line 1, in <module>
  5. File "<string>", line 1
  6. a=1
  7. ^
  8. SyntaxError: invalid syntax
  9. >>>
  10. >>>
  11. >>> eval('import re') # import statement
  12. Traceback (most recent call last):
  13. File "<stdin>", line 1, in <module>
  14. File "<string>", line 1
  15. import re
  16. ^
  17. SyntaxError: invalid syntax
  18. >>>

邪恶的eval()


您永远不要直接将不受信任的源传递给eval()。 由于恶意用户很容易对您的系统造成破坏。 例如,以下代码可以用于从系统中删除所有文件。

  1. >>>
  2. eval('os.system("RM -RF /")') # command is deliberately capitalized
  3. >>>

如果os模块在您当前的全局范围内不可用,则以上代码将失败。 但是我们可以通过使用__import__()内置函数轻松地避免这种情况。

  1. >>>
  2. >>> eval("__import__('os').system('RM -RF /')") # command is deliberately capitalized
  3. >>>

那么有什么方法可以使eval()安全吗?

指定命名空间


eval()可选地接受两个映射,作为要执行的表达式的全局和局部名称空间。 如果未提供映射,则将使用全局和局部名称空间的当前值。

这里有些例子:

示例 1

  1. >>>
  2. >>> globals = {
  3. ... 'a': 10,
  4. ... 'fruits': ['mangoes', 'peaches', 'bananas'],
  5. ... }
  6. >>>
  7. >>>
  8. >>> locals = {}
  9. >>>
  10. >>>
  11. >>> eval("str(a) + ' ' + fruits[0]", globals, locals)
  12. '10 mangoes'
  13. >>>

示例 2

  1. >>>
  2. >>> eval('abs(-100)', {}, {})
  3. 100
  4. >>>

即使我们已经将空字典作为全局和本地名称空间传递了,eval()仍可以访问内置函数(即__builtins__)。

  1. >>>
  2. >>> dir(__builtins__)
  3. ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError',
  4. ...
  5. ...
  6. 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted'
  7. , 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
  8. >>>

要从全局名称空间中删除内置函数,请传递一个字典,该字典包含一个值为None的键__builtins__

示例 3

  1. >>>
  2. >>> eval('abs(-100)', {'__builtins__':None}, {})
  3. Traceback (most recent call last):
  4. File "<stdin>", line 1, in <module>
  5. File "<string>", line 1, in <module>
  6. TypeError: 'NoneType' object is not subscriptable
  7. >>>

即使删除对内置函数的访问权限后,eval()仍然不安全。 考虑以下清单。

  1. >>>
  2. >>> eval("5**98765432111123", {'__builtins__':None}, {})
  3. >>>

这个看似简单的外观表达式足以使您的 CPU 崩溃。

关键要点是仅将eval()与受信任的源一起使用。