以下代码片段按预期工作:
def test():
print(f'local symbol table before exec : {locals()}')
exec('a = 0')
print(f'local symbol table after exec : {locals()}')
test()
# printed result:
# local symbol table before exec : {}
# local symbol table after exec : {'a': 0}
然而,一旦我在test
函数的结尾添加一个符号定义语句a = 1
,似乎exec
语句对本地符号表没有影响:
def test():
print(f'local symbol table before exec : {locals()}')
exec('a = 0')
print(f'local symbol table after exec : {locals()}')
a = 1
test()
# printed result:
# local symbol table before exec : {}
# local symbol table after exec : {}
那么,这是为什么呢?
我猜测:函数内部静态定义的符号将以某种方式在编译时保留,如果符号已被保留,则任何动态调用exec函数内部的符号定义语句都无法修改局部符号表。
这是真的吗?实际上在编译时正在发生什么?
额外测试1: 用'a = 0\nprint(locals())'
替换exec函数的参数。
def test():
print(f'local symbol table before exec : {locals()}')
exec('a = 0\nprint(locals())')
print(f'local symbol table after exec : {locals()}')
test()
# printed result:
# local symbol table before exec : {}
# {'a': 0}
# local symbol table after exec : {'a': 0}
def test():
print(f'local symbol table before exec : {locals()}')
exec('a = 0\nprint(locals())')
print(f'local symbol table after exec : {locals()}')
a = 1
test()
# printed result:
# local symbol table before exec : {}
# {'a': 0}
# local symbol table after exec : {}
正如我们所看到的,在exec()
执行期间,符号a
已被成功添加到本地符号表中,但是在a = 1
存在的情况下,它就神奇地消失了。
额外测试2:在a = 1
之前添加return
语句。
def test():
print(f'local symbol table before exec : {locals()}')
exec('a = 0\nprint(locals())')
print(f'local symbol table after exec : {locals()}')
return
test()
# printed result:
# local symbol table before exec : {}
# {'a': 0}
# local symbol table after exec : {'a': 0}
def test():
print(f'local symbol table before exec : {locals()}')
exec('a = 0\nprint(locals())')
print(f'local symbol table after exec : {locals()}')
return
a = 1
test()
# printed result:
# local symbol table before exec : {}
# {'a': 0}
# local symbol table after exec : {}
在第二个test()
函数中,a = 1
是无法到达的,但它仍会影响exec()
的行为。
甚至dis
模块的dis()
函数也无法区分这两个test()
函数。输出结果完全相同,如下所示:
5 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('local symbol table before exec : ')
4 LOAD_GLOBAL 1 (locals)
6 CALL_FUNCTION 0
8 FORMAT_VALUE 0
10 BUILD_STRING 2
12 CALL_FUNCTION 1
14 POP_TOP
6 16 LOAD_GLOBAL 2 (exec)
18 LOAD_CONST 2 ('a = 0\nprint(locals())')
20 CALL_FUNCTION 1
22 POP_TOP
7 24 LOAD_GLOBAL 0 (print)
26 LOAD_CONST 3 ('local symbol table after exec : ')
28 LOAD_GLOBAL 1 (locals)
30 CALL_FUNCTION 0
32 FORMAT_VALUE 0
34 BUILD_STRING 2
36 CALL_FUNCTION 1
38 POP_TOP
8 40 LOAD_CONST 0 (None)
42 RETURN_VALUE