Python的exec

  • Post author:
  • Post category:python



目录


exec()简单使用


动态执行简单的字符串代码


动态执行较复杂的代码


执行文件中的Python代码


在exec中传参




使用中遇到的问题


参考


exec()简单使用

个人比较喜欢用Python里面的exec(),可以用来动态执行字符串代码,在for循环里面能快速执行大量类似于list1= 1,list2=2,list3=3..这样的语句,使代码显得更加简洁。

首先简单说一说exec(),exec()是一个十分有趣且实用的内置函数,不同于eval()函数只能执行计算数学表达式的结果的功能,exec()能够动态地执行复杂的Python代码,功能强大但是也有不少小地方容易踩坑的,坑好出但是不太好理解,光影并存吧。

动态执行简单的字符串代码

动态执行较复杂的代码

func = "def fact(n):\n\treturn 1 if n==1 else n*fact(n-1)"
exec(func)
a = fact(5)
print(a)

执行文件中的Python代码

在eg.txt中存储我们想放的Python代码

def fact(n):
    if n==1:
        return 1
    else:
        return n*fact(n-1)
t = fact(6)
print(t)

在exec中传参

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()


使用中遇到的问题

今天在使用时遇到了一些问题简单记录一下。

def main():
    file_list = [2014, 2045, 2065, 2070, 2080, 2110, 2123, 2133]
    generate_outliers_analysis_log(file_list, "log/outliers.log")

    for file in file_list:
        csv_df = csv_file_to_df(r"D:/FTPD/newEnv/" + str(file) + ".csv")
        port_suffix = [33, 35, 36, 37, 39, 40]
        loc = locals()
        for suffix in port_suffix:
            # 通过执行字符串代码来避免反复执行相同语句
            exec("sorted_df_%s =  get_sorted_port_df(csv_df, '25GE1/0/%s')" % (str(suffix), str(suffix)))
        sorted_df_33, sorted_df_35, sorted_df_36 = loc["sorted_df_33"], loc["sorted_df_35"], loc["sorted_df_36"]
        sorted_df_37, sorted_df_39, sorted_df_40 = loc["sorted_df_37"], loc["sorted_df_39"], loc["sorted_df_40"]
        sorted_df_list = [sorted_df_33, sorted_df_35, sorted_df_36, sorted_df_37, sorted_df_39, sorted_df_40]
        name_list = ["25GE1/0/33", "25GE1/0/35", "25GE1/0/36", "25GE1/0/37", "25GE1/0/39", "25GE1/0/40"]
        save_port_figure_and_excel(range(1, 7), sorted_df_list, name_list, str(file), 'port_csv/' + str(file) + '.xlsx')

这一行代码在运行时遇到了报错:

sorted_df_33, sorted_df_35, sorted_df_36 = loc[“sorted_df_33”], loc[“sorted_df_35”], loc[“sorted_df_36”]

报错信息如下:


sorted_df_33, sorted_df_35, sorted_df_36 = loc[“sorted_df_33”], loc[“sorted_df_35”], loc[“sorted_df_36”]


KeyError

: ‘sorted_df_33’。

一看就让人觉得奇怪,为了避免出现KeyError的问题,exec常常和locals()连用。

首先,关于locals,个人认为值得注意的有四点:

  • 1.locals() 字典是局部命名空间的代理,它会


    采集局部作用域的变量


    ,代码运行期若动态修改局部变量,只会影响该字典,并不会影响真正的局部作用域的变量。
  • 2.当再次调用 locals() 时(即

    两次调用locals()时

    ),由于重新采集,则动态(exec())修改的内容会被丢弃,locals()会被刷新为不包含之前exec()执行后的kv对的字典。
  • 3.运行期的局部命名空间不可改变,这意味着 exec() 函数中的变量赋值不会对它产生影响,但


    locals() 字典是可变的,会受到 exec() 函数的影响


  • 4.locals()字典既然是局部命名空间(字典)的代理,会包含在当前局部作用域中的所有的局部变量,那么在把locals()的结果赋给一个变量时,


    就会产生循环引用


第4点什么意思呢,举个简单的例子

def test():
    a = 13
    loc = locals()
    exec('b = a + 1')
    b = loc['b']
    print(b)

在上面这一小段代码中,当执行到loc = locals()这一行时,loc这一个字典会有一个key为‘loc’,值为loc这个字典本身的键值对。

而且这个loc是一个循环引用,看一下下面的debug图就知道了。为什么呢,因为locals()会包含在当前局部作用域中的

所有的局部变量

。由于loc本身也是一个局部变量,所以就造成了


循环引用


exec的常见陷阱


https://segmentfault.com/a/1190000019217209

对遇到的问题的分析

看完上面的链接文章,个人觉得已经讲解的很透彻了。回头来简单看下,就是说对于下面的例一,会报一个KeyError,对于下面的例二,则不会报错。这与locals()的调用位置有关系,


locals()是局部变量的字典的copy,运行期的局部命名空间(局部变量字典)不可改变,这意味着 exec() 函数中的变量赋值不会对它产生影响,但 locals() 字典是可变的,会受到 exec() 函数的影响。意味着我们如果希望在后面获取exec中动态执行的值来赋给新的变量的话,需要在exec之前调用locals(),否则无法获取



好,在此基础上我们来回顾今天遇到的问题,首先简化出现如上问题的业务代码为下图示例3的

exec+locals+占位符

的example3的使用方式,然后利用example4的方式来做一个简单的验证。咦,这里就出现了一个有趣的问题,按理来说,在example3()函数中,第2行定义了loc,这里的loc在第3行第4行执行完成后是会被exec修改的,即loc中是肯定有“a0””a1″“a2”“a3””a4″5个key的,这个在example4中也得到了验证,

即只要不用原本的变量名就可以获取

。我也不太明白为什么会产生这样的报错,也许这是exec和占位符的设计问题吧,由此也可以得出一个结论,在代码中要获取exec动态执行的变量值,建议还是不要重名,避免定位这些细小琐碎的问题花费较多的时间。:


参考


https://segmentfault.com/a/1190000014581721


https://segmentfault.com/a/1190000019217209


https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p23_executing_code_with_local_side_effects.html



版权声明:本文为weixin_42348333原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。