[python3] exec()函数

exec()介绍

exec(str [, globals [, locals]]函数执行一个表达式字符串并返回结果。参数globalslocals都是字典。exec的返回值固定为None。在python2python3中,这个函数的用法是不一样的,本文只考虑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()能够使用太多方法和变量(会有安全隐患),所以可以通过globalslocals参数限制。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

可以看到gloablslocals中都定义了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字典优先级高。

参考

  1. Python exec()
  2. python3-cookbook
  3. 菜鸟教程 Python3 exec 函数

你可能感兴趣的:([python3] exec()函数)