a = "[[1,2], [3,4], [5,6], [7,8], [9,0]]"
print(type(a),a)print("-"*20)
eval_a = eval(a)
print(type(eval_a),eval_a)

但是注意,eval函数并不安全!!!

eval可以将字符串转成表达式并执行,就可以利用执行系统命令,删除文件等操作。

Python 提供了很多内置的工具函数(Built-in Functions),在最新的 Python 3 官方文档中,它列出了 69 个。

大部分函数是我们经常使用的,例如 print()、open() 与 dir(),而有一些函数虽然不常用,但它们在某些场景下,却能发挥出不一般的作用。内置函数们能够被“提拔”出来,这就意味着它们皆有独到之处,有用武之地。

因此,掌握内置函数的用法,就成了我们应该点亮的技能。

这篇文章是一份超级详细的学习记录,系统、全面而深入地辨析了eval() 和 exec() 这两大函数。

1、eval 的基本用法

语法:eval(expression, globals=None, locals=None)

它有三个参数,其中 expression 是一个字符串类型的表达式或代码对象,用于做运算;globals 与 locals  是可选参数,默认值是 None。

具体而言,expression 只能是单个表达式,不支持复杂的代码逻辑,例如赋值操作、循环语句等等。(PS:单个表达式并不意味着“简单无害”,参见下文第 4 节)

globals 用于指定运行时的全局命名空间,类型是字典,缺省时使用的是当前模块的内置命名空间。locals 指定运行时的局部命名空间,类型是字典,缺省时使用 globals 的值。两者都缺省时,则遵循 eval 函数执行时的作用域。值得注意的是,这两者不代表真正的命名空间,只在运算时起作用,运算后则销毁。


x = 10

def func():
    y = 20
    a = eval('x + y')
    print('a: ', a)
    b = eval('x + y', {'x': 1, 'y': 2})
    print('x: ' + str(x) + ' y: ' + str(y))
    print('b: ', b)
    c = eval('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
    print('x: ' + str(x) + ' y: ' + str(y))
    print('c: ', c)

func()

输出结果:


a:  30
x: 10 y: 20
b:  3
x: 10 y: 20
c:  4

由此可见,当指定了命名空间的时候,变量会在对应命名空间中查找。而且,它们的值不会覆盖实际命名空间中的值。

2、exec 的基本用法

语法:exec(object[, globals[, locals]])

在 Python2 中 exec 是个语句,而 Python3 将其改造成一个函数,像 print 一样。exec() 与 eval() 高度相似,三个参数的意义和作用相近。

主要的区别是,exec() 的第一个参数不是表达式,而是代码块,这意味着两点:一是它不能做表达式求值并返回出去,二是它可以执行复杂的代码逻辑,相对而言功能更加强大,例如,当代码块中赋值了新的变量时,该变量可能 在函数外的命名空间中存活下来。


>>> x = 1
>>> y = exec('x = 1 + 1')
>>> print(x)
>>> print(y)
2
None

可以看出,exec() 内外的命名空间是相通的,变量由此传递出去,而不像 eval() 函数,需要一个变量来接收函数的执行结果。

3、一些细节辨析

两个函数都很强大,它们将字符串内容当做有效的代码执行。这是一种字符串驱动的事件,意义重大。然而,在实际使用过程中,存在很多微小的细节,此处就列出我所知道的几点吧。

常见用途:将字符串转成相应的对象,例如 string 转成 list ,string 转成 dict,string 转 tuple 等等。


>>> a = "[[1,2], [3,4], [5,6], [7,8], [9,0]]"
>>> print(eval(a))
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 0]]
>>> a = "{'name': 'Python猫', 'age': 18}"
>>> print(eval(a))
{'name': 'Python猫', 'age': 18}

# 与 eval 略有不同
>>> a = "my_dict = {'name': 'Python猫', 'age': 18}"
>>> exec(a)
>>> print(my_dict)
{'name': 'Python猫', 'age': 18}

eval() 函数的返回值是其 expression 的执行结果,在某些情况下,它会是 None,例如当该表达式是 print() 语句,或者是列表的 append() 操作时,这类操作的结果是 None,因此 eval() 的返回值也会是 None。


