While it doesn't hinder my code, I'm curious as to why this is happening.
Well, you made the following assumption:
since it would count every integer up to and including gridwidth, but also 0.
Did you try testing that assumption?
for i in range(0, gridwidth):
print(i)
Notice that the value of gridwidth is not printed.
Or you could have tried reading the documentation:
>>> help(range)
Help on class range in module builtins:
class range(object)
| range(stop) -> range object
| range(start, stop[, step]) -> range object
|
| Return an object that produces a sequence of integers from start (inclusive) | to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.
| start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.
| These are exactly the valid indices for a list of 4 elements.
| When step is given, it specifies the increment (or decrement).
The reason it happens this way is because that is how range is defined to work.
The motivation for defining range that way is because that's what programmers are accustomed to - because in older languages that make you index into arrays in their loops (instead of just getting the items directly) it's easier to avoid logical errors that way.