How do I loop with an index in Python without using range(len())?
Short answer
Use enumerate to get the index and the item together, instead of looping over range(len(x)) and indexing back in. It is clearer, less error prone, and works on any iterable.
The short version
Use enumerate, which hands you the index and the item together on each pass. This replaces the clumsy for i in range(len(x)): item = x[i] pattern, which is easy to get wrong and reads poorly. enumerate also works on any iterable, not just things you can index.
The idiomatic way
colours = ["red", "green", "blue"]
for i, colour in enumerate(colours):
print(i, colour)
0 red
1 green
2 blue
You get both the position and the value, with no manual indexing.
The pattern it replaces
# works, but clumsy
for i in range(len(colours)):
print(i, colours[i])
This does the same thing, but it indexes back into the list every iteration, which is more to read, easy to mistype, and breaks on iterables that do not support indexing, such as a file or a generator.
Starting the count elsewhere
enumerate takes a start argument, useful when you want human-friendly numbering from one.
for line_no, line in enumerate(open("notes.txt"), start=1):
print(line_no, line.strip())
Note this works directly on the file object, which you could not index at all.
When you genuinely need range(len())
The one time range(len()) is justified is when you are modifying the list by position as you go, or stepping through indices in a non-sequential way. For simply reading each item with its index, enumerate is the right tool.
If you do not need the index
And if you never use the index, drop it entirely and loop over the items directly.
for colour in colours:
print(colour)
A frequent code smell is introducing an index with range(len()) only to use it once, for x[i], when a plain for item in x would do. Reach for the simplest form that expresses what you need: the item alone, or enumerate when you also want its position.