>>> result = eval('[].append(2)')
>>> print(result)
None

exec() 函数的返回值只会是 None,与执行语句的结果无关,所以,将 exec() 函数赋值出去,就没有任何必要。所执行的语句中,如果包含 return 或 yield ,它们产生的值也无法在 exec 函数的外部起作用。


>>> result = exec('1 + 1')
>>> print(result)
None

两个函数中的 globals 和 locals 参数,起到的是白名单的作用,通过限定命名空间的范围,防止作用域内的数据被滥用。

conpile() 函数编译后的 code 对象,可作为 eval 和 exec 的第一个参数。compile() 也是个神奇的函数。

吊诡的局部命名空间:前面讲到了 exec() 函数内的变量是可以改变原有命名空间的,然而也有例外。


def foo():
    exec('y = 1 + 1\nprint(y)')
    print(locals())
    print(y)

foo()

按照前面的理解,预期的结果是局部变量中会存入变量 y,因此两次的打印结果都会是 2,然而实际上的结果却是:


2
{'y': 2}
Traceback (most recent call last):
...(略去部分报错信息)
    print(y)
NameError: name 'y' is not defined

明明看到了局部命名空间中有变量 y,为何会报错说它未定义呢?

原因与 Python 的编译器有关,对于以上代码,编译器会先将 foo 函数解析成一个 ast(抽象语法树),然后将所有变量节点存入栈中,此时 exec() 的参数只是一个字符串,整个就是常量,并没有作为代码执行,因此 y 还不存在。直到解析第二个 print() 时,此时第一次出现变量 y ,但因为没有完整的定义,所以 y 不会被存入局部命名空间。

在运行期,exec() 函数动态地创建了局部变量 y ,然而由于 Python 的实现机制是“运行期的局部命名空间不可改变 ”,也就是说这时的 y 始终无法成为局部命名空间的一员,当执行 print() 时也就报错了。

至于为什么 locals() 取出的结果有 y,为什么它不能代表真正的局部命名空间?为什么局部命名空间无法被动态修改?官方的 bug 网站中也有对此问题的讨论。

查看地址:https://bugs.python.org/issue4831

若想把 exec() 执行后的 y 取出来的话,可以这样:z = locals()['y'] ,然而如果不小心写成了下面的代码,则会报错:


def foo():
    exec('y = 1 + 1')
    y = locals()['y']
    print(y)

foo()

#报错:KeyError: 'y'
#把变量 y 改为其它变量则不会报错

KeyError 指的是在字典中不存在对应的 key 。本例中 y 作了声明,却因为循环引用而无法完成赋值,即 key 值对应的 value 是个无效值,因此读取不到,就报错了。

此例还有 4 个变种,我想用一套自恰的说法来解释它们,但尝试了很久,未果。留个后话吧,等我想明白,再单独写一篇文章。

4、为什么要慎用 eval() ?

很多动态的编程语言中都会有 eval() 函数,作用大同小异,但是,无一例外,人们会告诉你说,避免使用它。

为什么要慎用 eval() 呢?主要出于安全考虑,对于不可信的数据源,eval 函数很可能会招来代码注入的问题。


>>> eval("__import__('os').system('whoami')")
desktop-fa4b888\pythoncat
>>> eval("__import__('subprocess').getoutput('ls ~')")
#结果略,内容是当前路径的文件信息

在以上例子中,我的隐私数据就被暴露了。而更可怕的是,如果将命令改为rm -rf ~ ,那当前目录的所有文件都会被删除干净。

针对以上例子,有一个限制的办法,即指定 globals 为 {'__builtins__': None} 或者 {'__builtins__': {}} 。


>>> s = {'__builtins__': None}
>>> eval("__import__('os').system('whoami')", s)
#报错:TypeError: 'NoneType' object is not subscriptable

__builtins__ 包含了内置命名空间中的名称,在控制台中输入 dir(__builtins__) ,就能发现很多内置函数、异常和其它属性的名称。在默认情况下,eval 函数的 globals 参数会隐式地携带__builtins__ ,即使是令 globals 参数为 {} 也如此,所以如果想要禁用它,就得显式地指定它的值。

