在理解的symtable中,这些额外的符号是什么?

Arm*_*ius 3 python list-comprehension python-3.x python-internals

我正在使用symtable一段代码的符号表.奇怪的是,当使用理解(listcomp,setcomp等)时,我还没有定义一些额外的符号.

复制(使用CPython 3.6):

import symtable

root = symtable.symtable('[x for x in y]', '?', 'exec')
# Print symtable of the listcomp
print(root.get_children()[0].get_symbols())
Run Code Online (Sandbox Code Playgroud)

输出:

[<symbol '.0'>, <symbol '_[1]'>, <symbol 'x'>]
Run Code Online (Sandbox Code Playgroud)

符号x是预期的.但是什么.0_[1]

请注意,对于任何其他非理解构造,我正好得到了我在代码中使用的标识符.例如,lambda x: y仅产生符号[<symbol 'x'>, <symbol 'y'>].

此外,文档说这symtable.Symbol是......

SymbolTable与源中的标识符对应的条目.

......虽然这些标识符显然没有出现在源代码中.

Mar*_*ers 5

这两个名称用于将列表推导实现为单独的范围,它们具有以下含义:

  • .0是一个隐式参数,用于迭代(源自y您的情况).
  • _[1]是符号表中的临时名称,用于目标列表.此列表最终会在堆栈中结束.*

列表推导(以及字典和集合理解和生成器表达式)在新范围内执行.为此,Python有效地创建了一个新的匿名函数.

因为它是一个函数,实际上,你需要传入你正在循环的迭代作为参数.这是.0为了什么,它是第一个隐式参数(所以在索引处0).您生成的符号表明确列出.0为参数:

>>> root = symtable.symtable('[x for x in y]', '?', 'exec')
>>> type(root.get_children()[0])
<class 'symtable.Function'>
>>> root.get_children()[0].get_parameters()
('.0',)
Run Code Online (Sandbox Code Playgroud)

表的第一个子节点是一个名为一个参数的函数.0.

列表理解也需要构建输出列表,该列表也可以被视为本地列表.这是_[1]临时变量.它实际上从未成为生成的代码对象中的命名局部变量; 这个临时变量保留在堆栈上.

您可以在使用时看到生成的代码对象compile():

>>> code_object = compile('[x for x in y]', '?', 'exec')
>>> code_object
<code object <module> at 0x11a4f3ed0, file "?", line 1>
>>> code_object.co_consts[0]
<code object <listcomp> at 0x11a4ea8a0, file "?", line 1>
Run Code Online (Sandbox Code Playgroud)

所以有一个外部代码对象,在常量中,是另一个嵌套的代码对象.后一个是循环的实际代码对象.它使用.0x作为局部变量.它也需要1个参数; 参数的名称是元组中的第一个co_argcountco_varnames:

>>> code_object.co_consts[0].co_varnames
('.0', 'x')
>>> code_object.co_consts[0].co_argcount
1
Run Code Online (Sandbox Code Playgroud)

.0这里的参数名称也是如此.

_[1]临时变量是在栈上处理,请参阅拆卸:

>>> import dis
>>> dis.dis(code_object.co_consts[0])
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (x)
              8 LOAD_FAST                1 (x)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

在这里,我们.0再次参考._[1]BUILD_LIST将列表对象推入堆栈的操作码,然后.0将其放在堆栈上以便FOR_ITER操作码迭代(操作码.0再次从堆栈中删除迭代).

每个迭代结果都被压入堆栈FOR_ITER,再次弹出并存储在x其中STORE_FAST,然后再次加载到堆栈中LOAD_FAST.最后LIST_APPEND从堆栈中获取top元素,并将其添加到堆栈中下一个元素引用的列表中,以便_[1].

JUMP_ABSOLUTE然后将我们带回到循环的顶部,我们继续迭代直到迭代完成.最后,RETURN_VALUE再次将堆栈顶部返回_[1]给调用者.

外部代码对象执行加载嵌套代码对象并将其作为函数调用的工作:

>>> dis.dis(code_object)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x11a4ea8a0, file "?", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (y)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 POP_TOP
             14 LOAD_CONST               2 (None)
             16 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

所以这创建了一个函数对象,使用名为<listcomp>(有助于追溯)的函数,加载y,为它生成一个迭代器(道德等价物iter(y),并以迭代器作为参数调用函数).

如果你想将它翻译成Psuedo代码,它看起来像:

def <listcomp>(.0):
    _[1] = []
    for x in .0:
        _[1].append(x)
    return _[1]

<listcomp>(iter(y))
Run Code Online (Sandbox Code Playgroud)

_[1]临时变量当然是不需要发电机的表达式:

>>> symtable.symtable('(x for x in y)', '?', 'exec').get_children()[0].get_symbols()
[<symbol '.0'>, <symbol 'x'>]
Run Code Online (Sandbox Code Playgroud)

生成器表达式函数对象不是追加到列表,而是生成值:

>>> dis.dis(compile('(x for x in y)', '?', 'exec').co_consts[0])
  1           0 LOAD_FAST                0 (.0)
        >>    2 FOR_ITER                10 (to 14)
              4 STORE_FAST               1 (x)
              6 LOAD_FAST                1 (x)
              8 YIELD_VALUE
             10 POP_TOP
             12 JUMP_ABSOLUTE            2
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

与外部字节码一起,生成器表达式等效于:

def <genexpr>(.0):
    for x in .0:
        yield x

<genexpr>(iter(y))
Run Code Online (Sandbox Code Playgroud)

*实际上不再需要临时变量; 它们被用于理解的初始实现,但是这个从2007年4月开始的提交将编译器移动到只使用堆栈,这已经成为所有3.x版本以及Python 2.7的标准.将生成的名称视为对堆栈的引用仍然更容易.因为不再需要该变量,所以我提交了问题32836以将其删除,并且Python 3.8及以后将不再将其包含在符号表中.

在Python 2.6中,您仍然可以在反汇编中看到实际的临时名称:

>>> import dis
>>> dis.dis(compile('[x for x in y]', '?', 'exec'))
  1           0 BUILD_LIST               0
              3 DUP_TOP
              4 STORE_NAME               0 (_[1])
              7 LOAD_NAME                1 (y)
             10 GET_ITER
        >>   11 FOR_ITER                13 (to 27)
             14 STORE_NAME               2 (x)
             17 LOAD_NAME                0 (_[1])
             20 LOAD_NAME                2 (x)
             23 LIST_APPEND
             24 JUMP_ABSOLUTE           11
        >>   27 DELETE_NAME              0 (_[1])
             30 POP_TOP
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

请注意该名称实际上必须再次删除!