[Python-ideas] Should range() == range(0)?

Steven D'Aprano steve at pearwood.info
Mon May 7 02:46:32 CEST 2012

```Terry Reedy wrote:
> It is a general principle that if a built-in class C has a unique (up to
> equality) null object, then C() returns that null object.
>
>  >>> for f in (bool, int, float, complex, tuple, list, dict, set,
> frozenset, str, bytes, bytearray):
>     print(bool(f()))
>
> # 12 lines of False

I don't think that's so much a general principle that should be aspired to as
a general observation that many objects have an obvious "nothing" (empty)
value that intuitively matches the zero-argument case, e.g. set, dict, list
and so forth.

The cases of int, float, complex etc. are a little more dubious; I'm not
convinced there's a general philosophical reason why int() should be allowed
at all. E.g. int("") fails, int([]) fails, etc. so there's no general
principle that the int of "emptiness" is expected to return 0.

The fact that float() has to choose between two zero objects, complex()
between four, and Fraction and Decimal between an infinity of zero objects,
highlights that the choice of a "default" is at least in part an arbitrary
choice. If Python has any general principle here, it is that we should be
reluctant to make arbitrary choices in the face of ambiguity.

For the avoidance of doubt, I'm not arguing for changing the behaviour of int.
The current behaviour is fine. But I don't think we should treat it as a
general principle that other objects should necessarily follow.

> Some imported classes such as fractions.Fraction and collections.deque
> can be added to the list.
[...]
> It is true that there are multiple distinct null range objects (because
> the defining start,stop,step args are kept as attributes) but they are
> all equal.
>  >>> range(1,1) == range(0)
> True

Are you using Python 2 here? If so, you should be looking at xrange, not
range. In Python 3, range objects are equal if their start, stop and step
attributes are equal, not if their output values are equal:

py> range(0) == range(1,1)
False
py> range(1, 6, 2) == range(1, 7, 2)
False

> range(0) == range(0, 0, 1) would be the obvious choice for range().

I'm not entirely sure that is quite so obvious. range() defaults to a start of
0 and a step of 1, so it's natural to reason that range() => range(0, end, 1).
But surely we should treat end to be a required argument? If end is not
required, that suggests the possibility of calling range with (say) a start
value only, using the default end and step values.

I think there is great value in keeping range simple, and the simplest thing
is to keep end as a required argument and refuse the temptation to guess if it
is not given.

I do think this is a line-call though. If I were designing range from scratch,
I too would be sorely tempted to have range() => range(0).

> Another advantage of doing this, beside consistency, is that it would
> emphasize that range() produces a re-iterable sequence, not just an
> iterator.

for some arbitrary value of args has no bearing on whether it is re-iterable.
Consider zip().

> 6. filter() does not work.
>
> While filter is a class, its instances, again, are dependent on another
> object, not just at creation but during its lifetime. Moreover,
> bool(empty-iterable) is not False. Ditto for map() and, for instance,
> open(), even though in the latter case the primary object is external.

Likewise reversed() and iter().

sorted() is an interesting case, because although it returns a list rather
than a (hypothetical) SortedSequence object, it could choose to return [] when
called with no arguments. I think it is right to not do so.

zip() on the other hand is a counter-example, and it is informative to think
about why zip() succeeds while range() fails. zip takes an arbitrary number of
arguments, where no particular argument is required or treated differently
from the others. Also there is a unique interpretation of zip() with no
arguments: an empty zip object (or list in the case of Python 2).

Nevertheless, I consider it somewhat surprising that zip() succeeds, and don't
think that it is a good match for range.

Given the general principle "the status quo wins", I'm going to vote -0 on the
suggested change.

--
Steven

```