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与源中的标识符对应的条目.
......虽然这些标识符显然没有出现在源代码中.
这两个名称用于将列表推导实现为单独的范围,它们具有以下含义:
.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)
所以有一个外部代码对象,在常量中,是另一个嵌套的代码对象.后一个是循环的实际代码对象.它使用.0和x作为局部变量.它也需要1个参数; 参数的名称是元组中的第一个co_argcount值co_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)
请注意该名称实际上必须再次删除!
| 归档时间: |
|
| 查看次数: |
149 次 |
| 最近记录: |