我使用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())
输出:
[<symbol '.0'>, <symbol '_[1]'>, <symbol 'x'>]
符号x
是预期的。但是.0
和_[1]
是什么?
请注意,对于任何其他非理解构造,我完全得到了我在代码中使用的标识符。例如,lambda x: y
仅产生符号[<symbol 'x'>, <symbol 'y'>]
。
此外,文档说symtable.Symbol
是......
SymbolTable
中与源中的标识符对应的条目。
......虽然这些标识符显然不会出现在源代码中。
答案 0 :(得分:5)
这两个名称用于将列表推导实现为单独的范围,它们具有以下含义:
.0
是一个隐式参数,用于迭代(在您的情况下来自y
)。_[1]
是符号表中的临时名称,用于目标列表。此列表最终会在堆栈中结束。 * 列表推导(以及dict和set comprehension和generator表达式)在新范围内执行。为此,Python有效地创建了一个新的匿名函数。
因为它是一个函数,实际上,你需要传入你作为参数循环的iterable。这是.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_argcount
元组中的第一个co_varnames
值:
>>> 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
。
每个迭代结果都被x
压入堆栈,再次弹出并存储在STORE_FAST
中LOAD_FAST
,然后再次使用LIST_APPEND
加载到堆栈中。最后,_[1]
从堆栈中获取top元素,并将其添加到堆栈中下一个元素引用的列表中,以便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))
,并调用以迭代器作为参数的函数。
如果您想将其转换为Psuedo代码,它将如下所示:
_[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))
与外部字节码一起,生成器表达式等同于:
>>> 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
* 实际上不再需要临时变量;它们用于最初的理解实现,但是this commit from April 2007将编译器移动到只使用堆栈,这已经成为所有3.x版本以及Python 2.7的标准。将生成的名称视为对堆栈的引用仍然更容易。因为不再需要该变量,所以我提交了issue 32836以删除它,并且Python 3.8及其后将不再将其包含在符号表中。
在Python 2.6中,您仍然可以在反汇编中看到实际的临时名称:
@bot.command(pass_context = True)
async def mute(ctx, user_id, userName: discord.User):
if ctx.message.author.server_permissions.administrator:
user = ctx.message.author
role = discord.utils.get(user.server.roles, name="Muted")
await client.add_roles(user, role)
else:
embed=discord.Embed(title="Permission Denied.", description="You don't have permission to use this command.", color=0xff00f6)
await bot.say(embed=embed)
请注意该名称实际上必须再次删除!
答案 1 :(得分:3)
因此,实现list-comprehensions的方式实际上是通过创建一个代码对象,它有点像创建一次性使用匿名函数,用于范围目的:
>>> import dis
>>> def f(y): [x for x in y]
...
>>> dis.dis(f)
1 0 LOAD_CONST 1 (<code object <listcomp> at 0x101df9db0, file "<stdin>", line 1>)
3 LOAD_CONST 2 ('f.<locals>.<listcomp>')
6 MAKE_FUNCTION 0
9 LOAD_FAST 0 (y)
12 GET_ITER
13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
16 POP_TOP
17 LOAD_CONST 0 (None)
20 RETURN_VALUE
>>>
检查代码对象,我可以找到.0
符号:
>>> dis.dis(f.__code__.co_consts[1])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (x)
12 LOAD_FAST 1 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
注意,list-comp代码对象中的LOAD_FAST
似乎正在加载未命名的参数,该参数对应于GET_ITER