Python: 拆分函数是否在列表理解中被多次评估?
Python: Is the split function evaluated multiple times in a list comprehension?
有些事情我一直想知道。这个list comprehension中split是执行一次还是多次?
l = [line.split()[i] for i in indexes]
我目前以这种方式进行列表理解:
l = line.rstrip().split()
l = [l for i in indexes]
但我不确定是否有必要。除了 yes/no 的答案之外,我绝对想知道,我如何通过 CPU 分析或阅读一些文档来自己找出这个问题。谢谢
列表理解中左侧的表达式针对每个元素重新计算,是的。
如果你只需要评估一次,你需要完全按照你所做的去做;首先调用它并存储要在列表理解中重复使用的结果。
来自List displays documentation:
In this case, the elements of the new list are those that would be produced by considering each of the for
or if
clauses a block, nesting from left to right, and evaluating the expression to produce a list element each time the innermost block is reached.
强调我的。
您还可以使用 dis.dis()
function:
反汇编列表理解
>>> import dis
>>> dis.dis(compile('[line.split()[i] for i in indexes]', '', 'eval'))
1 0 BUILD_LIST 0
3 LOAD_NAME 0 (indexes)
6 GET_ITER
>> 7 FOR_ITER 22 (to 32)
10 STORE_NAME 1 (i)
13 LOAD_NAME 2 (line)
16 LOAD_ATTR 3 (split)
19 CALL_FUNCTION 0
22 LOAD_NAME 1 (i)
25 BINARY_SUBSCR
26 LIST_APPEND 2
29 JUMP_ABSOLUTE 7
>> 32 RETURN_VALUE
FOR_ITER
操作码启动循环(JUMP_ABSOLUTE
关闭循环),每次执行 LOAD_NAME line
、LOAD_ATTR split
和 CALL_FUNCTION
.换句话说,字节码 13 到 19 实现了 line.split()
部分,并且每次都执行从字节码 7 到 29.
的循环。
(Python 3 注意:列表推导式有自己的作用域,您需要从外部代码对象常量中提取代码对象;dis.dis(compile('[line.split()[i] for i in indexes]', '', 'eval').co_consts[0])
)。
我要补充一点,您的 LC 示例可替代:
l = []
for i in indexes:
l.append(line.split()[i])
所以,答案肯定是肯定的,每次迭代都会对其进行评估。
正如@Dalen 所说,在对列表理解进行推理时,我相信您可以期望它的行为就像您在没有理解的情况下做了同样的事情一样。 @Martijn 展示了如何通过查看程序集来验证这一点。
我的回答
提供了一种更简单的方法来自己验证行为(从某种意义上说,您不需要阅读汇编)。
在列表理解中的多个位置显示函数的行为(当我想知道 "outer function"(见下文)被调用的频率时,我发现了这个问题)。
代码:
def inner_func(i):
print('called inner')
return i
def outer_func(n):
print('called outer')
return range(n)
l = [inner_func(i) for i in outer_func(5)]
这将打印 called outer
一次和 called inner
5 次,验证就像在正常的 for 循环中一样,每个循环执行一次外部函数和一次内部函数。
有些事情我一直想知道。这个list comprehension中split是执行一次还是多次?
l = [line.split()[i] for i in indexes]
我目前以这种方式进行列表理解:
l = line.rstrip().split()
l = [l for i in indexes]
但我不确定是否有必要。除了 yes/no 的答案之外,我绝对想知道,我如何通过 CPU 分析或阅读一些文档来自己找出这个问题。谢谢
列表理解中左侧的表达式针对每个元素重新计算,是的。
如果你只需要评估一次,你需要完全按照你所做的去做;首先调用它并存储要在列表理解中重复使用的结果。
来自List displays documentation:
In this case, the elements of the new list are those that would be produced by considering each of the
for
orif
clauses a block, nesting from left to right, and evaluating the expression to produce a list element each time the innermost block is reached.
强调我的。
您还可以使用 dis.dis()
function:
>>> import dis
>>> dis.dis(compile('[line.split()[i] for i in indexes]', '', 'eval'))
1 0 BUILD_LIST 0
3 LOAD_NAME 0 (indexes)
6 GET_ITER
>> 7 FOR_ITER 22 (to 32)
10 STORE_NAME 1 (i)
13 LOAD_NAME 2 (line)
16 LOAD_ATTR 3 (split)
19 CALL_FUNCTION 0
22 LOAD_NAME 1 (i)
25 BINARY_SUBSCR
26 LIST_APPEND 2
29 JUMP_ABSOLUTE 7
>> 32 RETURN_VALUE
FOR_ITER
操作码启动循环(JUMP_ABSOLUTE
关闭循环),每次执行 LOAD_NAME line
、LOAD_ATTR split
和 CALL_FUNCTION
.换句话说,字节码 13 到 19 实现了 line.split()
部分,并且每次都执行从字节码 7 到 29.
(Python 3 注意:列表推导式有自己的作用域,您需要从外部代码对象常量中提取代码对象;dis.dis(compile('[line.split()[i] for i in indexes]', '', 'eval').co_consts[0])
)。
我要补充一点,您的 LC 示例可替代:
l = []
for i in indexes:
l.append(line.split()[i])
所以,答案肯定是肯定的,每次迭代都会对其进行评估。
正如@Dalen 所说,在对列表理解进行推理时,我相信您可以期望它的行为就像您在没有理解的情况下做了同样的事情一样。 @Martijn 展示了如何通过查看程序集来验证这一点。
我的回答
提供了一种更简单的方法来自己验证行为(从某种意义上说,您不需要阅读汇编)。
在列表理解中的多个位置显示函数的行为(当我想知道 "outer function"(见下文)被调用的频率时,我发现了这个问题)。
代码:
def inner_func(i):
print('called inner')
return i
def outer_func(n):
print('called outer')
return range(n)
l = [inner_func(i) for i in outer_func(5)]
这将打印 called outer
一次和 called inner
5 次,验证就像在正常的 for 循环中一样,每个循环执行一次外部函数和一次内部函数。