不同的字符串执行方法:exec、eval、locals、compile
eval是Python的一个内置函数,这个函数的作用是,把字符串解析转换为执行对象,比如说一个有效的表达式,或者一条linux执行命令,并返回执行结果。
eval(expression[, globals[, locals]])
# expression : 字符串
# globals : 变量作用域,全局命名空间,如果被提供,则必须是一个dict对象。
# locals : 变量作用域,局部命名空间,如果被提供,可以是任何mapping对象。
expression
举例:
实现list、dict、tuple与str之间的转化,是str()函数的逆过程,同样str函数把list,dict,tuple转为为字符串。
a = "[[1,2], [3,4]]"
b = eval(a) # [[1, 2], [3, 4]]
type(b) #
a = "{0: 'a', 1: 'b'}"
b = eval(a) # {0: 'a', 1: 'b'}
type(b) #
a = "([1,2], [3,4], [5,6], [7,8], (9,0))"
b = eval(a) # ([1, 2], [3, 4], [5, 6], [7, 8], (9, 0))
type(b) #
eval("{'student':'liming','age':age}",{"age":20}) #{'student': 'liming', 'age': 20}
age=18
eval("{'student':'liming','age':age}",{"age":20},locals()) #{'student': 'liming', 'age': 18}
当locals参数为空,globals参数不为空时,查找globals参数中是否存在变量,并计算。
当两个参数都不为空时,先查找locals参数,再查找globals参数,locals参数中同名变量会覆盖globals中的变量。
eval 后面2个参数的作用,相当于给表达式添加临时的作用域,并不会污染真正的globals(), locals()变量的值,仅仅通过字典即可完成作用域的传递。
可以与任意 Python 运算符的布尔表达式一起使用:
* 值比较运算符:<,>,<=,>=,==,!=
* 逻辑(布尔)运算符: and, or, not
* 测试运算符:in ,not in
* 身份运算:is ,is not
print(eval('a+b', {'a': 8, 'b': 9})) # 17
print(eval('a+b', {'a':'8','b':'9'})) # 89
d={'c':1,'b':2}
e={'a':8,'b':9}
print(eval('a+b', e, d)) # print(eval('a+b', {'a': 8, 'b': 9}, {'c': 1, 'b': 2})) # 10
def func(a, b, condition):
if eval(condition):
return a + b
return a - b
func(2, 4, "a > b") # -2
func(2, 2, "a is b") # 4
如果您尝试将复合语句传递给eval()
,那么您将得到一个SyntaxError. 任何其他复合语句,例如if
、for、while、import、def或class,都会引发错误。(处理判断的控制流语句,x循环迭代语句,异常处理程序,函数、声明语句等)
>>> x = 100
>>> eval("if x: print(x)")
Traceback (most recent call last):
File "", line 1, in
File "", line 1
if x: print(x)
^
SyntaxError: invalid syntax
x = 10
def func():
y = 20
a = eval('x + y')
print('a: ', a) # a: 30
b = eval('x + y', {'x': 1, 'y': 2})
print('b: ', b) # b: 3
c = eval('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
print('c: ', c) # c: 4
d = eval('print(x, y)') # 10 20
print('d: ', d) # d: None
func()
还可以将编译后的代码对象传递给 Python 的eval()
. 要编译您要传递给的代码eval()
,您可以使用compile(). 这是一个内置函数,可以将输入字符串编译为代码对象或AST 对象,以便您可以使用eval()
.
code = compile("5 + 4", "", "eval")
eval(code) #9
Python2 中exec不是函数,而是一个内置语句(statement),但是Python 2中有一个 execfile() 函数。可以理解为 Python 3 把 exec 这个 statement 和 execfile() 函数的功能够整合到一个新的 exec() 函数中去了。
exec()函数用于动态执行Python 程序,该程序可以是字符串或目标代码。如果它是一个字符串,则该字符串被解析为一套Python语句,然后执行,除非发生语法错误,如果它是一个目标代码,则简单地执行它。
exec(object[, globals[, locals]])
prog = 'print("The sum of 5 and 10 is", (5+10))'
exec(prog) # The sum of 5 and 10 is 15
我们必须小心,不能在函数定义之外使用 return 语句,甚至不能在传递给 exec() 函数的代码上下文中使用。它不返回任何值,永远是None。
eval更偏向于表达式类型的字符串的转换,处理序列并返回处理结果,不适用于复合语句。eval不支持任何形式的变量赋值操作。
exec可以动态运行代码段,可以处理复合字符串对象,也可以产生新的变量,没有返回值,永远为None。因此,如果需要获取返回执行结果,就别用exec。
>>> a = 1
>>> exec("a = 2") # 相当于直接执行 a=2
>>> print(a) # 2
>>> a = exec("2+3") # 相当于直接执行 2+3,但是并没有返回值,a 应为 None
>>> print(a) # None
>>> a = eval('2+3') # 执行 2+3,并把结果返回给 a
>>> print(a) # 5
>>> eval("a = 2")
Traceback (most recent call last):
File "", line 1, in
File "", line 1
a = 2
^
SyntaxError: invalid syntax
>>> string_input = """
... def sum_of_even_squares(numbers):
... return sum(number**2 for number in numbers if number % 2 == 0)
...
... print(sum_of_even_squares(numbers))
... """
>>> compiled_code = compile(string_input, "", "exec")
>>> numbers = [2, 3, 7, 4, 8]
>>> exec(compiled_code) # 84
1、需要防止恶意输入,所以永远不要将其与不受信任的输入一起使用。
除了将字符串转成表达式并执行,也可以利用执行系统命令,删除文件,读取隐私数据文件等操作。
比如:
eval("__import__('os').system('ls /Users/secret/')") # os.system('ls /Users/secret/')
eval("__import__('os').system('cat /Users/secret/secret.txt')")
eval("__import__('os').system('rm * -rf')") # 这个命令惊呆了!
__import__是python中的一个内置方法,它与import
语句不同。通过这个方法导入一个os模块, __import__('os')等价于import os, system方法可以接收一个字符串参数,在字符串中可以指定终端下能够执行的命令,比如上面的代码中传入的是ls,就可以查看当前目录下的情况。
针对以上例子,有一个限制的办法,即指定 globals 为 {'__builtins__': None}
或者 {'__builtins__': {}}
。
s = {'__builtins__': None}
eval("__import__('os').system('whoami')", s)
# result:
"""
Traceback (most recent call last):
File "", line 1, in
File "", line 1, in
TypeError: 'NoneType' object is not subscriptable"""
__builtins__
包含了内置命名空间中的名称,在控制台中输入 dir(__builtins__) ,就能发现很多内置函数、异常和其它属性的名称。在默认情况下,eval 函数的 globals 参数会隐式地携带__builtins__
,即使是令 globals 参数为 {} 也如此,所以如果想要禁用它,就得显式地指定它的值。
上例将它映射成 None,就意味着限定了 eval 可用的内置命名空间为 None,从而限制了表达式调用内置模块或属性的能力。
().__class__.__bases__[0].__subclasses__()
().__class__.__bases__[0]
将为我们提供一个object
类,上述代码,提供了我们现在可以使用的所有子类的列表object
参考下:
用python继承链搞事情:https://xz.aliyun.com/t/2308
Keith Bot:https://ctftime.org/writeup/15658
将目标代码构建为字符串后,您就可以exec()
像执行任何 Python 代码一样执行它们。在这种情况下,我们很难确定字符串将包含什么内容。如果在构建代码时,使用不受信任的输入源(例如用户的直接输入)这也是有隐患的。函数 eval() 和 exec() 常常会被黑客利用,成为可以执行系统级命令的入口点,进而来攻击网站。
2、会造成内存耗尽问题:
>>> eval("2 ** 999999999999999", {"__builtins__":None}, {})
解决方法:
ast 模块的 literal()
是 eval() 的安全替代,与 eval() 不做检查就执行的方式不同,ast.literal() 会先检查表达式内容是否有效合法。 exec() ,似乎还没有类似的替代方法。
大神的文章目前还没更多学习,先膜拜下。
locals、compile未完待续。。。
参考文档:
深度辨析 Python 的 eval() 与 exec():https://juejin.cn/post/6844903805931225095
Python eval(): Evaluate Expressions Dynamically – Real Python :https://realpython.com/python-eval-function/