在比较 Python 中 filter(xs, lambda x: x != el)
的各种等效形式时,我偶然发现了一些让我感到惊讶的事情。考虑以下形式:
def method1(xs, el):
p = lambda x: x != el
return [x for x in xs if p(x)]
def method2(xs, el):
return [x for x in xs if (lambda y: y != el)(x)]
我希望 Python 只构建一次 lambda,然后将其存储在一个临时变量中,这样两种形式的性能都差不多。由于名称查找,甚至 method1
的性能可能更差。
但是当我对它们进行基准测试时,结果证明 method2
的性能始终比 method1
差。为什么是这样?是否为每次迭代重建 lambda?
我的基准测试脚本(在一个单独的模块中,并期望 methods
包含 method1
和 method2
)如下:
import math, timeit
def bench(n,rho,z):
pre = """\
import random
from methods import %(method)s
x = [(random.randint(0,%(domain)i)) for r in xrange(%(size)i)]
el = x[0]\
"""
def testMethod(m):
mod = pre % { 'method': m, 'domain': int(math.ceil(n / rho)), 'size': n }
return timeit.timeit("%s(x, el)" % m, mod, number = z)/(z * n)
print "Testing", n, rho, z
return tuple(testMethod(m) for m in ("method1", "method2"))
n = 31
min_size, max_size = 10.0**1, 10.0**4
size_base = math.pow(max_size / min_size, 1.0/(n-1))
# size_default = 10**3
#min_sel, max_sel = 0.001, 1.0
#sel_base = math.pow(max_sel / min_sel, 1.0/(n-1))
sel_default = 0.001
tests = [bench(int(min_size*size_base**x), sel_default, 100) for x in xrange(n)]
#tests = [bench(size_default, min_sel*sel_base**x, 100) for x in xrange(n)]
def median(x):
x = list(sorted(x))
mi = int(len(x)/2)
if n % 2 == 0:
return x[mi]
else:
return (x[mi] + x[mi+1])/2
def madAndMedian(x):
meh = median(x)
return meh, median([abs(xx - meh) for xx in x])
for z in zip(*tests):
print madAndMedian(z)
最佳答案
是的,它在每个循环中重建 lambda;它需要重新评估整个表达式。
要查看此内容,请使用 dis
module :
>>> dis.dis(method1)
2 0 LOAD_CLOSURE 0 (el)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object <lambda> at 0x102000230, file "<stdin>", line 2>)
9 MAKE_CLOSURE 0
12 STORE_FAST 2 (p)
3 15 BUILD_LIST 0
18 LOAD_FAST 0 (xs)
21 GET_ITER
>> 22 FOR_ITER 24 (to 49)
25 STORE_FAST 3 (x)
28 LOAD_FAST 2 (p)
31 LOAD_FAST 3 (x)
34 CALL_FUNCTION 1
37 POP_JUMP_IF_FALSE 22
40 LOAD_FAST 3 (x)
43 LIST_APPEND 2
46 JUMP_ABSOLUTE 22
>> 49 RETURN_VALUE
>>> dis.dis(method2)
2 0 BUILD_LIST 0
3 LOAD_FAST 0 (xs)
6 GET_ITER
>> 7 FOR_ITER 33 (to 43)
10 STORE_FAST 2 (x)
13 LOAD_CLOSURE 0 (el)
16 BUILD_TUPLE 1
19 LOAD_CONST 1 (<code object <lambda> at 0x101fd37b0, file "<stdin>", line 2>)
22 MAKE_CLOSURE 0
25 LOAD_FAST 2 (x)
28 CALL_FUNCTION 1
31 POP_JUMP_IF_FALSE 7
34 LOAD_FAST 2 (x)
37 LIST_APPEND 2
40 JUMP_ABSOLUTE 7
>> 43 RETURN_VALUE
LOAD_CONST
操作码加载 lambda 体的编译代码; MAKE_CLOSURE
从中创建 lambda。对于 method1
这发生一次,而对于 method2
这在循环的每次迭代中重复(从 FOR_ITER
操作码到 JUMP_ABSOLUTE
操作码);请注意 method1
中变量 p
的 LOAD_FAST
操作码,它引用局部变量。
关于python - Python 列表理解的性能 : rebuilding lambda in `if` part?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14235594/