355
votes
>>> range(1,11)

gives you

[1,2,3,4,5,6,7,8,9,10]

Why not 1-11?

Did they just decide to do it like that at random or does it have some value I am not seeing?

10
read Dijkstra, ewd831SilentGhost
Basically you are choosing one set of off-by-one bugs for another. One set are more likely to cause your loops to terminate early, the other is likely to cause an Exception (or buffer overflow in other languages). Once you have written a bunch of code, you will see that the choice of behaviour range() has makes sense much more oftenJohn La Rooy
Link to Dijkstra, ewd831: cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDFunutbu
@unutbu That Djikstra article is oft-cited in this topic but provides nothing of value here, people use it just as an appeal to authority. The only relevant pseudo-reason he gives for the OP's question is he happens to feel that including the upper bound becomes "unnatural" and "ugly" in the particular case where the sequence in empty - that's a wholly subjective position and easily argued against, so it doesn't bring much to the table. The "experiment" with Mesa isn't of much value either without knowing their particular constraints or evaluation methods.sundar - Remember Monica
@andreasdr But even if the cosmetic argument is valid, doesn't Python's approach introduce a new problem of readability? In common-usage English the term "range" implies that something ranges from something to something -- like an interval. That len(list(range(1,2))) returns 1 and len(list(range(2))) returns 2 is something you really have to learn to digest.armin

10 Answers

280
votes

Because it's more common to call range(0, 10) which returns [0,1,2,3,4,5,6,7,8,9] which contains 10 elements which equals len(range(0, 10)). Remember that programmers prefer 0-based indexing.

Also, consider the following common code snippet:

for i in range(len(li)):
    pass

Could you see that if range() went up to exactly len(li) that this would be problematic? The programmer would need to explicitly subtract 1. This also follows the common trend of programmers preferring for(int i = 0; i < 10; i++) over for(int i = 0; i <= 9; i++).

If you are calling range with a start of 1 frequently, you might want to define your own function:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
45
votes

Although there are some useful algorithmic explanations here, I think it may help to add some simple 'real life' reasoning as to why it works this way, which I have found useful when introducing the subject to young newcomers:

With something like 'range(1,10)' confusion can arise from thinking that pair of parameters represents the "start and end".

It is actually start and "stop".

Now, if it were the "end" value then, yes, you might expect that number would be included as the final entry in the sequence. But it is not the "end".

Others mistakenly call that parameter "count" because if you only ever use 'range(n)' then it does, of course, iterate 'n' times. This logic breaks down when you add the start parameter.

So the key point is to remember its name: "stop". That means it is the point at which, when reached, iteration will stop immediately. Not after that point.

So, while "start" does indeed represent the first value to be included, on reaching the "stop" value it 'breaks' rather than continuing to process 'that one as well' before stopping.

One analogy that I have used in explaining this to kids is that, ironically, it is better behaved than kids! It doesn't stop after it supposed to - it stops immediately without finishing what it was doing. (They get this ;) )

Another analogy - when you drive a car you don't pass a stop/yield/'give way' sign and end up with it sitting somewhere next to, or behind, your car. Technically you still haven't reached it when you do stop. It is not included in the 'things you passed on your journey'.

I hope some of that helps in explaining to Pythonitos/Pythonitas!

25
votes

Exclusive ranges do have some benefits:

For one thing each item in range(0,n) is a valid index for lists of length n.

Also range(0,n) has a length of n, not n+1 which an inclusive range would.

19
votes

It works well in combination with zero-based indexing and len(). For example, if you have 10 items in a list x, they are numbered 0-9. range(len(x)) gives you 0-9.

Of course, people will tell you it's more Pythonic to do for item in x or for index, item in enumerate(x) rather than for i in range(len(x)).

Slicing works that way too: foo[1:4] is items 1-3 of foo (keeping in mind that item 1 is actually the second item due to the zero-based indexing). For consistency, they should both work the same way.

I think of it as: "the first number you want, followed by the first number you don't want." If you want 1-10, the first number you don't want is 11, so it's range(1, 11).

If it becomes cumbersome in a particular application, it's easy enough to write a little helper function that adds 1 to the ending index and calls range().

14
votes

It's also useful for splitting ranges; range(a,b) can be split into range(a, x) and range(x, b), whereas with inclusive range you would write either x-1 or x+1. While you rarely need to split ranges, you do tend to split lists quite often, which is one of the reasons slicing a list l[a:b] includes the a-th element but not the b-th. Then range having the same property makes it nicely consistent.

11
votes

The length of the range is the top value minus the bottom value.

It's very similar to something like:

for (var i = 1; i < 11; i++) {
    //i goes from 1 to 10 in here
}

in a C-style language.

Also like Ruby's range:

1...11 #this is a range from 1 to 10

However, Ruby recognises that many times you'll want to include the terminal value and offers the alternative syntax:

1..10 #this is also a range from 1 to 10
5
votes

Consider the code

for i in range(10):
    print "You'll see this 10 times", i

The idea is that you get a list of length y-x, which you can (as you see above) iterate over.

Read up on the python docs for range - they consider for-loop iteration the primary usecase.

5
votes

Basically in python range(n) iterates n times, which is of exclusive nature that is why it does not give last value when it is being printed, we can create a function which gives inclusive value it means it will also print last value mentioned in range.

def main():
    for i in inclusive_range(25):
        print(i, sep=" ")


def inclusive_range(*args):
    numargs = len(args)
    if numargs == 0:
        raise TypeError("you need to write at least a value")
    elif numargs == 1:
        stop = args[0]
        start = 0
        step = 1
    elif numargs == 2:
        (start, stop) = args
        step = 1
    elif numargs == 3:
        (start, stop, step) = args
    else:
        raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
    i = start
    while i <= stop:
        yield i
        i += step


if __name__ == "__main__":
    main()
1
votes

It's just more convenient to reason about in many cases.

Basically, we could think of a range as an interval between start and end. If start <= end, the length of the interval between them is end - start. If len was actually defined as the length, you'd have:

len(range(start, end)) == start - end

However, we count the integers included in the range instead of measuring the length of the interval. To keep the above property true, we should include one of the endpoints and exclude the other.

Adding the step parameter is like introducing a unit of length. In that case, you'd expect

len(range(start, end, step)) == (start - end) / step

for length. To get the count, you just use integer division.

0
votes

The range(n) in python returns from 0 to n-1. Respectively, the range(1,n) from 1 to n-1. So, if you want to omit the first value and get also the last value (n) you can do it very simply using the following code.

for i in range(1, n + 1):
        print(i) #prints from 1 to n