Welcome, guest | Sign In | My Account | Store | Cart

The built-in xrange is fast, but it does not support floats and longs as start,stop,step parameters. This means you cannot iterate large number (that don't fit in an int) ranges via xrange as you would with small numbers.

Python, 64 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class xrange(object):
    """A pure-python implementation of xrange.

    Can handle float/long start/stop/step arguments and slice indexing"""

    __slots__ = ['_slice']
    def __init__(self, *args):
        self._slice = slice(*args)
        if self._slice.stop is None:
            # slice(*args) will never put None in stop unless it was
            # given as None explicitly.
            raise TypeError("xrange stop must not be None")
        
    @property
    def start(self):
        if self._slice.start is not None:
            return self._slice.start
        return 0
    @property
    def stop(self):
        return self._slice.stop
    @property
    def step(self):
        if self._slice.step is not None:
            return self._slice.step
        return 1

    def __hash__(self):
        return hash(self._slice)

    def __cmp__(self, other):
        return (cmp(type(self), type(other)) or
                cmp(self._slice, other._slice))

    def __repr__(self):
        return '%s(%r, %r, %r)' % (self.__class__.__name__,
                                   self.start, self.stop, self.step)

    def __len__(self):
        return self._len()

    def _len(self):
        return max(0, int((self.stop - self.start) / self.step))

    def __getitem__(self, index):
        if isinstance(index, slice):
            start, stop, step = index.indices(self._len())
            return xrange(self._index(start),
                          self._index(stop), step*self.step)
        elif isinstance(index, (int, long)):
            if index < 0:
                fixed_index = index + self._len()
            else:
                fixed_index = index
                
            if not 0 <= fixed_index < self._len():
                raise IndexError("Index %d out of %r" % (index, self))
            
            return self._index(fixed_index)
        else:
            raise TypeError("xrange indices must be slices or integers")

    def _index(self, i):
        return self.start + self.step * i

Example use:

>>> for i in xrange(10000000000000000, 10000000000000005):
...   print i
>>> for i in xrange(3, 5, 0.2):
...   print i
>>> xrange(0, 10, 2)[:3]

These use-cases are not supported by the built-in xrange.

2 comments

Richard Moores 16 years, 10 months ago  # | flag

a bug, I thnk. for i in xrange(1000000000000000000000, 1000000000000000000005, 2): print i,

produces 1000000000000000000000 1000000000000000000002

instead of the expected 1000000000000000000000 1000000000000000000002 1000000000000000000004

AND

for i in xrange(3, 4.1, 0.2): print i,

produces 3.0 3.2 3.4 3.6 3.8 instead of the expected 3.0 3.2 3.4 3.6 3.8 4.0

Alex Monteiro 16 years, 5 months ago  # | flag

Maybe it is not a bug. In fact it may be a rounding problem. Take the xrange(3,4.1,0.2) example: there should be 6 items: 3.0, 3.2, 3.4, 3.6, 3.8 and 4.0, but when the code is calculating _len: (4.1-3)/0.2 is 5.5, and int(5.5)=5.

Created by Eyal Lotem on Mon, 4 Jun 2007 (PSF)
Python recipes (4591)
Eyal Lotem's recipes (1)

Required Modules

  • (none specified)

Other Information and Tasks