417
>>> 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?

moinudin
  • 134,091
  • 45
  • 190
  • 216
MetaGuru
  • 42,847
  • 67
  • 188
  • 294
  • 21
    read Dijkstra, ewd831 – SilentGhost Dec 21 '10 at 22:52
  • 19
    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 often – John La Rooy Dec 21 '10 at 23:34
  • 44
    Link to Dijkstra, ewd831: http://www.cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF – unutbu Dec 21 '10 at 23:41
  • 1
    @sundar Dijkstras argument is that the convention `a < i <= b` is the only one for which `a` and `b` never need to leave the set of natural numbers, as long as the range of `i` is either empty or within the set of natural numbers. Although a mostly cosmetic argument, I think it's a perfectly valid one. – andreasdr Apr 21 '15 at 13:29
  • 12
    @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 Jul 24 '16 at 14:11
  • 2
    Another freaky Python convention borrowed. :P I prefer simulating natural intuition and not to make something like January[0], February[1], etc. – Peter.k Nov 21 '17 at 20:30
  • 3
    It would be nice if range() offered an option for inclusive upper bound. I'm fine with range() being exclusive by default, but there are situations where inclusive makes syntax more readable. – Shuklaswag Sep 02 '18 at 19:27
  • 1
    @Shuklaswag I think range(start, end+1) would be much more readable than range(start, end, end_exclusive=False) or any variation thereof. – kkawabat Nov 11 '19 at 18:12
  • @sundar-ReinstateMonica "Easily argued against." How? – user76284 May 01 '20 at 07:31
  • 6
    If a person said they want a range of colors from green to red, then very few people would say they don't want red. So the Eng word range is not appropriate word. This is not going to change but i think this is a chink in the armor that python is a sensible language. – Peter Moore Aug 06 '20 at 13:28
  • 1
    The Range function in R returns the minimum and maximum value from the input vector – SL5net Jan 14 '21 at 06:31
  • Ruby implementation for range: (1..5) => 1, 2, 3, 4, 5 and (1...5) => 1, 2, 3, 4 – toquart Jan 22 '21 at 14:33
  • I've written a bunch of code and that choice of behavior of `range` doesn't make any sense. – EugZol Feb 02 '21 at 01:01
  • Though it was a fairly close call, I decided that the other question is a better canonical duplicate, even though it is currently much less popular. The deciding factor is that the other question is at the *appropriate level of generality*: it mentions other constructs (`slice` objects and the slicing operator) that work the same way for the same reason, without making the question harder to answer. – Karl Knechtel Jan 04 '23 at 03:37

11 Answers11

312

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]
wim
  • 338,267
  • 99
  • 616
  • 750
moinudin
  • 134,091
  • 45
  • 190
  • 216
  • 77
    If that were the reasoning wouldn't the parameters be `range(start, count)`? – Mark Ransom Dec 21 '10 at 22:50
  • 1
    ah, so really it's because x can be 0 – MetaGuru Dec 21 '10 at 22:55
  • 6
    @shogun The start value defaults to 0, i.e. `range(10)` is equivalent to `range(0, 10)`. – moinudin Dec 21 '10 at 22:57
  • 9
    Your `range1` will not work with ranges that have a different step size than `1`. – dimo414 May 21 '15 at 05:09
  • 7
    You explain that range(x) should start with 0 and x will be the "length of the range". OK. But you didn't explain why range(x,y) should start with x and end with y-1. If the programmer wants a for-loop with i ranging from 1 to 3, he has to explicitly add 1. Is this really about convenience? – armin Jul 24 '16 at 14:42
  • 1
    The "length of the range" argument doesn't apply when the step is not equal to 1. i.e. `range(0, 10, 2)` is `[0, 2, 4, 6, 8]` which obviously doesn't have 10 elements. I suppose it would have `end / step` elements, rounding down of course. – dub stylee Oct 25 '16 at 23:26
  • 1
    Even though this is old, something as simple as adding +1 in range, solved my problem. – andyADD Jan 29 '17 at 06:56
  • 1
    @dimo414 Its pretty simple to have an additional parameter as step for range function to work. `range1 = lambda start,end,step: range(start,end+1,step) for i in range(1,26): print(i) for i in range1(1,25,3): print(i)` – Doogle Mar 12 '17 at 13:59
  • 11
    `for i in range(len(li)):` is rather an antipattern. One should use `enumerate`. – hans Mar 06 '18 at 12:17
  • 1
    "prefer" is a strong word – Chris Calo Dec 24 '18 at 02:07
  • 1
    Programmers do prefer 0-based indexing, which is why it's bizarre that Python uses 1-based indexing for stop positions. I don't know many computing languages, but they've all had only one convention. Python has two. The for-loop examples here are deceptive. When I write "5:12", or "range(5, 12, 3)", I mean "go from 5 to 12". If I wanted 11, that's what I would have written. In Python, you use a number to represent the number before it... but only sometimes. – bzip2 Jan 17 '20 at 22:10
  • 1
    this is where Ruby shines: (1..10) vs (1...10) – toquart Apr 10 '20 at 15:26
  • 1
    @bzip2 Programmers don't _prefer_, they are _taught_ that in computer science, due to some conventions, most things are indexed with 0. One may as well create an equivalent set of equally valid conventions where objects are indexed starting from 3512 (or any other number), as well as there are equally valid arithmetics and axiomatic mathematics. – gented Jan 26 '21 at 13:20
  • It's a right-open interval rather then (start, count) because those are nicer to work with. For example contiguous ranges have corresponding end/start values. E.g. this in set of ranges `[0, 8) [8, 32), [64, 128)` it's easier to see that the first two are contiguous and the next two aren't than `(0, 8), (8, 24), (64, 64)`. Using right-open intervals is completely standard in programming. Counts are generally only used for entire containers (e.g. `std::vector` in C++). – Timmmm Jul 19 '22 at 09:34
  • Then the problem is that Python (and some other programming languages) use the illogical, confusing and unnatural 0-indexing. And they need to compensate it with tricks as exclusive boundaries. Most scientific languages, such as R, Matlab, Julia, Fortran, Mathematica, Lua, PL/I, Maple, Sage, APL... use 1-indexing, the same than mathematicians, physicists and engineers. – skan Jan 04 '23 at 19:36
