请记住,这是 Python,而不是 C,有时它的工作方式不一定直观。这意味着您必须验证您的假设。我已经使用 IPython 的内置 %%timeit 魔法完成了这项工作。
a = [1,2,2,3,4,5]
%timeit b=[x for x in a if x > 2]\
1000000 loops, best of 3: 362 ns per loop
%timeit c=[x for x in a if not (x > 2)]
1000000 loops, best of 3: 371 ns per loop
所以两者的时间都不到 800 ns,但我们在循环上迭代了两次。我们当然不需要这样做吗?以上几种方法怎么样?让我们从分类开始,它非常简单,只遍历列表一次:
%timeit b, a = classify(a, lambda x: x > 2)
1000000 loops, best of 3: 1.89 µs per loop
哇,即使它只遍历循环一次,它所花费的时间也是上述简单解决方案的两倍多,后者遍历了两次。让我们尝试对上面提出的其他解决方案稍作改动:
%%timeit
b, c = [], []
for x in a:
b.append(x) if x > 2 else a.append(x)
1000000 loops, best of 3: 1.2 µs per loop
更好,但它仍然比我们的“幼稚”/“低效”实现慢。也许稍微不同的配方会更好:
%%timeit
b, c = [], []
for x in a:
if x > 2:
b.append(x)
else:
c.append(x)
1000000 loops, best of 3: 983 ns per loop
嗯,这看起来几乎一样,但要快一点。仍然没有击败天真的实现。让我们变得非常聪明,也许让它更快:
%%timeit
b, c = [], []
for x in a:
(b, c)[x > 2].append(x)
1000000 loops, best of 3: 1.28 µs per loop
所以,我们看到的是,尽管我们希望不重复循环两次,但我们似乎无法在仅执行两个列表推导上有所改进。列表推导式在 Python 中有点像“幕后”,因此对于许多事情,它们比你想出的任何事情都要快。
现在,x < 2 比较便宜 - 没有函数调用,简单的数学运算。会有一点开始变得有意义。让我们创建一个更昂贵的比较函数 - 我们将计算阶乘(并且效率低下):
def factorial(x):
if x in (0,1):
return 1
else:
return x * factorial(x-1)
%timeit b = [x for x in a if factorial(x) > 6]
100000 loops, best of 3: 3.47 µs per loop
%timeit c = [x for x in a if not factorial(x) > 6]
100000 loops, best of 3: 3.53 µs per loop
所以,显然时间变长了一点 - 现在一切都在 7uS 左右。
现在让我们尝试一些其他示例:
%timeit b, c = classify(a, lambda x: factorial(x) > 6)
100000 loops, best of 3: 5.05 µs per loop
%%timeit
b, c = [], []
for x in a:
if factorial(x) > 6:
b.append(x)
else:
c.append(x)
100000 loops, best of 3: 4.01 µs per loop
%%timeit
b, c = [], []
for x in a:
(b, c)[factorial(x) > 6].append(x)
100000 loops, best of 3: 4.59 µs per loop
所有这些的教训:在处理 python 和效率时,在控制台中尝试通常是一个好主意,但通常天真的实现具有合理的性能并且最容易阅读。快速测试会告诉您尝试优化是否真的值得;如果您不小心,通常会使其可读性降低且速度变慢....
附录:有人评论说我们需要更长的列表,因为我们衡量的是函数调用开销而不是性能。他们有一个很好的观点,但时间在我的机器上显示出大致相同的关系:
In [16]: a = range(100000)
In [17]: random.shuffle(a)
In [18]: %timeit b = [x for x in a if x > 50000]
100 loops, best of 3: 5.2 ms per loop
In [19]: %timeit c = [x for x in m if not x > 50000]
100 loops, best of 3: 5.18 ms per loop
In [20]: %timeit c = [x for x in m if x <= 50000]
100 loops, best of 3: 5.35 ms per loop
In [21]: %%timeit
....: b, c = [], []
....: for x in a:
....: if x > 50000:
....: b.append(x)
....: else:
....: c.append(x)
....:
100 loops, best of 3: 12.7 ms per loop
请注意,如果我将比较更改为 x > 2(而不是 x > 50000),第二个循环会加速到大约 11.4 毫秒。但是,这仍然比简单的实现快一点。也就是说,它可能是我更喜欢的那个——它仍然很容易阅读,而且速度并不慢(或明显慢)。而且随着时间的推移,代码往往会变得更加复杂,因此当您稍后添加另一个比较时,或者将其更改为函数调用时,这样做会更容易,而且在这个版本中性能受到的影响也比单纯的版本要小。
但同样:这并不是说“使用列表推导式”(尽管它们通常是个好主意),而是验证您的假设。