For loop using range vs iterables

By Martin McBride, 2022-09-15
Tags: for loop range
Categories: python language intermediate python


Suppose you wanted to loop over all the values in a list. Novice Python programmers will often write something like this:

k = ['red', 'green', 'blue']
for i in range(len(k)):
    print(k[i])

In this code, we use the len to find the length of the list k. The length is 3, in this case.

The range function provides 3 values starting from zero, ie 0, 1, and 2. The loop executes 3 times, with the loop variable i taking value 0, then 1, then 2. The loop prints k[i] for each value.

This works fine but it isn't considered good code for reasons we will discuss later. Here is a much more Pythonic loop:

k = ['red', 'green', 'blue']
for s in k:
    print(s)

This time we don't use a loop index variable i. We loop over the list k directly.

Here is how the for loop works:

Loop over each element in k, and for each element:
    assign the element to the loop variable s
    execute the body of the loop

Why this is better

Looking at the two loops the second loop does not need the extra variable i. While that is obviously a good thing, simpler code is always better, that isn't the main benefit. Look at the for loop line:

for s in k:

This tells you exactly what the code is doing - it is looping over the list k. Compare this with the first case:

for i in range(3):

All this tells you is that the loop will run 3 times. You have to look at the code inside the loop to figure out that each pass through the loop is actually processing the next element of k. That isn't too hard to do in this case because the loop body only contains one line, but in a more complex loop, you might have to study the code to work out what the loop is doing.

A second important benefit is that the first loop will normally be faster. In the first example we are maintaining a variable i, and each time through the loop we need to index into the list using that variable. In the second example, there is no index variable, and the loop always just fetches the next element in the list. The second code simply does less, so it is bound to be faster.

A final benefit of this form is that it lets us iterate over lazy iterators. A lazy iterator is a type of iterator that produces its values one by one, as required. With a lazy iterable, all you can do is ask for the next value, you can't use square brackets to ask for element k[i], so the indexing technique simply doesn't work. In that case, you have to loop over the lazy iterator directly, using a loop counter isn't possible.

Looping over the list directly is, therefore, shorter, easier to understand, works in all cases, and is faster than using a loop counter variable. You would need a very good reason not to do it that way. In fact, it is rare to see loop counter variables in well-written Python code.

Looping over iterables

A for loop can loop over any iterable item. This includes sequences such as lists, tuples or strings. Looping over a tuple is the same as looping over a list, but looping over a string is slightly different (but very useful):

s = 'hello'
for c in s:
    print(c)

In this case, on each pass of the loop, c contains the next character of the string. So the first time through the loop will print 'h', the next time will print 'e', and so on.

Python doesn't have a special data type for a character. A character is represented by a string of length 1.

for can also be used with other types of iterables such as generators and list comprehensions. These objects don't work in quite the same way as a list.

A list has all its values available before the loop starts - they are just the values in the list. A generator, on the other hand, doesn't create values until it is asked for them.

This is called lazy iteration

How does range work?

So, to go right back to the beginning, how does range work? range is a function, so you might simplistically think of it as a function that returns a list:

v = range(5)   # v = [0, 1, 2, 3, 4] ? No!

This is how range used to work in Python 2. But since Python 3, the range function returns a range object, which is a lazy iterable. It creates the sequence of values 0 to 4, one at a time, when the for loop requests them.

In summary, where possible, avoid using a loop counter variable, and loop over the sequence directly. This simplifies the code and makes the intent clear, and runs a bit faster too.

See also

If you found this article useful, you might be interested in the book NumPy Recipes or other books by the same author.

Join the PythonInformer Newsletter

Sign up using this form to receive an email when new content is added:

Popular tags

2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function latex len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart pil pillow polygon pong positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template tex text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest