Python'for'循环的更好方法

Max*_*hon 67 python performance time-complexity python-3.x

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

这样做的一般方法是,

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

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

# Uncommon way.
for _ in [0] * count:
    pass
Run Code Online (Sandbox Code Playgroud)

还有旧的while方式.

i = 0
while i < count:
    i += 1
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

该测试使用Windows 10Python 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
Run Code Online (Sandbox Code Playgroud)

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

为什么这比range两者都快,因为它们都是发电机.是因为价值永远不变吗?

Tim*_*ers 92

运用

for _ in itertools.repeat(None, count)
    do something
Run Code Online (Sandbox Code Playgroud)

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

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

>>> 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)
Run Code Online (Sandbox Code Playgroud)

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

  • 这是一个相当学习的曲线!itertools的源代码位于https://github.com/python/cpython/blob/master/Modules/itertoolsmodule.c,`repeat`的实现跨越`repeat_new`附近的几个不同函数.我怎么知道这个?因为我用Python的源代码玩了25年;-) (4认同)
  • @Beginner,我没有创建Python,但仍然每天都在使用它,是的,相信它的创建者(Guido van Rossum)有充分的理由为此感到骄傲。 (2认同)

Hyp*_*ino 11

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

比较尺寸range(0, 1000)大小list(range(0, 1000)):在线试试!.前者非常有效; 无论大小如何,它只需要48个字节,而整个列表在大小方面呈线性增长.

第二种方法虽然速度更快,却占用了我过去谈论的内存.(而且,似乎虽然0占用24个字节并None占用16个,但10000每个阵列的大小都相同.有趣.可能因为它们是指针)

有趣的[0] * 10000是,小于list(range(10000))大约10000,这是有道理的,因为在第一个,一切都是相同的原始值,所以它可以被优化.

第三个也是不错的,因为它不需要另外的筹码堆值(而调用range需要调用堆栈上的另一个地方),但因为它是慢的6倍,这是不值得说.

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