上例将它映射成 None,就意味着限定了 eval 可用的内置命名空间为 None,从而限制了表达式调用内置模块或属性的能力。

但是,这个办法还不是万无一失的,因为仍有手段可以发起攻击。

某位漏洞挖掘高手在他的博客中分享了一个思路,令人大开眼界。其核心的代码是下面这句,你可以试试执行,看看输出的是什么内容。


>>> ().__class__.__bases__[0].__subclasses__()

关于这句代码的解释,以及更进一步的利用手段。

详见:https://www.tuicool.com/articles/jeaqe2n

另外还有一篇博客,不仅提到了上例的手段,还提供了一种新的思路:


#警告:千万不要执行如下代码,后果自负。
>>> eval('(lambda fc=(lambda n: [c 1="c" 2="in" 3="().__class__.__bases__[0" language="for"][/c].__subclasses__() if c.__name__ == n][0]):fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})())()', {"__builtins__":None})

这行代码会导致 Python 直接 crash 掉。

详见:https://segmentfault.com/a/1190000011532358

除了黑客的手段,简单的内容也能发起攻击。像下例这样的写法, 将在短时间内耗尽服务器的计算资源。


>>> eval("2 ** 888888888", {"__builtins__":None}, {})

如上所述,我们直观地展示了 eval() 函数的危害性,然而,即使是 Python 高手们小心谨慎地使用,也不能保证不出错。

在官方的 dumbdbm 模块中,曾经(2014年)发现一个安全漏洞,攻击者通过伪造数据库文件,可以在调用 eval() 时发起攻击。

详情:https://bugs.python.org/issue22885

无独有偶,在上个月(2019.02),有核心开发者针对 Python 3.8 也提出了一个安全问题,提议不在 logging.config 中使用 eval() 函数,目前该问题还是 open 状态。

详情:https://bugs.python.org/issue36022

如此种种,足以说明为什么要慎用 eval() 了。同理可证,exec() 函数也得谨慎使用。

5、安全的替代用法

既然有种种安全隐患,为什么要创造出这两个内置方法呢?为什么要使用它们呢?

理由很简单,因为 Python 是一门灵活的动态语言。与静态语言不同,动态语言支持动态地产生代码,对于已经部署好的工程,也可以只做很小的局部修改,就实现 bug 修复。

那有什么办法可以相对安全地使用它们呢?

ast 模块的 literal() 是 eval() 的安全替代,与 eval() 不做检查就执行的方式不同,ast.literal() 会先检查表达式内容是否有效合法。它所允许的字面内容如下:

strings, bytes, numbers, tuples, lists, dicts, sets, booleans, 和 None

一旦内容非法,则会报错:


import ast
ast.literal_eval("__import__('os').system('whoami')")

报错:ValueError: malformed node or string

不过,它也有缺点:AST 编译器的栈深(stack depth)有限,解析的字符串内容太多或太复杂时,可能导致程序崩溃。

至于 exec() ,似乎还没有类似的替代方法,毕竟它本身可支持的内容是更加复杂多样的。

最后是个建议:搞清楚它们的区别与运行细节(例如前面的局部命名空间内容),谨慎使用,限制可用的命名空间,对数据源作充分校验。

参考:

深度辨析Python的eval()与exec()_Python大本营的博客-CSDN博客作者 | 豌豆花下猫来源|Python猫(ID:python_cat)Python 提供了很多内置的工具函数(Built-in Functions),在最新的 Pyt...https://blog.csdn.net/weixin_42232219/article/details/88968787

