Basic FOR cycle

Two most basic FOR cycles in Python are iterations over indices and iterations over list of values.

Iteration over indices:

for i in range(len(my_list)):
print(f"Index: {i}")

Iteration over list of values:

for x in my_list:
print(f"Value: {x}")

However, as you improve in Python, you are more likely to need to write a more complex code and you will need to get more information while trying to have the code as clear as possible. So let's cover some advanced tricks that may ease your work, specifically:

  • How to get an index and a value at once
  • How to traverse a list by two successive numbers
  • How to measure time per iteration

Advanced tips

Getting an index of an item and a value

There are two easy solutions for how to get an index and a value at the same time, using basic FOR cycles:

  • Iterate over indices and get value from a list by index when needed
  • Initialize a variable to keep the index number in and iterate over values, incrementing the variable at the end of each for cycle.
for i in range(len(my_list)):
x = my_list[i]
print(f"Index {i} - Value {x}")
i = 0
for x in my_list:
print(f"Index {i} - Value {x}")
i += 1

However, to ease our work, there is already a function called enumerate which is wrapped around the iterable (our list) and does exactly what we wrote above. It creates a variable and keeps an index number in it, automatically incrementing it with each cycle.

for i, x in enumerate(my_list):
print(f"Index {i} - Value {x}")

Sometimes, it happens that you need to start indexing at a different number, in such a case, enumerate have an argument start, so you can start the index on a number you choose:

for i, x in enumerate(my_list, start=1):
print(f"Index {i} - Value {x}")

Traversing a list by two successive numbers

Sometimes, it is needed to iterate through a list by two successive numbers. Maybe for a computation of another variable you need two successive numbers or a list contains boundaries of bins for a histogram and you need both bounds so you can correctly count number of elements in a bin.

The basic way how to achieve this is to iterate over indices of the list and in the loop get a value with a given index and the second value on the position index+1. This, however, won't be effective for large list.

This ineffective version may look like:

for i in range(len(my_list)-1):
x = my_list[i]
y = my_list[i+1]
print(f"First value = {x}, Second value = {y}")

Instead of iterating over indices, we can use zip to create a list of tuples out of two lists. An example how zip works:

first_list = [1, 2, 3]
second_list = ["A", "B", "C"]
list(zip(first_list, second_list))
>>> [(1, "A"), (2, "B"), (3, "C")]

Similarly, instead of zipping two distinct lists, we can zip the same list with itself and just remove the last item from the first list and the first item from the second list. They will have the same length and they will be shifted by one element.

first_list = [1, 2, 3]
second_list = ["A", "B", "C"]
list(zip(first_list[:-1], second_list[1:))
>>> [(1, "B"), (2, "C")]

Effectively, when zipped, we will traverse the original list by two successive numbers.

for x, y in zip(my_list[:-1], my_list[1:]):
print(f"First value = {x}, Second value = {y}")

Measuring time per iteration

Sometimes, when a for cycle has many iterations and you do complex stuff inside that takes some time, you may want to measure time per iteration in order to have an overview of how much time will it take to finish.

In Python there is a library called tqdm just for this. It is not a part of the standard library, so you need to install it. I recommend to use the following import then:

from tqdm.auto import tqdm, trange

In case you are using Jupyter, this import will find out and make the progressbar look pretty.

tqdm is then just another wrapper around iterable and you use it like:

for x in tqdm(my_list):
print(f"Value {x}")

Instead of wrapping tqdm around range you can simply use trange:

for i in trange(len(my_list)):
print(f"Index {i}")

Combinations

enumerate + zip

You may have noticed, that when we used zip, we actually got rid of the variable that held the index number.

The easiest way, how to keep the information about index is to use enumerate, again.

zip returns a tuple, which is automatically unpacked if you just use it, but if you add enumerate which returns an integer, you need to enclose variables needed for tuple unpacking into brackets:

for i, (x, y) in enumerate(zip(my_list[:-1], my_list[1:])):
print(f"Index {i}: First value = {x}, Second value = {y}")

tqdm + enumerate

I usually prefer to write tqdm as the outermost wrapper as progressbar is not really as essential as an index and I usually add it later just to have an overview of the progress.

tqdm, however, does not need to be the outermost wrapper. If combined with enumerate you can choose the order arbitrarily. There is a catch, though. tqdm is not able (yet?) to find out the maximal number of iterations if it is wrapped around enumerate or zip. It is able to find out the number of iterations just from a list, though, which means that

for i, x in enumerate(tqdm(my_list)):
print(f"Index {i} - Value {x}")

returns a progressbar that gets filled with each iteration, while

for i, x in tqdm(enumerate(my_list)):
print(f"Index {i} - Value {x}")

just counts iterations.

If you would rather use tqdm as the outermost wrapper as me, you can easily fix this by passing then length of the list to tqdm using the total parameter.

for i, a in tqdm(enumerate(my_list), total=len(my_list)):
print(f"Index {i}: First value = {x}, Second value = {y}")

enumerate + zip + tqdm

Adding tqdm to the enumerate + zip combo does not really change much. You have still just two options:

  • Wrap enumerate around zip and after that wrap tqdm around enumerate - in such a case you need to pass the list length as total argument for tqdm
  • Swap enumerate with tqdm so that tqdm is wrapped around zip - in this case you can:
    - Pass the list length as in the previous case
    - Use `list` to transform the `zip` into a list of tuples
    because this way, `tqdm` will be able to find the length of the list.
for i, (x, y) in tqdm(enumerate(zip(my_list[:-1], my_list[1:])), total=len(my_list)):
print(f"{i}: {a} - {b}")

P. S. Just to have the list of basic FOR cycles "complete", in order to iterate through a list from the end, you can reverse the list by writing my_list[::-1].

To slice the list and go only through just a part of it you probably use my_list[start:end]. There is a third parameter though, the step, i.e. my_list[start:end:step]. If you omit the first two parameters and the step will be negative number, you will iterate through the list in reverse.