5

They produce the same results.

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

contrasted with:

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

From what I understand, Python3's range creates a generator rather than storing the whole range in memory. reversed likewise generates its values one at a time, I think. Is there any reason to use one over the other?

Eli Rose
  • 6,788
  • 8
  • 35
  • 55
  • I guess `range()` would be preferred over `reverse()` as the latter one leads a bit more of overhead , But the correct analysis can only be done by logging the execution time of both – ZdaR Apr 12 '15 at 18:18
  • I always though that reversed would not produce a generator but actually make a list. According to the Python 3 docs, it makes an iterator but I doubt it's a generator. https://docs.python.org/3/library/functions.html?highlight=reversed#reversed – SimonT Apr 12 '15 at 18:19
  • 2
    Most of the time, the choice is actually between `range(n-1, -1, -1)` and `reversed(range(n))`. The second option is the clear winner in readability, and it's much less error-prone. Given that the timing difference is minor, and that the loop body will nearly always dominate, I would nearly always go for `reversed`. – user2357112 Apr 12 '15 at 18:21
  • There is one more aspect, if you are looping over a large range. Consider `range(1<<32,-1,-1)` vs `reversed(range(1<<32))`. Perhaps you are writing an algorithm that is exiting after some stopping criteria, e.g. at `(1<<32)-10`. In the second case you will first have to generate *all* the output of `range(1<<32)` before you can start reversing, which will take up a lot of memory and be very slow. You basically loose the memory advantage of having generators. There is no such problem in the first case. – Dov Grobgeld Apr 12 '15 at 18:40
  • @DovGrobgeld So you're saying that `reversed(seq)` for sequence `seq` needs to step through every item in `seq` before it can start yielding anything? – Eli Rose Apr 12 '15 at 19:37
  • 1
    @DovGrobgeld that is wrong. Python has a couple of ways in which `reversed` can avoid having to cache the whole sequence in memory. If the object has a `__reversed__` method then `reversed` delegates to that function. Or, it the object has both `__len__` and `__getitem__` methods then a smart reverse iterator is generated from those methods. Only when neither of these two options are available does `reversed` fall back on caching the whole sequence in memory. – Dunes Apr 12 '15 at 22:56
  • @Dunes: Thanks! I wasn't aware of those optimizations. My mistake was that I described the worse case scenario as always happening. So @Eli Rose, as Dunes said, it depends. For `reverse(range())` there is no penalty, but for `reversed(myprimenumbergenerator(1000))` there will be unless you follow Dunes note. – Dov Grobgeld Apr 13 '15 at 07:15
  • 1
    @Dunes: Actually, it never falls back on that. Try `reversed(reversed(range(10)))`, and you'll see that rather than trying to store all output to reverse it, the outer `reversed` call just throws an error. – user2357112 Apr 15 '15 at 11:10

4 Answers4

4

This is the results from using timeit on the two modules

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

As you can see, the second way is slower that is because it has an additional call to the function reversed.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
  • "Also noted here that generators are 3 times faster than iterators." - that is not what the link is saying at all. Also, `range` does not produce a generator, and generators are a kind of iterator. – user2357112 Apr 12 '15 at 18:29
  • @user2357112 Noted that! I read the answer just yesterday. Perhaps I misinterpreted that!. I will gladly remove the misleading words. Thanks again – Bhargav Rao Apr 12 '15 at 18:30
  • I guess I better leave the two links that I deleted here. 1. [Speed comparison between iterator and Generator](http://stackoverflow.com/questions/4402858/pythons-generator-expression-at-least-3x-faster-than-listgenerator-expres) 2. [General Difference between Iterator and Generator](http://stackoverflow.com/questions/2776829/difference-between-pythons-generators-and-iterators) – Bhargav Rao Apr 12 '15 at 18:35
3

The reversed() function in Python has a special case for when you pass it a range(). The only real difference between reversed(range(...)) and range(...) is that you can iterate over a range() more than once, but reversed() returns an iterator, so it can only be used once.

>>> iter(range(0, 10))
<range_iterator object at 0x7f735f5badb0>
>>> reversed(range(0, 10))
<range_iterator object at 0x7f735f5baf30>

You can see that in both cases, the iterator type is range_iterator. So, the performance of the loop itself will be identical in both cases.

Since the only overhead to reversed() is one extra function call, I always prefer reversed(range(10)) over range(9, -1, -1).

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
2

reversed makes it an iterator so depending on how you use it there are differences:

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
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
1

The only real difference between range(-n+1, -1, -1) and reversed(range(n)) is that range() returns a range object that can be further used/manipulated before iterating over it. Whereas reversed() returns an iterator -- all you can do is iterate over it.

Examples of using a range object, which cannot be done with an iterator.

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]

Since range objects can be sliced this also opens up the possibility of reversing them in another way:

assert range(n)[::-1] == range(-n+1, -1, -1)

However, unless additional functionality of a range object is needed, then reversed(range(n)) is vastly preferable as it is easier to understand.

Dunes
  • 37,291
  • 7
  • 81
  • 97