9

Why does range allow a non-default parameter (stop) to follow a default parameter (start)?

Case in point:

>>> r = range(1, 2, 3)
>>> print(r.start, r.stop, r.step)
1 2 3
>>> r = range(10)
>>> print(r.start, r.stop, r.step)
0 10 1

Trying to emulate the signature is an obvious violation:

def my_range(start=0, stop, end=1):
    pass

I understand that the fact it is implemented in C probably allows for behavior that would be a violation in Pythonland.

I'm guessing this was done to make the API more user-friendly but, I didn't find any sources to back it up (The source code doesn't tell much and PEP 457 only states how range is odd). Does anyone know why this was done?

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • check the docs: seems that `range` has 2 different signatures !! `class range(object) | range(stop) -> range object | range(start, stop[, step]) -> range object ` – Jean-François Fabre May 16 '17 at 10:42
  • @JimFasarakisHiliard closed, but reopened. This isn't a dupe, I should read your questions better. Useful link anyway: http://stackoverflow.com/questions/8637130/how-to-implement-python-method-with-signature-like-start-stop-step-i – Jean-François Fabre May 16 '17 at 10:43
  • @Jean-FrançoisFabre I think I will because I'm not really looking to implement it myself :-). – Dimitris Fasarakis Hilliard May 16 '17 at 10:46
  • 1
    too late, did that for ya :) now I cannot close the question again but I think I won't. I'd better come up with an answer. – Jean-François Fabre May 16 '17 at 10:47
  • 3
    found the source code, didn't help: https://hg.python.org/cpython/file/3f739f42be51/Objects/rangeobject.c (not a comment to explain why). My guess is as good as yours: it's useless to only allow start or step, and having to specify start and stop when doing a loop would be worse than a C loop. Maybe Raymond Hettinger or Alex Martelli could answer. – Jean-François Fabre May 16 '17 at 10:52
  • Glanced there for any comments but didn't find anything from a cursory glance. – Dimitris Fasarakis Hilliard May 16 '17 at 10:56
  • I guess it's so obvious to them (not for us mere mortals) – Jean-François Fabre May 16 '17 at 10:59

1 Answers1

8

I think the question is based on a wrong premise:

I understand that the fact it is implemented in C probably allows for behavior that would be a violation in Pythonland.

It's implemented in C but the behaviour isn't a violation in "Pythonland". The signature in the documentation is just incorrect (not actually incorrect, it's an approximation of the "real signature" - that can be easily understood).

For example range doesn't even support named parameters - but according to the documentation it should:

>>> range(stop=10)
TypeError: range() does not take keyword arguments

So the implementation is more along the lines of:

class range(object):
    def __init__(self, *args):
        start, step = 0, 1
        if len(args) == 1:
            stop = args[0]
        elif len(args) == 2:
            start, stop = args
        elif len(args) == 3:
            start, stop, step = args

That's valid Python and (roughly) does what range internally does (the actual implementation (CPython, Python 3.6.1) could be slightly different so don't take that class to seriously).

However a signature like range(*args) is probably not really helpful for users (especially new users that don't even know what *args means). Having a documentation that says range has 2 signatures: range(stop) and range(start, stop[, step]) may not be (technically) accurate but it "explains" how the signature is interpreted.


As for the why: I don't have any creditable sources but I quickly scanned my code:

I use range(stop) much more often than range(start, stop) or range(start, stop, step). So the one argument case was probably special and common enough to have a convenience for it. It would be pretty annoying to always write range(0, stop) all over the place.

MSeifert
  • 145,886
  • 38
  • 333
  • 352