@
locals()与exec()的代码运行推测
本文只讨论在函数内部,exec(object)与locals()同存时的代码运行问题。
关于locals()须知
1.locals()返回的是存储在‘store_fast’数据结构中的数组‘f_localsplus’中的局部变量的副本,修改字典locals不会对当前的局部命名空间造成影响,例如字典locals内容为{b:2},不代表局部命名空间定义过变量b,若直接print(b),可能会报错:name 'b' is not defined
2.推测:调用locals()函数会创建字典locals,首先采集当前作用域中的声明和exec()中的声明,然后自上而下至“当前行的前一行”寻找各声明对应的值,最后以键值对的形式存入字典locals中。
——只有声明没有值或者值在当前行之后的变量,不存入其中。
——若exec()中对某变量的声明在当前作用域中已存在,则仅在当前作用域中寻找该变量的值。
补充
1.因为字典是可变对象,所以在同一作用域新建同名字典时,Python不会重新分配地址,而是在原地址上对字典内数据的引用进行修改。
2.令loc=locals(),调用loc会提取locals()所指向地址中存储的字典locals,不会影响字典locals的内容;而调用locals()则会创建字典locals,覆盖原来的字典locals。示例如下:
def foo():
loc = locals()
print(loc)
print(locals())
foo()
#{}
#{'loc': {...}}
关于exec()须知
exec(object[, globals[, locals]])
——object:必选参数,表示需要被指定的 Python 代码。它必须是字符串或 code 对象。如果 object 是一个字符串,该字符串会先被解析为一组 Python 语句,然后再执行(除非发生语法错误)。如果 object 是一个 code 对象,那么它只是被简单的执行。
默认情况下,exec是作用于局部范围的
——globals:可选参数。globals 是个 dict 对象,用来指定代码执行时可以使用的全局变量以及收集代码执行后的全局变量。如果只指定了 globals 参数,它的值将同样用于 locals 参数。
【当 globals 字典不包含 builtins 这个 key 时, python 会自动加一个指向 builtins 的引用。所以如果要禁止/限制代码使用内置函数的话, 需要同时指定 builtins 这个 key】
——locals:可选参数。locals 可以是任何 mapping 对象,用来指定代码执行时的局部变量以及收集代码执行后的局部变量。如果该参数被忽略,那么它将会取与 globals 相同的值。
补充
exec()参数情况分三种:
1.exec(object)
2.1exec(object,globals)
2.2exec(object,globals,locals)
3.exec(object,{},locals)】
——上面的2.1相当于globals和locals相同的2.2
——2和3中globals+locals必须包含所有object中尚未赋值的变量
【本文只考虑第一种情况】
exec()对locals()的影响
exec()运行后,会将其中的变量储存进字典locals()中。示例如下:
def fun():
print(locals())
exec('b=1+1')
print(locals())
fun()
#{}
#{'b': 2}
例子分析
下方例子结果已经给出,请根据上文自行分析原因。
def fun():
exec('b=1+1')
b = locals()['b']
print(b)
fun() #KeyError: 'b'
b=locals()['b'],该代码首先创建字典locals,采集到当前作用域有b的声明,exec中有b的声明,于是仅在当前作用域寻找b的值,发现b还未赋值,因此字典为空{},不含b,报错keyerror。(参看上方"关于locals()须知"第二条)
def fun():
lc = locals()
exec('b=1+1')
b = lc['b']
print(b)
fun() #2
b=lc['b'],该代码调用了字典locals的内容:{b:2},然后将2绑定给变量b。
def fun():
exec('b=1+1')
z = locals()['b']
print(z)
fun() #2
z= locals()['b'],该代码首先创建字典locals,在当前作用域采集到z的声明,在exec中采集到b的声明,但z在当前行还未有绑定值,所以不存入字典locals中,b有绑定值2,存入字典locals中。locals内容:{b:2}
其他例子
#例0
def foo():
lc = locals()
exec('b=1+1')
b = lc['b']
print(b)
foo() #2
#例1
def foo():
exec('b=1+1')
z = locals()['b']
print(z)
foo() #2
#例2
def foo():
print(locals())
exec('y = 1 + 1')
foo() #{}
#例3
def foo():
exec('y = 1 + 1')
print(locals())
foo() #{'y': 2}
#例4
def foo():
exec('y = 1 + 1')
print(locals())
y=3
foo() #{}
字典locals虽然采集到了y的两处声明,仅在局部作用域中寻找y的绑定值,发现”y=3“,但这行代码在下方,所以locals为变量绑定值只会自上而下至”当前行的前一行“,所以y没有找到绑定值,不存入字典locals中,因此字典locals内容:{}
#例5
def foo():
y=3
exec('y = 1 + 1')
print(locals())
foo() #{y:3}
#例6
def foo():
exec('y = 1 + 1')
y=3
print(locals())
foo() #{y:3}
#例7
def foo():
exec('y = 1 + 1')
z = 3
print(locals())
foo() #{'y': 2, 'z': 3}
#例8
def foo():
exec('b=1+1')
lc = locals()
print(lc)
print(locals())
foo()
#{'b': 2}
#{'b': 2, 'lc': {...}}
读者须知
我只是一个python的初学者,所以我的措辞并不专业,理解也可能不是正确的答案,所以请大家只将其当作一种参考而非正解来阅读。担心会误人子弟,希望文中有错误的地方大家能指出来,感激不尽。
参考文献
[1] https://blog.csdn.net/S1234567_89/article/details/52598433
[2] https://blog.csdn.net/chinesehuazhou2/article/details/90285754
[3] https://blog.csdn.net/sinat_38682860/article/details/103907809
[4] https://blog.csdn.net/sdafhkjas/article/details/103275067