[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)
py> range(1, 6, 2) == range(1, 7, 2)

> 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.

I don't follow your reasoning there. Whether range(*args) succeeds or fails 
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.


More information about the Python-ideas mailing list