Turn slice into range
There's an easier way to do this (at least in 3.4, I don't have 3.3 at the moment, and I don't see it in the changelog).
Assuming your class already has a known length you can just slice a range of that size:
>>> range(10)[1:5:2]range(1, 5, 2)>>> list(range(10)[1:5:2])[1, 3]
If you don't know the length a priori you'll have to do:
>>> class A: def __getitem__(self, item): if isinstance(item, slice): return list(range(item.stop)[item])>>> a = A()>>> a[1:5:2][1, 3]>>> a[1:5][1, 2, 3, 4]
Try
class A: def __getitem__(self, item): ifnone = lambda a, b: b if a is None else a if isinstance(item, slice): if item.stop is None: # do something with itertools.count() else: return list(range(ifnone(item.start, 0), item.stop, ifnone(item.step, 1))) else: return item
This will reinterpret .start
and .step
appropriately if they are None
.
Another option could be the .indices()
method of a slice. It is called with the number of entries and reinterprets None
to the appropriate values and wraps negative values around the given length parameter:
>>> a=slice(None, None, None)>>> a.indices(1)(0, 1, 1)>>> a.indices(10)(0, 10, 1)>>> a=slice(None, -5, None)>>> a.indices(100)(0, 95, 1)
It depends what you intend to do with negative indices...
The problem:
A slice consists of start
, stop
, and step
parameters and can be created with either slice notation or using the slice
built-in. Any (or all) of the start
, stop
, and step
parameters can be None
.
# validsliceable[None:None:None]# also validcut = slice(None, None, None)sliceable[cut]
However, as pointed out in the original question, the range
function does not accept None
arguments. You can get around this in various ways...
The solutions
With conditional logic:
if item.start None: return list(range(item.start, item.stop))return list(range(item.start, item.stop, item.step))
...which can get unnecessarily complex since any or all of the parameters may be None
.
With conditional variables:
start = item.start if item.start is None else 0step = item.step if item.step is None else 1return list(range(item.start, item.stop, item.step))
... which is explicit, but a little verbose.
With conditionals directly in the statement:
return list(range(item.start if item.start else 0, item.stop, item.step if item.step else 1))
... which is also unnecessarily verbose.
With a function or lambda statement:
ifnone = lambda a, b: b if a is None else arange(ifnone(item.start, 0), item.stop, ifnone(item.step, 1)
...which can be difficult to understand.
With 'or':
return list(range(item.start or 0, item.stop or len(self), item.step or 1))
I find using or
to assign sensible default values the simplest. It's explicit, simple, clear, and concise.
Rounding out the implementation
To complete the implementation you should also handle integer indexes (int
, long
, etc) by checking isinstance(item, numbers.Integral)
(see int vs numbers.Integral).
Define __len__
to allow for using len(self)
for a default stop value.
Finally raise an appropriate TypeError
for invalid indexes (e.g. strings, etc).
TL;DR;
class A: def __len__(self): return 0 def __getitem__(self, item): if isinstance(item, numbers.Integral): # item is an integer return item if isinstance(item, slice): # item is a slice return list(range(item.start or 0, item.stop or len(self), item.step or 1)) else: # invalid index type raise TypeError('{cls} indices must be integers or slices, not {idx}'.format( cls=type(self).__name__, idx=type(item).__name__, ))