`range(n, -1, -1)` 和 `reversed(range(0, n+1))` 之间有什么区别吗?(涉及 IT 技术)

5

它们产生相同的结果。

>>> for i in range(10, -1, -1):
...     print(i)
... 
10
9
8
7
6
5
4
3
2
1
0

与之相比:

>>> for i in reversed(range(0, 10 + 1):
...     print(i)
... 
10
9
8
7
6
5
4
3
2
1
0

据我了解,Python3的range创建的是生成器,而不是将整个范围存储在内存中。reversed同样是一次生成一个值。是否有使用一个而不是另一个的原因?


我一直认为reversed不会产生生成器,而是生成一个列表。但根据Python 3文档,它确实生成一个迭代器,但我怀疑它是否是生成器。https://docs.python.org/3/library/functions.html?highlight=reversed#reversed - SimonT
2
大多数情况下,选择实际上是在 range(n-1, -1, -1)reversed(range(n)) 之间。第二个选项在可读性方面是明显的赢家,并且它更少出错。考虑到时间差异很小,并且循环体几乎总是占主导地位,我几乎总是会选择 reversed - user2357112
1
@DovGrobgeld 这是错误的。Python 有一些方法可以避免在内存中缓存整个序列。如果对象有一个 __reversed__ 方法,那么 reversed 就会委托给该函数。或者,如果对象同时具有 __len____getitem__ 方法,则可以从这些方法生成智能反转迭代器。只有当这两个选项都不可用时,reversed 才会回退到在内存中缓存整个序列。 - Dunes
@Dunes:谢谢!我之前不知道这些优化。我的错误在于我把最坏情况描述成了总是发生的情况。所以,正如Dunes所说,@Eli Rose,这取决于具体情况。对于reverse(range()),没有任何惩罚,但是对于reversed(myprimenumbergenerator(1000)),除非你遵循Dunes的提示,否则会有惩罚。 - Dov Grobgeld
1
@Dunes:实际上,它从来没有退回到那个状态。尝试使用reversed(reversed(range(10))),你会发现外部的reversed调用只是抛出一个错误,而不是尝试存储所有输出以进行反转。 - user2357112
显示剩余3条评论
4个回答

4
这是使用 timeit 对这两个模块进行测试的结果。
bhargav@bhargav:~$ python -m timeit "for i in range(10, -1, -1):(i)"
1000000 loops, best of 3: 0.466 usec per loop
bhargav@bhargav:~$ python -m timeit "for i in reversed(range(0, 10 + 1)):(i)"
1000000 loops, best of 3: 0.531 usec per loop

正如您所看到的,第二种方式较慢,这是由于它额外调用了函数reversed

这里还要注意到生成器比迭代器快3倍。链接并没有说这个。另外,range不会产生一个生成器,而生成器是迭代器的一种。 - user2357112
@user2357112 已经注意到了!我昨天刚读了那个答案。也许我误解了它!我很乐意删除误导性的话语。再次感谢。 - Bhargav Rao
我想我最好在这里留下我删除的两个链接。 1. 迭代器和生成器之间的速度比较 2. 迭代器和生成器的一般差异 - Bhargav Rao

3
在Python中,reversed()函数有一个特殊情况,当你传递给它一个range()时。 reversed(range(...))range(...)之间唯一的真正区别在于你可以多次迭代range(),但是reversed()返回一个迭代器,因此只能使用一次。
>>> iter(range(0, 10))
<range_iterator object at 0x7f735f5badb0>
>>> reversed(range(0, 10))
<range_iterator object at 0x7f735f5baf30>

你可以看到,在这两种情况下,迭代器类型都是range_iterator。因此,循环本身的性能在两种情况下都是相同的。
由于使用reversed()只会带来一个额外的函数调用开销,所以我总是更喜欢使用reversed(range(10))而不是range(9, -1, -1)

2

reversed 会使其成为一个迭代器,因此根据使用方式可能会有所不同:

In [1]: r =  reversed(range(0, 10 + 1))

In [2]: next(r)
Out[2]: 10

In [3]: r = range(10, -1, -1)

In [4]: next(r)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-0b5056469c9c> in <module>()
----> 1 next(r)

TypeError: 'range' object is not an iterator

1
唯一真正的区别在于range(-n+1, -1, -1)reversed(range(n))之间,就是range()返回一个可以在迭代之前进一步使用/操作的范围对象。而reversed()返回一个迭代器——你所能做的就是迭代它。

以下是使用范围对象的示例,这是无法使用迭代器完成的。

rng = range(20, 40, 2)
length = len(rng)
element = rng[0]
index_of_element = rng.index(element)
membership_test = 0 in rng
new_rng_from_slice = rng[2:5]

由于范围对象可以被切片,这也打开了另一种反转它们的可能性:
assert range(n)[::-1] == range(-n+1, -1, -1)

但是,除非需要范围对象的其他功能,否则reversed(range(n))更易于理解,因此更可取。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接