为什么 exec() 中的符号定义语句有时对本地符号表没有影响?

Ala*_* Xu 10 python exec local-variables symbol-table python-3.x

以下代码片段按预期工作:

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}
Run Code Online (Sandbox Code Playgroud)

但是,一旦我a = 1test函数末尾添加了符号定义语句,该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  : {}
Run Code Online (Sandbox Code Playgroud)

那么,为什么会发生这种情况?

这是我的猜测:在函数内部静态定义的符号将在编译时以某种方式被保留,如果符号已经被保留,则在 exec 函数内部动态调用的任何符号定义语句将无法修改本地符号表。

真的吗?编译期间实际发生了什么?


额外测试 1:将 exec 参数替换为'a = 0\nprint(locals())'

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}
Run Code Online (Sandbox Code Playgroud)
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  : {}
Run Code Online (Sandbox Code Playgroud)

正如我们所见,符号aexec()执行过程中被成功添加到本地符号表中,但之后它随着 的存在神奇地消失了a = 1


额外测试 2return在前面添加语句a = 1

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}
Run Code Online (Sandbox Code Playgroud)
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  : {}
Run Code Online (Sandbox Code Playgroud)

a = 1在第二可达test()功能,但它仍然影响的行为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
Run Code Online (Sandbox Code Playgroud)

zen*_*poy 1

根据文档

注意:默认局部变量的行为如下面函数 locals() 中所述:不应尝试修改默认局部变量字典。如果您需要在函数 exec() 返回后查看代码对局部变量的影响,请传递显式局部变量字典。

所以我相信这属于“意外行为”,但我想你可以去实现来exec()真正深入和理解。