

What the heck is an xrange? - dcrosta
http://late.am/post/2012/06/18/what-the-heck-is-an-xrange

======
sltkr
Nitpicking, but this statement doesn't work with large numbers (which xrange()
is supposed to handle correctly):

    
    
        self._len = int(ceil(float(stop - start) / step))
    

Python supports arbitrary-length integers; you can't just cast those to
(fixed-length) floating point numbers without losing precision. It's better to
use integer division here, for example:

    
    
        self._len = (stop - start)//step + bool((stop - start)%step)
    

(A variant like (stop - start + step - 1)//step works only for positive
numbers; I guess it could work if you put it in the earlier if-clause and put
a corresponding assignment with +1 at the end in the other branch.)

~~~
dcrosta
Great catch. If you want to submit a pull request on github you can get the
credit -- github.com/dcrosta/xrange

~~~
sltkr
Thanks for the consideration, but I don't really use github, so you go ahead
and take the credit yourself.

~~~
dcrosta
OK, thanks. Updated in github and on the blog.

------
eliben
Nice.

As a side-note, here's a related article on implementing a Python generator
"for real" using the C API:
[http://eli.thegreenplace.net/2012/04/05/implementing-a-
gener...](http://eli.thegreenplace.net/2012/04/05/implementing-a-
generatoryield-in-a-python-c-extension/)

------
dbecker
I didn't expect to learn much when I started reading this, but I was wrong.
Excellent post. Thanks.

------
leetrout
I had a _very_ similar question during my Google interview. In the course of
the day I was tasked with implementing a generator (although answering with
`(x for x in foo)` got a smile I did have to build a class) and later in the
day was asked how a sequence manager can maintain constant time.

This is an excellent post and every Python hacker should read it. Kudos to the
author.

------
mercuryrising
This is cool. I know this isn't how they actually do it, but seeing it in a
language I can understand (Python) and not in C is really pleasing.

You can do tons of examples, but until you figure out how the machine works
inside, you'll be completely lost (you could get it with examples, but you
won't necessarily know WHY you got it, so you'll be useless in helping other
people learn).

------
gus_massa
Does anyone know how this is implemented in Pypy?

~~~
brian_cloutier
[https://bitbucket.org/pypy/pypy/src/default/pypy/annotation/...](https://bitbucket.org/pypy/pypy/src/default/pypy/annotation/builtin.py#cl-53)

------
lloeki
Not worth a pull request, but personally I'd replace:

    
    
            if len(args) == 1:
                start, stop, step = 0, args[0], 1
            elif len(args) == 2:
                start, stop, step = args[0], args[1], 1
            elif len(args) == 3:
                start, stop, step = args
            else:
                raise TypeError('xrange() requires 1-3 int arguments')
    

with:

    
    
            map = [
                    lambda args: (0, args[0], 1),
                    lambda args: (args[0], args[1], 1),
                    lambda args: args,
                  ]
            try:
                start, stop, step = map[len(args)](args)
            except IndexError:
                raise TypeError('xrange() requires 1-3 int arguments')
    

It's more DRY, and it conveys the intent better.

I would _not_ do such a change to the _if step_ block since its pattern feels
noticeably different: "open" checks fit well in a _if/else_ , whereas bunch-
of-equalities fit a dispatch map better (plus you can actually modify the map
at runtime).

~~~
sateesh
To me your version is less clearer than the explicit if calls:

* Name 'map' for a variable is a poor choice (as it has same name as the python builtin function map)

* Your version has off by one error, it doesn't give correct results when called with a single element list or if the list has three elements:
    
    
      >>>mymap = [
                    lambda args: (0, args[0], 1),
                    lambda args: (args[0], args[1], 1),
                    lambda args: args,
                  ]
    
      >>>args = [5]
    
      >>>mymap[len(args)](args)
        IndexError 
        Traceback (most recent call last)
        ....
       # This shouldn't be the case, a list with a single
       # element is a valid input
    
      >>>args = [1,6,1]
    
      >>>mymap[len(args)](args)
        IndexError   Traceback (most recent call last)
        ...
    
       # This shouldn't be the case, a list with a three
       # elements is a valid input

~~~
saintfiends
If only lambda's allowed statements :(.

    
    
      >>>mymap = [
                   lambda args: raise IndexError,
                   lambda args: (0, args[0], 1),
                   lambda args: (args[0], args[1], 1),
                   lambda args: args,
                 ]
    

That aside, this approach would be much slower too.

------
d0mine
Answers to
[http://stackoverflow.com/questions/1482480/xrange2100-overfl...](http://stackoverflow.com/questions/1482480/xrange2100-overflowerror-
long-int-too-large-to-convert-to-int) contain several implementations of
xrange() in pure Python

~~~
dcrosta
Thanks, hadn't seen this.

------
ufo
I cried a little bit on the inside when he said that that the iterator should
not be implemented using generators, etc. Writing iterators by hand forces one
to turn the iteration code inside out and it can be a painful experience if
the logic is anything nontrivial.

~~~
andreasvc
But there's a good motivation here: (x)range objects are supposed to be index-
able, while a generator (expression) cannot go back once an element has been
consumed.

------
exDM69
I feel that this kind of combination of lazy evaluation for sequences,
together with eager evaluation for imperative code hits a particular sweet
spot. Python and Clojure have very nice lazy sequences.

