It isn't magic numbers as such, rather the smattering of sometimes zeroes and sometimes ones. If it was always zero or one it would be a lot less likely to have off by one errors.
Python solves this by always starting from zero, and crucially not including the final number - ie range(0,3) gives 0, 1, 2 (no 3). You can also do negative indexing to count from the end - eg range(0, 3)[-1] gives the last element (2). The right thing happens when you mix the length of things into the arithmetic too - eg range(0, len(item)).