美文网首页
[python3] exec()函数

[python3] exec()函数

作者: StormZhu | 来源:发表于2018-05-20 14:28 被阅读0次

    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 字典中
    # <function func at 0x000001ED5C829048>
    # 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()函数

          本文链接:https://www.haomeiwen.com/subject/xectjftx.html