range non-default parameter follows default one

2020-03-13 06:08发布

问题:

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?

回答1:

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.