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

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.

Python, 37 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``` ```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) == 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. Steven D'Aprano 6 years, 10 months ago

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...) Steven D'Aprano 6 years, 10 months ago Created by Andrew Barnert on Mon, 12 Jan 2015 (MIT)