Skip to content

newrange: read-only getters, empty range slice fix, and refactoring #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from Dec 14, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions src/future/types/newrange.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""

from collections import Sequence, Iterator
from itertools import islice


class newrange(Sequence):
Expand Down Expand Up @@ -55,6 +56,18 @@ def __init__(self, *args):
self._step = step
self._len = (stop - start) // step + bool((stop - start) % step)

@property
def start(self):
return self._start

@property
def stop(self):
return self._stop

@property
def step(self):
return self._step

def __repr__(self):
if self._step == 1:
return 'range(%d, %d)' % (self._start, self._stop)
Expand Down Expand Up @@ -94,12 +107,7 @@ def __contains__(self, value):
return False

def __reversed__(self):
"""Return a range which represents a sequence whose
contents are the same as the sequence this range
represents, but in the opposite order."""
sign = self._step / abs(self._step)
last = self._start + ((self._len - 1) * self._step)
return newrange(last, self._start - sign, -1 * self._step)
return iter(self[::-1])

def __getitem__(self, index):
"""Return the element at position ``index`` in the sequence
Expand All @@ -119,40 +127,34 @@ def __getitem_slice(self, slce):
of the sequence represented by this range.
"""
start, stop, step = slce.indices(self._len)
return newrange(self[start], stop + self._start, step * self._step)
return newrange(self._start + self._step*start,
self._start + stop,
self._step * step)

def __iter__(self):
"""Return an iterator which enumerates the elements of the
sequence this range represents."""
return rangeiterator(self)
return range_iterator(self)


class rangeiterator(Iterator):
class range_iterator(Iterator):
"""An iterator for a :class:`range`.
"""

def __init__(self, rangeobj):
self._range = rangeobj

# Intialize the "last outputted value" to the value
# just before the first value; this simplifies next()
self._last = self._range._start - self._range._step
self._count = 0
def __init__(self, range_):
self._stepper = islice(_count(range_.start, range_.step), len(range_))

def __iter__(self):
"""An iterator is already an iterator, so return ``self``.
"""
return self

def next(self):
"""Return the next element in the sequence represented
by the range we are iterating, or raise StopIteration
if we have passed the end of the sequence."""
self._last += self._range._step
self._count += 1
if self._count > self._range._len:
raise StopIteration()
return self._last
return next(self._stepper)


# itertools.count in Py 2.6 doesn't accept a step parameter
def _count(start=0, step=1):
while True:
yield start
start += step


__all__ = ['newrange']
19 changes: 17 additions & 2 deletions tests/test_future/test_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,29 @@
from future.builtins import range
from future.tests.base import unittest

from collections import Sequence
from collections import Iterator, Sequence


class RangeTests(unittest.TestCase):
def test_range(self):
self.assertTrue(isinstance(range(0), Sequence))
self.assertTrue(isinstance(reversed(range(0)), Iterator))

def test_bool_range(self):
self.assertFalse(range(0))
self.assertTrue(range(1))
self.assertFalse(range(1, 1))
self.assertFalse(range(5, 2))

def test_equality(self):
def test_equality_range(self):
self.assertEqual(range(7), range(7))
self.assertEqual(range(0), range(1, 1))
self.assertEqual(range(0, 10, 3), range(0, 11, 3))

def test_slice_empty_range(self):
self.assertEqual(range(0)[:], range(0))
self.assertEqual(range(0)[::-1], range(-1, -1, -1))

def test_slice_range(self):
r = range(8)
self.assertEqual(r[:], range(8))
Expand Down Expand Up @@ -99,6 +104,16 @@ def test_slice_zero_step(self):
with self.assertRaisesRegexp(ValueError, msg):
range(8)[::0]

def test_properties(self):
# Exception string differs between PY2/3
r = range(0)
with self.assertRaises(AttributeError):
r.start = 0
with self.assertRaises(AttributeError):
r.stop = 0
with self.assertRaises(AttributeError):
r.step = 0


if __name__ == '__main__':
unittest.main()