python str转为list,dict,tuple等 eval,exec,相关推荐

  1. Python(set/list/dict/tuple)

    set集合: set是一个无序,不重复元素的集合.可嵌套列表,字典(可以for循环或者迭代的对象). ######差集: a={11,22} b={22,33} c=a.difference(b) # ...

  2. python(3): str list dict tuple set 常用函数整理

    多个类型对象共有方法 1.方括号切片 语法:[start:end:step]   #step默认为1 适用所有可下标操作的对象,如:str , list,tuple等 例: print([1,2,3, ...

  3. Python序列 数据类型 创建方式 Tuple元组 Str字符串 List列表 dict字典 Set集合 range,zip,map,enumerate

    Python 序列分类 数据类型 详细介绍 Python 主要数据类型: 元组Tuple, 字符串Str,列表List,字典Dict,集合Set 对比 元组Tuple 字符串Str 列表List 字典 ...

  4. python常用数据类型转换(str、list、tuple、set、dict)方法编码与解码字符串格式化处理时间格式化

    python常用数据类型str.list.tuple.set.dict的常用方法汇总&编码与解码 一.字符串(str)的常用方法 1.1 字符串的格式化 1.1.1 %s.%d.%f进行占位 ...

  5. Python set list dict tuple 区别和相互转换 - Python零基础入门教程

    目录 一.前言 二.Python set list dict tuple 区别 三.猜你喜欢 零基础 Python 学习路线推荐 : Python 学习目录 >> Python 基础入门 ...

  6. 浅谈python中的 list、tuple、dict、set

    浅谈python中的 list.tuple.dict.set list: 简介: 有序的集合,包含多个元素 数据可以是任意类型 支持索引访问 用[ ]表示,例如a=[ ] 或者 a=list() 访问 ...

  7. Python str 函数 - Python零基础入门教程

    目录 一.Python str 函数介绍 二.Python str 函数使用 三.猜你喜欢 零基础 Python 学习路线推荐 : Python 学习目录 >> Python 基础入门 一 ...

  8. gj6 深入python的set和dict

    6.1 collections中的abc from collections.abc import Mapping, MutableMapping #dict属于mapping类型a = {} prin ...

  9. defaultdict python3,Python collections.defaultdict() 与 dict的使用和区别|python3教程|python入门|python教程...

    https://www.xin3721.com/eschool/python.html 在Python里面有一个模块collections,解释是数据类型容器模块.这里面有一个collections. ...

  10. Python数据类型转换 int()、float()、eval()函数

    python数据类型转换 一.其他数据类型转为int型 1.float数据类型转为int 2.整型的字符串转整型 二.其他数据类型转为float型 1.int数据类型转为float型 2.数字类型的字 ...

最新文章

  1. 如何使用TVM Pass Relay
  2. 东方数智发布新品AIDOL™,打造人工智能偶像全息平台
  3. 缓存一致性(MESI)
  4. isnan isinf
  5. 【ARM】Cortex存储系统
  6. BZOJ1008: [HNOI2008]越狱(组合数)
  7. JAVA入门级教学之(内存中的空指针异常)
  8. android服务绑定异步,Android中异步类AsyncTask用法总结
  9. jquery实现app开发闹钟功能_一款让你真正摆脱懒觉的“闹钟APP软件”
  10. Zygo读取保存dat文件(光学领域知道Zygo的一定要看)
  11. java基础三--IO流(1)
  12. 人工智能进场 AR/VR何去何从?
  13. eclipse安装activiti 工作流插件
  14. 《Entity Framework 6 Recipes》中文翻译系列 (24) ------ 第五章 加载实体和导航属性之查询内存对象...
  15. 如何编写一份高质量的测试计划
  16. 哪个计算机无法做到双屏显示,笔记本电脑怎么实现双屏显示不同的内容
  17. Hadoop指令-周美婷
  18. QStyle之PenStyle的CustomDashLine使用
  19. 解决百度ueditor富文本编辑器不能插入视频的问题/src掉链/src清空,不能显示视频
  20. 计算机网络学习云平台,云计算机网络学习系统,云计算机网络实验室设备

热门文章

  1. 深度学习_深度学习基础知识_二分类、单标签多分类、多标签多分类、多任务的含义
  2. 键盘密码(Qwerty)——python解密
  3. Laravel log channel 分组配置
  4. SqlServer 查询一段时间,没有数据自动补充0
  5. SAP 发出商品业务实操和配置(开具销售发票时确认成本)
  6. GIF 格式图片的存储和压缩 Node.js实现
  7. 怎么将视频转化为gif动画?视频如何生成gif格式图片?
  8. 基于量化交易回测的金融股票案例基础知识
  9. [CVPR2022] A Dual Weighting Label Assignment Scheme for Object Detection
  10. 如何解决“应用程序无法启动,因为应用程序的并行配置不正确“问题