当前位置: 首页 > 后端技术 > Python

Python代码的动态执行初探

时间:2023-03-25 23:01:42 Python

作为一种“动态”语言,Python在运行时加载一段代码并执行,这绝对比“静态语言”(如C、Java)需要更方便待编译。执行方法根据是否返回结果可以分为两种:exec和eval。execexec负责执行字符串代码,支持多行,可以定义变量,但不能返回结果defpr(x):print('Myresult:{}'.format(x))if__name__=="__main__":s='''a=15b=3ifa>b:pr(a+b)'''exec(s)执行结果>我的结果:18evaleval可以返回结果,但是只能执行单行表达式defselect_max(x,y):如果x>y则返回x否则yif__name__=="__main__":a=3b=5c=eval('select_max(a,b)')print("cis{}".format(c))执行结果>c是5个运行环境从上面的代码例子可以看出,无论exec还是eval,它们的运行环境都和调用它们的代码一样:无论是全局函数还是局部变量,只要在执行指定代码之前定义了就可以使用,而且exec中定义的变量也可以被后面的代码引用。如果有必要,我们还可以在运行动态代码时指定环境定义的内容,以增加和阻止一些信息。exec和evel都有一个以上的参数,它们的第二个和第三个参数可以分别指定动态代码的globals和locals环境。所谓globals就是代码执行时的全局环境,可以通过globals()函数获取,返回结果是一个dict,列出了所有的全局变量和全局方法,包括导入的模块和方法;同样,locals()函数可以返回所有局部变量和局部方法。当我们调用动态代码时,如果我们像这样传递参数:defselect_max(x,y):returnxifx>yelseyc=eval('select_max(3,5)',{},{})它会覆盖默认的globals(第二个参数)和locals(第三个参数)设置,只能使用buildin方法。这时候,上面的代码就会报错——因为找不到select_max方法。为了使这个方法可用,我们需要给其中一个字典赋值:defselect_max(x,y):returnxifx>yelseyc=eval('select_max(3,5)',{'select_max':select_max},{})这看起来有点像脱裤子放屁:直接用就可以了,为什么要先覆盖,然后再赋值呢?事实上,这是出于安全考虑。安全性动态代码能力通常暴露在程序外部,允许配置器扩展程序逻辑。但如果没有限制,这种能力也是非常危险的:比如调用open方法,可以打开任意文件并删除其内容。所以更安全的方法是禁用内置函数,只暴露允许外部调用的方法:defselect_max(x,y):returnxifx>yelseyc=eval('select_max(3,5)',{"__builtins__":{}},{'select_max':select_max})注:网上有很多文章将__builtins__设置为None。根据实测,至少在Python3.7环境下是不可行的,应该设置为空字典。上面提到的优化编译的两种方式的例子都是直接执行字符串。我们完全可以想象,这些字符串在执行前会被python运行时“解析”(parsed),解析过程非常耗时。因此,为了优化效率,可以先使用编译函数提前“编译”,把字符串变成“代码”,每次执行的效率都会大大提高。defselect_max(x,y):如果x>y则返回x否则yif__name__=="__main__":exp=compile('select_max(a,b)','','eval')foriinrange(10):a=ib=i+10c=eval(exp)print("cis{}".format(c))可以看出字符串编译后变成了表达式,然后重复调用在循环中用这个表达式会比每次都解析字符串效率高很多。同样,exec执行的内容也可以先compile编译成一个表达式。注意:compile的第二个参数是文件名(代码可以直接从文件中读取)。如果没有可以直接清空编译的环境,compile和eval/exec不能在同一个函数中调用,那么它们的执行环境是不一样的,但实际上compile是不检查环境的,变量或动态代码中使用的方法在编译时不需要存在。例如,将上面的代码改为:exp=compile('select_max(a,b)','','eval')defselect_max(x,y):returnxifx>yelseyif__name__=="__main__":foriinrange(10):a=ib=i+10c=eval(exp)print("cis{}".format(c))在定义select_max方法之前编译表达式,不完全影响运行效果:c是10c是11c是12c是13c是14c是15c是16c是17c是18c是19