An equivalent of `numpy.linspace`

, but as a pure-Python lazy sequence.

Like NumPy's `linspace`

, but unlike the `spread`

and `frange`

recipes listed here, the `num`

argument specifies the number of values, not the number of intervals, and the range is closed, not half-open.

Although this is primarily designed for floats, it will work for `Fraction`

, `Decimal`

, NumPy arrays (although this would be silly) and even `datetime`

values.

This recipe can also serve as an example for creating lazy sequences.

See the discussion below for caveats.

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 | ```
class linspace(collections.abc.Sequence):
"""linspace(start, stop, num) -> linspace object
Return a virtual sequence of num numbers from start to stop (inclusive).
If you need a half-open range, use linspace(start, stop, num+1)[:-1].
"""
def __init__(self, start, stop, num):
if not isinstance(num, numbers.Integral) or num <= 1:
raise ValueError('num must be an integer > 1')
self.start, self.stop, self.num = start, stop, num
self.step = (stop-start)/(num-1)
def __len__(self):
return self.num
def __getitem__(self, i):
if isinstance(i, slice):
return [self[x] for x in range(*i.indices(len(self)))]
if i < 0:
i = self.num + i
if i >= self.num:
raise IndexError('linspace object index out of range')
if i == self.num-1:
return self.stop
return self.start + i*self.step
def __repr__(self):
return '{}({}, {}, {})'.format(type(self).__name__,
self.start, self.stop, self.num)
def __eq__(self, other):
if not isinstance(other, linspace):
return False
return ((self.start, self.stop, self.num) ==
(other.start, other.stop, other.num))
def __ne__(self, other):
return not self==other
def __hash__(self):
return hash((type(self), self.start, self.stop, self.num))
``` |

For Python 3.3+ code, use `collections.abc.Sequence`

instead of `collections.Sequence`

.

There are two obvious simple algorithms for `linspace`

(plus some more advanced ones):

- division first:
`start + i*(stop-start)/(num-1)`

- multiplication first:
`(stop*i + start*(num-i-1))/(num-1)`

This recipe uses the former, primarily because it's the one used by NumPy.

Both are simple and fast; neither accumulates errors (both will close to the minimum possible number of 1 ulp errors distributed evenly throughout the range, which is as good as you can hope for with floats); but neither is perfect:

- Division first underflows denormal numbers to
`0`

. (See NumPy bug #5437) - Multiplication first overflows very large numbers to
`inf`

. - Multiplication first doesn't match NumPy's results.
- Division first errors show up worse in a few highly visible cases (e.g.,
`linspace(0, 1, 11)[3] == 0.30000000000000004`

). - Multiplication first requires types that can be multiplied and divided by integers, so it will not work with, e.g.,
`datetime`

. (Note that division first only multiplies and divides _differences_ between valuesâ€”so, with`datetime`

,`timedelta`

s.)

For many lazy sequences, a slice should return an instance of the same sequence. This is how the builtin `range`

works, for instance. However, floating point rounding makes that impossible for `linspace`

; a slice could at best guarantee a sequence whose values are within 2 ulp of the original values. So, a slice instead returns a list.

Inheriting from `Sequence`

means that `linspace`

provides `__contains__`

, `index`

, and `count`

methods, using the default (linear-search) implementation. It's generally a bad idea to use these (for the same reason it's a bad idea to compare floats with `==`

), but not providing them would mean `linspace`

is no longer a `Sequence`

. Of course an `O(1)`

implementation could be provided pretty easily, but that would just encourage (mis)use.

Thanks for linking to my recipes, but you linked to the same one twice :-(

I think you meant spread. (Hope I got the markdown syntax right...)

Ah drat. Let's try that link again: spread