这两个名称用于将列表推导实现为单独的作用域,它们具有以下含义:
-
.0 是一个隐式参数,用于可迭代(在您的情况下来自y)。
-
_[1] 是符号表中的临时名称,用于目标列表。此列表最终会出现在堆栈中。*
列表推导(以及 dict 和集合推导以及生成器表达式)在新范围内执行。为此,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',)
表的第一个子表是一个带有一个名为.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>
所以有一个外部代码对象,在常量中,是另一个嵌套的代码对象。后一个是循环的实际代码对象。它使用.0 和x 作为局部变量。它还需要 1 参数;参数的名称是 co_varnames 元组中的第一个 co_argcount 值:
>>> code_object.co_consts[0].co_varnames
('.0', 'x')
>>> code_object.co_consts[0].co_argcount
1
所以.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
在这里我们看到.0 再次被引用。 _[1] 是将列表对象推入堆栈的BUILD_LIST 操作码,然后将.0 放入堆栈以供FOR_ITER 操作码迭代(操作码再次从堆栈中删除来自.0 的可迭代对象) .
每个迭代结果由FOR_ITER 压入堆栈,再次弹出并使用STORE_FAST 存储在x 中,然后再次使用LOAD_FAST 加载到堆栈中。最后LIST_APPEND 从堆栈中取出顶部元素,并将其添加到堆栈中下一个元素引用的列表中,因此添加到_[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
因此,这将创建一个函数对象,函数名为 <listcomp>(有助于回溯),加载 y,为其生成一个迭代器(道德上等同于 iter(y),并使用该迭代器调用该函数论据。
如果你想把它翻译成伪代码,它看起来像:
def <listcomp>(.0):
_[1] = []
for x in .0:
_[1].append(x)
return _[1]
<listcomp>(iter(y))
生成器表达式当然不需要_[1] 临时变量:
>>> symtable.symtable('(x for x in y)', '?', 'exec').get_children()[0].get_symbols()
[<symbol '.0'>, <symbol 'x'>]
生成器表达式函数对象不是附加到列表,而是产生值:
>>> 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
加上外层字节码,生成器表达式等价于:
def <genexpr>(.0):
for x in .0:
yield x
<genexpr>(iter(y))
* 这个临时变量其实已经不需要了;它们在推导式的初始实现中使用,但this commit from April 2007 将编译器移动到仅使用堆栈,这已成为所有 3.x 版本以及 Python 2.7 的规范。将生成的名称视为对堆栈的引用仍然更容易。由于不再需要该变量,我提交了issue 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
请注意实际上必须再次删除该名称!