80

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!

dingles
  • 1,548
  • 1
  • 14
  • 11
  • 4
    This explanation is more intuitive. Thanks – Fred Oct 10 '19 at 02:50
  • 2
    Love the stop sign analogy, sorry to steal it :) – coyote Sep 20 '21 at 10:58
  • @nyholku Sorry for that! I have now deleted my comment. It was a harsh response to a harsh comment, which had already been deleted too, so keeping it is completely pointless, especially if it makes other people feel bad for nothing. Technologies should be tools for making great things rather than be apples of discord. – Arthur Khazbs Jun 13 '23 at 08:58
  • @ArthurKhazbs that [deleting] was an act of integrity! Well done. I will delete my comment, though it was kind tongue in cheek. Have a nice day. :) – nyholku Jun 14 '23 at 09:11
27

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.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
20

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.

user200783
  • 13,722
  • 12
  • 69
  • 135
xuanji
  • 5,007
  • 2
  • 26
  • 35
20

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

kindall
  • 178,883
  • 35
  • 278
  • 309
12

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
Skilldrick
  • 69,215
  • 34
  • 177
  • 229
8

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.

Robert
  • 6,412
  • 3
  • 24
  • 26
6

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()
Саша Черных
  • 2,561
  • 4
  • 25
  • 71
Ashish Dixit
  • 61
  • 1
  • 2
  • To avoid the possible surprise of an endless loop, I suggest to improve this code so that it works also in case of a negative step value. – Claudio Mar 11 '22 at 19:18
5

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
Nick Pantelidis
  • 455
  • 4
  • 12
  • 1
    The OP knows how to obtain the extra value, they are asking about the reason it is not included by default. – mins Sep 25 '22 at 15:37
1

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.

Arseny
  • 933
  • 8
  • 16
  • 3
    These defenses of Python's inconsistency are hilarious. If I wanted the interval between two numbers, why would I use subtraction to get the difference instead of the interval? It's inconsistent to use different indexing conventions for start and end positions. Why would you need to write "5:22" in order to get positions 5 to 21? – bzip2 Jan 17 '20 at 22:33
  • It's not Python's, it's pretty common across the board. In C, Java, Ruby, you name it – Arseny Jan 17 '20 at 22:52
  • I meant to say that it's common for indexing, not that the other languages necessarily have the same exact kind of object – Arseny Apr 09 '20 at 18:56
  • @Arseny in defence of Ruby, this is not true. You can construct inclusive and exclusive ranges in Ruby: `(3..5).include?(5) => true` but `(3...5).include?(5) => false`. Array slicing is explicit and inclusive: `[0,1,2,3,4].slice(0,2) => [0, 1]`. You can even construct open ranges: `r = 42..; r.include?(Float::INFINITY) => true` – Andreas Gebhard Feb 23 '22 at 16:04
  • @AndreasGebhard, no doubt there are cases when that's convenient. Scala, for instance, has both `a to b` and `a until b`. My point is that excluding the right end of the range is common practice and isn't an inconsistency whatsoever. Also, historically, the `<` comparison is faster for the processor than the `<=` comparison – Arseny Feb 23 '22 at 18:29
  • @Arseny I agree that excluding the right end of the range is common, and I do not judge Python to adopt this practice. For me, this is fine. I can use inclusive ranges in Ruby now and five minutes later I can use exclusive ranges in Python and still like them both. I just wanted to say that citing Ruby as an example for right-exclusive ranges is slightly simplified because Ruby gives you both, like Scala. – Andreas Gebhard Mar 23 '22 at 07:03
0

Two major uses of ranges in python. All things tend to fall in one or the other

  1. integer. Use built-in: range(start, stop, step). To have stop included would mean that the end step would be assymetric for the general case. Consider range(0,5,3). If default behaviour would output 5 at the end, it would be broken.
  2. floating pont. This is for numerical uses (where sometimes it happens to be integers too). Then use numpy.linspace.
Stefan Karlsson
  • 1,092
  • 9
  • 21