一个相当常见的操作是list
基于另一个过滤一个list
。人们很快发现:
[x for x in list_1 if x in list_2]
对于大输入来说很慢 - 它是 O(n*m)。呸。我们如何加快速度?使用 aset
进行过滤查找 O(1):
s = set(list_2)
[x for x in list_1 if x in s]
这给出了很好的整体 O(n) 行为。然而,我经常看到即使是经验丰富的编码人员也陷入了陷阱™:
[x for x in list_1 if x in set(list_2)]
确认!这又是 O(n*m),因为 pythonset(list_2)
每次都构建,而不仅仅是一次。
我认为这就是故事的结局——python 无法将其优化为只构建set
一次。请注意陷阱。必须忍受它。唔。
#python 3.3.2+
list_2 = list(range(20)) #small for demonstration purposes
s = set(list_2)
list_1 = list(range(100000))
def f():
return [x for x in list_1 if x in s]
def g():
return [x for x in list_1 if x in set(list_2)]
def h():
return [x for x in list_1 if x in {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}]
%timeit f()
100 loops, best of 3: 7.31 ms per loop
%timeit g()
10 loops, best of 3: 77.4 ms per loop
%timeit h()
100 loops, best of 3: 6.66 ms per loop
呵呵,python(3.3)可以优化掉一组文字。它甚至比f()
这种情况下更快,大概是因为它可以将 a 替换为LOAD_GLOBAL
a LOAD_FAST
。
#python 2.7.5+
%timeit h()
10 loops, best of 3: 72.5 ms per loop
Python 2 显然没有进行这种优化。我已经尝试进一步调查 python3 正在做什么,但不幸dis.dis
的是无法探究理解表达式的内部结构。基本上所有有趣的东西都会变成MAKE_FUNCTION
.
所以现在我想知道 - 为什么 python 3.x 可以优化设置文字只构建一次,但不能set(list_2)
?