关于python:Python-代码动态执行初探

60次阅读

共计 2142 个字符,预计需要花费 6 分钟才能阅读完成。

作为“ 动静 ”语言,Python 在运行时加载一段代码并执行,必定是比须要编译的“动态语言”(比方 C,Java)要不便多了。

执行形式

能够按是否返回后果,简略分为两种:exec 和 eval。

exec

exec 负责执行字符串代码,可反对多行,可定义变量,但无奈返回后果

def pr(x):
    print('My result: {}'.format(x))


if __name__ == "__main__":
    s = '''
a = 15
b = 3
if a > b:
    pr(a+b)
'''
    exec(s)

执行后果
> My result: 18

eval

eval 能够返回后果,但只能执行单行表达式

def select_max(x, y):
    return x if x > y else y


if __name__ == "__main__":
    a = 3
    b = 5
    c = eval('select_max(a , b)')
    print("c is {}".format(c))

执行后果
> c is 5

运行时环境

从下面的代码示例能够看出,无论 exec 还是 eval,它们的运行环境,就跟调用它们地位的代码一样:无论是全局的函数,还是部分的变量,只有在执行指定代码前定义过,就能够应用,并且 exec 中定义的变量,也能够被前面的代码援用。

如果有必要,咱们也能够在运行动静代码的时候,指定环境定义的内容,从而减少和屏蔽一些信息。exec 与 evel,都不止一个参数,他们的第二个和第三个参数,别离能够指定动静代码的 globals 与 locals 环境。

所谓 globals,就是代码执行时的全局环境,能够通过 globals() 函数获取,返回后果是个 dict,列出了所有全局变量和全局办法,包含用 import 的导入的模块和办法;同理,locals() 函数能返回所有局部变量和部分办法。

而咱们调用动静代码的时候,如果像上面这样传参:

def select_max(x, y):
    return x if x > y else y

c = eval('select_max(3 , 5)', {}, {})

就会笼罩掉缺省的 globals(第 2 个参数) 和 locals(第 3 个参数) 设定,只能应用 buildin 的办法了,此时下面的代码就会报错——因为找不到 select_max 办法。为了让这个办法可用,咱们须要给其中某个 dict 赋值:

def select_max(x, y):
    return x if x > y else y

c = eval('select_max(3 , 5)', {'select_max':select_max}, {})

这看起来有点像脱了裤子放屁:明明间接用就好,为什么先笼罩掉,再赋一遍值呢?其实是出于安全性思考。

安全性

动静代码能力,通常是裸露给程序内部的,让配置人员能够扩大程序逻辑。然而如果不加限度,这个能力也是很危险的:比方通过调用 open 办法,能够关上任意文件,删除其内容。

所以比拟平安的形式,是把内置函数也禁用,只裸露容许内部调用的办法:

def select_max(x, y):
    return x if x > y else y

c = eval('select_max(3 , 5)', {"__builtins__": {}}, {'select_max':select_max})

留神:网上有很多文章,把__builtins__设置为 None,经实测,至多在 Python 3.7 环境中是不可行的,该当设置为空字典

优化

编译

下面提到的两种形式示例,都是间接执行字符串。咱们必定能够想到,这些字符串再执行前,会先被 python 运行时“解析”(parsing)一遍,而解析的过程是很耗时的。所以,为了优化效率,能够先用 compile 函数,事后“编译”好,把字符串变成“代码”,每次执行的效率就会大大提高。

def select_max(x, y):
    return x if x > y else y


if __name__ == "__main__":
    exp = compile('select_max(a , b)', '','eval')
    for i in range(10):
        a = i
        b = i + 10
        c = eval(exp)
        print("c is {}".format(c))

能够看进去,字符串通过 compile 之后,变成了表达式,之后在循环中重复调用该表达式,会比每次解析字符串,效率高得多。同样,exec 执行的内容,也能够先用 compile 编译为表达式。

留神:compile 的第二个参数,是文件名(能够间接从文件读取代码),如果没有可间接置空

编译的环境

compile 和 eval/exec 能够不在同一个函数中被调用,那么它们领有的执行环境就不一样,但实际上 compile 并不查看环境,动静代码中用到的变量或办法,在编译时齐全能够不存在。比方把下面的代码改成上面的样子:

exp = compile('select_max(a , b)', '','eval')


def select_max(x, y):
    return x if x > y else y


if __name__ == "__main__":
    for i in range(10):
        a = i
        b = i + 10
        c = eval(exp)
        print("c is {}".format(c))

在定义 select_max 办法之前,就编译表达式,齐全不影响运行成果:

c is 10
c is 11
c is 12
c is 13
c is 14
c is 15
c is 16
c is 17
c is 18
c is 19

正文完
 0