Python'for'循环的更好方法

时间:2017-10-29 02:30:56

标签: python python-3.x performance time-complexity

我们都知道在Python中执行语句一定次数的常用方法是使用for循环。

这样做的一般方法是,

# I am assuming iterated list is redundant.
# Just the number of execution matters.
for _ in range(count):
    pass

我相信没有人会争辩说上面的代码是常见的实现,但还有另一种选择。通过乘以引用来使用Python列表创建的速度。

# Uncommon way.
for _ in [0] * count:
    pass

还有旧while方式。

i = 0
while i < count:
    i += 1

我测试了这些方法的执行时间。这是代码。

import timeit

repeat = 10
total = 10

setup = """
count = 100000
"""

test1 = """
for _ in range(count):
    pass
"""

test2 = """
for _ in [0] * count:
    pass
"""

test3 = """
i = 0
while i < count:
    i += 1
"""

print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))

# Results
0.02238852552017738
0.011760978361696095
0.06971727824807639

如果存在小的差异,我不会发起主题,但可以看出速度的差异是100%。如果第二种方法效率更高,为什么Python不鼓励这种用法呢?还有更好的方法吗?

测试是使用 Windows 10 Python 3.6 完成的。

按照@Tim Peters的建议,

.
.
.
test4 = """
for _ in itertools.repeat(None, count):
    pass
"""
print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test4, setup=setup).repeat(repeat, total)))

# Gives
0.02306803115612352
0.013021619340942758
0.06400113461638746
0.008105080015739174

这提供了一个更好的方法,这几乎回答了我的问题。

为什么这比range更快,因为两者都是生成器。是因为价值永远不会改变吗?

4 个答案:

答案 0 :(得分:91)

使用

for _ in itertools.repeat(None, count)
    do something

是获得最佳世界的非显而易见的方式:微小的恒定空间要求,并且每次迭代都不会创建新对象。在幕后,repeat的C代码使用本机C整数类型(不是Python整数对象!)来跟踪剩余的计数。

出于这个原因,计数需要适合平台C ssize_t类型,在32位盒子上通常最多2**31 - 1,并且在64位盒子上:< / p>

>>> itertools.repeat(None, 2**63)
Traceback (most recent call last):
    ...
OverflowError: Python int too large to convert to C ssize_t

>>> itertools.repeat(None, 2**63-1)
repeat(None, 9223372036854775807)

这对我的循环来说很重要; - )

答案 1 :(得分:11)

第一种方法(在Python 3中)创建了一个范围对象,它可以遍历值范围。 (它就像一个生成器对象,但你可以多次遍历它。)它不占用大量内存,因为它不包含整个值范围,只有当前值和最大值值,它继续增加步长(默认值1),直到达到或超过最大值。

range(0, 1000)的大小与list(range(0, 1000))的大小进行比较:Try It Online!。前者非常有效;无论大小如何,它只需要48个字节,而整个列表在大小方面呈线性增长。

第二种方法虽然速度更快,却占据了我过去谈论的那种记忆。 (另外,虽然0占用了24个字节,而None占用了16个,但每个10000的数组都有相同的大小。有趣。可能是因为它们的指针很多)

有趣的是,[0] * 10000小于list(range(10000))约10000,这是有道理的,因为在第一个中,所有内容都是相同的原始值,因此可以进行优化。

第三个也很好,因为它不需要另一个堆栈值(而调用range需要调用堆栈上的另一个点),但是因为它慢了6倍,所以#&#6 39;不值得。

最后一个可能是最快的,因为itertools很酷:P我认为它使用了一些C库优化,如果我没记错的话。

答案 2 :(得分:0)

前两种方法需要为每次迭代分配内存块,而第三种方法只需为每次迭代创建一个步骤。

Range是一个缓慢的函数,我只在必须运行不需要速度的小代码时使用它,例如range(0,50)。我想你无法比较这三种方法;他们完全不同。

根据下面的评论,第一种情况仅适用于Python 2.7,在Python 3中它的工作方式与xrange类似,并且不为每次迭代分配一个块。我测试了,他是对的。

答案 3 :(得分:0)

为方便起见,此答案提供了循环构造。有关循环使用itertools.repeat的其他背景,请查找Tim Peters的答案above,Alex Martelli的答案here和Raymond Hettinger的答案here

# loop.py

"""
Faster for-looping in CPython for cases where intermediate integers
from `range(x)` are not needed.

Example Usage:
--------------

from loop import loop

for _ in loop(10000):
    do_something()

# or:

results = [calc_value() for _ in loop(10000)]
"""

from itertools import repeat
from functools import partial

loop = partial(repeat, None)