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

14

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

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
1个回答

1
根据文档

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

因此,我认为这属于“意外行为”,但我想你可以去实现exec()来真正深入了解。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接