[英]A better way for a Python 'for' loop
We all know that the common way of executing a statement a certain number of times in Python is to use a for
loop. 我们都知道在Python中执行语句一定次数的常用方法是使用for
循环。
The general way of doing this is, 这样做的一般方法是,
# I am assuming iterated list is redundant.
# Just the number of execution matters.
for _ in range(count):
pass
I believe nobody will argue that the code above is the common implementation, however there is another option. 我相信没有人会争辩说上面的代码是常见的实现,但是还有另一种选择。 Using the speed of Python list creation by multiplying references. 通过乘以引用来使用Python列表创建的速度。
# Uncommon way.
for _ in [0] * count:
pass
There is also the old while
way. 也有老的while
方式。
i = 0
while i < count:
i += 1
I tested the execution times of these approaches. 我测试了这些方法的执行时间。 Here is the code. 这是代码。
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
I would not initiate the subject if there was a small difference, however it can be seen that the difference of speed is 100%. 如果存在小的差异,我不会发起主题,但是可以看出速度的差异是100%。 Why does not Python encourage such usage if the second method is much more efficient? 如果第二种方法效率更高,为什么Python不鼓励这种用法呢? Is there a better way? 有没有更好的办法?
The test is done with Windows 10 and Python 3.6 . 该测试使用Windows 10和Python 3.6完成 。
Following @Tim Peters' suggestion, 按照@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
Which offers a much better way, and this pretty much answers my question. 这提供了一个更好的方式,这几乎回答了我的问题。
Why is this faster than range
, since both are generators. 为什么这比range
更快,因为两者都是发电机。 Is it because the value never changes? 是因为价值永远不变吗?
Using 运用
for _ in itertools.repeat(None, count)
do something
is the non-obvious way of getting the best of all worlds: tiny constant space requirement, and no new objects created per iteration. 是获得最佳世界的非显而易见的方法:微小的恒定空间要求,并且每次迭代都不会创建新对象。 Under the covers, the C code for repeat
uses a native C integer type (not a Python integer object!) to keep track of the count remaining. 在封面下, repeat
的C代码使用本机C整数类型(不是Python整数对象!)来跟踪剩余的计数。
For that reason, the count needs to fit in the platform C ssize_t
type, which is generally at most 2**31 - 1
on a 32-bit box, and here on a 64-bit box: 出于这个原因,计数需要适合平台C ssize_t
类型,在32位盒子上通常最多2**31 - 1
,这里是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)
Which is plenty big for my loops ;-) 这对我的循环来说很重要;-)
The first method (in Python 3) creates a range object, which can iterate through the range of values. 第一种方法(在Python 3中)创建一个范围对象,它可以遍历值范围。 (It's like a generator object but you can iterate through it several times.) It doesn't take up much memory because it doesn't contain the entire range of values, just a current and a maximum value, where it keeps increasing by the step size (default 1) until it hits or passes the maximum. (它就像一个生成器对象,但你可以多次遍历它。)它不会占用太多内存,因为它不包含整个值范围,只包含当前值和最大值,它不断增加步长(默认为1),直到达到或超过最大值。
Compare the size of range(0, 1000)
to the size of list(range(0, 1000))
: Try It Online! 将range(0, 1000)
的大小与list(range(0, 1000))
的大小list(range(0, 1000))
: 在线试用! . 。 The former is very memory efficient; 前者非常有效; it only takes 48 bytes regardless of the size, whereas the entire list increases linearly in terms of size. 无论大小如何,它只需要48个字节,而整个列表在大小方面呈线性增长。
The second method, although faster, takes up that memory I was talking about in the past one. 第二种方法虽然速度更快,却占用了我过去谈论的内存。 (Also, it seems that although 0
takes up 24 bytes and None
takes 16, arrays of 10000
of each have the same size. Interesting. Probably because they're pointers) (另外,似乎虽然0
占用24个字节而None
占用16个,但每个10000
个数组的大小相同。有趣。可能因为它们是指针)
Interestingly enough, [0] * 10000
is smaller than list(range(10000))
by about 10000, which kind of makes sense because in the first one, everything is the same primitive value so it can be optimized. 有趣的是, [0] * 10000
小于list(range(10000))
大约10000,这是有道理的,因为在第一个中,所有内容都是相同的原始值,因此可以进行优化。
The third one is also nice because it doesn't require another stack value (whereas calling range
requires another spot on the call stack), though since it's 6 times slower, it's not worth that. 第三个也很好,因为它不需要另一个堆栈值(而调用range
需要调用堆栈上的另一个点),但由于它慢了6倍,所以不值得。
The last one might be the fastest just because itertools
is cool that way :PI think it uses some C-library optimizations, if I remember correctly. 最后一个可能是最快的,因为itertools
很酷:PI认为它使用了一些C库优化,如果我没记错的话。
The first two methods need to allocate memory blocks for each iteration while the third one would just make a step for each iteration. 前两种方法需要为每次迭代分配内存块,而第三种方法只需为每次迭代创建一个步骤。
Range is a slow function, and I use it only when I have to run small code that doesn't require speed, for example, range(0,50)
. 范围是一个缓慢的函数,只有当我必须运行不需要速度的小代码时才使用它,例如, range(0,50)
。 I think you can't compare the three methods; 我想你无法比较这三种方法; they are totally different. 他们是完全不同的。
According to a comment below, the first case is only valid for Python 2.7, in Python 3 it works like xrange and doesn't allocate a block for each iteration. 根据下面的评论,第一种情况仅适用于Python 2.7,在Python 3中它的工作方式与xrange类似,并且不为每次迭代分配一个块。 I tested it, and he is right. 我测试了,他是对的。
This answer provides a loop construct for convenience. 这个答案提供了一个方便的循环结构。 For additional background about looping with itertools.repeat
look up Tim Peters' answer above , Alex Martelli's answer here and Raymond Hettinger's answer here . 有关使用循环的其他背景itertools.repeat
查找添彼得斯的回答以上 ,亚历克斯·马尔泰利的答案在这里和雷蒙德赫廷杰的答案在这里 。
# 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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.