为何locals()返回奇怪的自引用列表?

14 投票
1 回答
651 浏览
提问于 2025-04-17 21:40

我在函数里用locals()来获取一些参数,这样做效果很好:

def my_function(a, b):
    print locals().values()

>>> my_function(1,2)
[1, 2]

这都是一些常见的操作。不过现在我们来引入一个列表推导式:

def my_function(a, b):
    print [x for x in locals().values()]

>>> my_function(1,2)
[[...], 1, 2]

咦?为什么会出现自我引用的情况呢?

1 个回答

25

在Python 2.7和3.1之前的版本中,生成列表推导式时使用的字节码并不是最优的。在这些版本中,列表推导式会被存储在一个局部变量中(如果在模块范围内,甚至会存储在全局变量中):

>>> import dis
>>> def foo():
...     return [x for x in y]
... 
>>> dis.dis(foo)
  2           0 BUILD_LIST               0
              3 DUP_TOP             
              4 STORE_FAST               0 (_[1])
              7 LOAD_GLOBAL              0 (y)
             10 GET_ITER            
        >>   11 FOR_ITER                13 (to 27)
             14 STORE_FAST               1 (x)
             17 LOAD_FAST                0 (_[1])
             20 LOAD_FAST                1 (x)
             23 LIST_APPEND         
             24 JUMP_ABSOLUTE           11
        >>   27 DELETE_FAST              0 (_[1])
             30 RETURN_VALUE        

这里的_[1]局部变量就是正在构建的列表。当嵌套使用列表推导式时,会用递增的整数来表示结果:

>>> def bar():
...     return [[x for x in y] for z in spam]
... 
>>> dis.dis(bar)
  2           0 BUILD_LIST               0
              3 DUP_TOP             
              4 STORE_FAST               0 (_[1])
              7 LOAD_GLOBAL              0 (spam)
             10 GET_ITER            
        >>   11 FOR_ITER                40 (to 54)
             14 STORE_FAST               1 (z)
             17 LOAD_FAST                0 (_[1])
             20 BUILD_LIST               0
             23 DUP_TOP             
             24 STORE_FAST               2 (_[2])
             27 LOAD_GLOBAL              1 (y)
             30 GET_ITER            
        >>   31 FOR_ITER                13 (to 47)
             34 STORE_FAST               3 (x)
             37 LOAD_FAST                2 (_[2])
             40 LOAD_FAST                3 (x)
             43 LIST_APPEND         
             44 JUMP_ABSOLUTE           31
        >>   47 DELETE_FAST              2 (_[2])
             50 LIST_APPEND         
             51 JUMP_ABSOLUTE           11
        >>   54 DELETE_FAST              0 (_[1])
             57 RETURN_VALUE        

通过循环访问locals().values(),你会在返回值中包含对正在构建的列表的引用。需要注意的是,字节码使用了DELETE_FAST来清理局部名称,以尽量避免命名空间的污染。

这个问题在Python 3.1和2.7中得到了优化,具体可以查看问题2183。正在构建的列表结果被移到了栈上。这个优化改变了LIST_APPEND字节码,使其引用栈上哪个列表进行追加,从而不再需要在开始时使用DUP_TOP -> STORE_FAST,每次迭代时使用LOAD_FAST,以及在列表推导式后使用DELETE_FAST

撰写回答