exec()介绍
exec(str [, globals [, locals]]
函数执行一个表达式字符串并返回结果。参数globals
和locals
都是字典。exec
的返回值固定为None
。在python2
和python3
中,这个函数的用法是不一样的,本文只考虑python3
。
例子
最简单的例子
program = 'a = 5\nb=10\nprint("Sum =", a+b)'
exec(program) # 省略 globals 和 locals参数
# Sum = 15
在这个例子中,将program
转为python
代码执行。
查看在exec中能够使用的变量和方法
exec('print(dir())')
# ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
可以看到存在__builtins__
,这也是exec调用的字符串中能够识别print()
和dir()
函数的原因。
from math import *
exec('print(dir())')
# ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']
看到在导入了math
包后,exec
的字符串参数就可以调用很多math
库的数学函数,比如:
from math import * # 如果不导入库,下面的exec会报错
exec('print(sin(4))')
exec('print(exp(3))')
# -0.7568024953079282
# 20.085536923187668
还可以访问自己定义的函数和变量:
def func():
print("in func")
x = 4
exec('print(dir())')
exec('print(x)')
exec('x=5\nprint(x)')
exec('func()')
# ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func', 'x']
# 4
# 5
# in func
可以看到调用dir()
时,多打印出了'func', 'x'
,表明在exec()
中可以访问func
函数和x
变量。
限制exec中能够使用的变量和方法
大部分情况下,没必要让exec()
能够使用太多方法和变量(会有安全隐患),所以可以通过globals
和locals
参数限制。globals
里面存储全局参数,而locals
里存储局部变量,当只传递globals
时,既包括全局也包括局部。
只传递global,不传locals
from math import *
exec('print(dir())', {})
# ['__builtins__']
# This code will raise an exception
# exec('print(sqrt(9))', {})
可以看到在传递了空字典后,dir()
的结果变少了很多,math
包的内容也不能访问了。
也可以指定能够访问的函数:
from math import *
exec('print(dir())', {'sqrt': sqrt, 'pow': pow})
# object can have sqrt() module
exec('print(sqrt(9))', {'sqrt': sqrt, 'pow': pow})
传递的字典的key
不一定要和函数名相同,可以自定义,比如:
from math import *
exec('print(dir())', {'squareRoot': sqrt, 'pow': pow})
# object can have squareRoot() module
exec('print(squareRoot(9))', {'squareRoot': sqrt, 'pow': pow})
# 下面的话会报异常
# exec('print(sqrt(9))', {'squareRoot': sqrt, 'pow': pow})
设定了'squareRoot': sqrt
之后,在exec中就不能直接访问sqrt
。
还可以限制不能访问__builtins__
:
exec('print("111")', {'__builtins__': None})
# 会有异常,不能够访问print函数
同时传递globals和locals
普通用法:
from math import *
globalsParameter = {'__builtins__' : None}
localsParameter = {'print': print, 'dir': dir}
exec('print(dir())', globalsParameter, localsParameter)
# ['dir', 'print']
开始认为完全可以把参数传递进gloabls
不就行了吗,测试如下:
# 和下面的写法相同
globalsParameter = {'__builtins__' : None,'print': print, 'dir': dir}
exec('print(dir())', globalsParameter)
# 结果和上面不一样
# ['__builtins__', 'dir', 'print']
测试结果发现和使用locals
不一样,对于两者的区别没有找到详细解释,挖坑留待以后。
locals
的优先级比gloabls
高:
x = 10
expr = """
z = 30
sum = x + y + z
print(sum)
"""
def func():
y = 20
exec(expr)
exec(expr, {'x': 1, 'y': 2})
exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
func()
# 60
# 33
# 34
可以看到gloabls
和locals
中都定义了y
,最终以local
中为准。
神奇用法
看别人代码的时候,看到的比较神奇的用法,查阅资料也没有讲的详细的,但是理解一下也觉得是有道理的。简化之后:
python_source = '''print(dir())
def func(a, b):
print(a+b)
print(dir())'''
global_namespace = {}
# 在执行完exec后,python_source字符串的func函数就存在于global_namespace字典中
exec(python_source, global_namespace)
func = global_namespace['func']# 通过字典查找函数
print(func) # 打印出函数的地址空间
func(4,6) # 调用函数
# ['__builtins__'] 最开始 由于传的是空字典,所以只能打印出'__builtins__'
# ['__builtins__', 'func'] 调用exec时,先产生了函数func,此时打印dir(),多了'func',并将这个函数放在了传递进去的 global_namespace 字典中
#
# 10
由上面可以看到,可以将获取exec
调用的结果。
再自由发挥一下,如果字符串内的函数不传参,想使用外面的变量,结果上面整理的结果,可以写成:
a = 4
b = 5
python_source = '''def func():
print(a+b)
'''
global_namespace = {'a':a,'b':b}
# 在执行完exec后,python_source字符串的func函数就存在于global_namespace字典中
exec(python_source, global_namespace)
func = global_namespace['func']# 通过字典查找函数
print(func) # 打印出函数的地址空间
func() # 调用函数
a = 6 # 如果修改变量
func() # 调用函数 结果不变
# 9
# 9 外面的a变了,结果不变,说明传进去的a只是一个备份
可以通过gloabls
参数将外面的变量传递进去,但是传递进去的只是备份,两边修改互不影响。
加入同时传入globals
字典和locals
字典时,变量和函数保留在哪个字典里面呢?
python_source = '''
def func():
print('in func')
print(dir())
a = 5
print(dir())'''
global_namespace = {}
local_namespace = {}
exec(python_source, global_namespace,local_namespace)
a = local_namespace['a']# 通过字典查找变量
# func = global_namespace['func']# 报错
func = local_namespace['func']# 通过字典查找函数
# func = global_namespace['func']# 报错
print(a) # 打印出函数的地址空间
func()
# ['func']
# ['a', 'func']
# 5
# in func
可以看到,在有globals
字典和locals
字典是,内部变量都保存在locals
字典中。
总结
- 用户不传递
globals
字典时,默认会传递一个字典(是什么不知道,应该是当前的全局变量、函数和内置函数)。 - 用户只传递
globals
字典时,如果传递空字典,exec内只能访问内置函数(__builtins__
) - 用户只传递
globals
字典时,如果传递非空字典,exec内只能访问内置函数(__builtins__
)、字典中包含的函数和变量(变量只是一个拷贝,内部修改了不会影响外面的变量,外面修改了不会影响内部的变量)。 - 用户同时传递
globals
字典和locals
字典时,使用方式同上,但是如果globals
字典设置{'\_\_builtins\_\_' : None}
,locals
字典设置其他函数和方法是,就不能访问内置函数(__builtins__
)。注:这一点有点疑问! - exec执行的字符串中定义了函数和变量,是会保存在传递进去的
globals
字典中的,所以可以使用该字典获取字符串内的函数,并调用。 - 同时传递
globals
字典和locals
字典时,locals
字典优先级高。
参考
- Python exec()
- python3-cookbook
- 菜鸟教程 Python3 exec 函数