目录
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