Bottom Page

Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
 for loops
#1
Basics

The for loop is a generic sequence iterator in Python: it can step through the items in any ordered sequence object. The for statement works on strings, lists, tuples, other built-in iterables, and new objects created by classes. If you are familiar with another language, the Python for loop is more like another language for-each loop.

For loops subsumes most counter-style loops. Its generally simpler to code and quicker to run than a while loop. So its the first tool you should reach for whenever you need to step through a sequence. You use a for loop when there is a predefined length to an object. You use a while loop when there is not. For example: You use a for loop to loop files in a directory, characters in a file, elements in a list, etc. All of these are predefined lengths whether you know the length or not. However in game you use a while loop because you have no idea when the user is going to close the program.

The Python for loop begins with a header line that specifies an assignment target (or targets), along with the sequence you want to step through. The header is followed by a block of (normally indented) statements that you want to repeat:

for target in sequence:
    print(target)
When Python runs the for loop, it assigns the items in the sequence object to the target one by one and executes the loop body for each. The loop body typically uses the assignment target to refer to the current item in the sequence as though it were a cursor stepping through the sequence.

The name used as an assignment target in a for header line is usually a (possibly new) variable in the scope where the for statement is coded. It can be changed inside the loop's body, but will automatically be set to the next item in the sequence when control returns to the top of the loop again. After the loop this variable normally still refers to the last item visited, which is the last item in the sequence, unless the loop exits with a break statement.

Here we show an example. We assign the name x to each of the three items in a list in turn, from left to right, and the print statement will be executed for each. Because the print function defaults to a newline, each element is a new line as it is printed. In the first iteration x='spam', the second iteration x='eggs', and the third iteration x='ham'.

>>> for x in ['spam', 'eggs', 'ham']:
...     print(x)
... 
spam
eggs
ham
In this example we compute the sum of all the items in a list.

>>> total = 0
>>> for i in [1,2,3,4]:
...     total = total + i
... 
>>> total
10
You can loop through any data type. Any sequence works in a for loop. Here we loop through a string letter by letter and print it out with a space between each letter.
>>> word = 'lumberjack'
>>> for letter in word:
...     print(letter, end=' ')
... 
l u m b e r j a c k 
Tuple assignment

You can loop through tuples. If you're iterating through a sequence of tuples, the loop target itself can actually be a tuple of targets. This is another case of tuple unpacking assignment. The for loop assign items in the sequence object to the target, and assignment works the same everywhere.
>>> t = [(1,2), (3,4), (5,6)]
>>> for (a,b) in t:
...     print(a,b)
... 
1 2
3 4
5 6
The first iteration (a,b) = (1,2), the second iteration (a,b) = (3,4), etc. This is commonly used in conjunction with python's built-in zip function discussed later on.

Tuples come in handy to iterate through both keys and values in dictionaries using the items method.
>>> d = {'a':1, 'b':2, 'c':3}
>>> for key, value in d.items():
...    print(key, value)
... 
a 1
b 2
c 3
Its important to note that tuple assignment in for loops is not a special case. Any assignment target works syntactically after the word for. Although we can always assign manually within the loop to unpack.
>>> t = [(1,2), (3,4), (5,6)]
>>> for both in t:
...     a,b = both
...     print(a,b)
... 
1 2
3 4
5 6
Built-in functions

To understand how to use python for loops to the full effectiveness means to use python built-in functions to do the work for you. A lot of times in Python a for loop is looping through a sequence returned by a built-in function that did the work for it. To get more info regarding built-in functions or any module even, you can use the built-in help function to return all docstrings about any built-in. This will give you detailed information about what arguments can be given in the python interpreter quickly.
>>> help(range)
We are only going to use a handful of built-in functions for examples in for loops here. range(), enumerate(), zip(), sorted(), and map(). This is because these are the most often used with for loops as opposed to other built-ins. The following examples use such built-ins.

Nested loops

For loops can be nested too. Here we use the built-in range() function to return a list generating the numbers 0-2 for each loop. The inner j loop loops for each i loop. In this example the inner i loop runs 3 separate times (one for each time the outer loop runs). This is shown in the output of the loops.
>>> for i in range(3):
...     for j in range(3):
...         print(i,j)
... 
0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2
It should be noted that python has ways to simplify this even more. Instead of having two nested for loops, we can obtain the same results by importing the itertools module
from itertools import product
>>> for pairs in product(range(3), range(3)):
...     print(pairs)
... 
(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2)
(2, 0)
(2, 1)
(2, 2)

range

Pythons built in range function is an iterator that generates a list of integers. It is often used in conjunction with a for loop as it is a simple way to generate a list of required numbers. As shown in the above example. It generated a list [0, 1, 2] by giving it the argument 3. This can be very useful for various situations. For three numbers it was not impressive. I really used that number to keep the output low. But imagine needing a list of numbers up to 10,000 or 100,000.

The arguments for range are as following:
range(stop) -> range object
range(start, stop[, step]) -> range object
The range function when given a single argument, returns a list from 0 to that stopping number. Here it generates a list from 0-9 by giving it the stopping number of 10.
>>> for i in range(10):
...     print(i)
... 
0
1
2
3
4
5
6
7
8
9
range can also be given a start, stop, and step arguments. If i only wanted to get the second half of the same numbers i could give the argument 5 to start at and 10 to stop.
>>> for i in range(5,10):
...     print(i)
... 
5
6
7
8
9
Now we are going to add in the step argument. Here we want to increment only by 2's. It is starting at 5, stopping at 10 (not including 10), and stepping through this sequence by every 2.
>>> for i in range(5,10,2):
...     print(i)
... 
5
7
9
We can also use step to go backwards through a sequence by giving a negative number. Here we start at 10, stop at 5, and step through this sequence by negative 2.
>>> for i in range(10,5,-2):
...     print(i)
... 
10
8
6
Here we reverse a sequence from 10 up to 0
>>> for i in range(10,0,-1):
...     print(i)
... 
10
9
8
7
6
5
4
3
2
1
As a side note: You dont even need a for loop to reverse a sequence in Python. You can splice the list using the step similar to range step
>>> l = list(range(3))
>>> l
[0, 1, 2]
>>> l[::-1]
[2, 1, 0]
or you can use the built-in reverse function as well.
>>> list(reversed(l))
[2, 1, 0]
This is ways to reduce the amount of code in Python. The less code the easier it is to maintain. Why create a for loop when you do not even need to, right?


A note on range(len(sequence))

It is important to note. In one situation you should not use this in is using range(len(sequence)) in a for loop to get the index of each element in the sequence. This is not pythonic because python allows you to loop the sequence directly. In Python, when you loop a sequence with a for loop you have no need to know the indexes most of the time. Remember this is an example of what NOT to do.
>>> seq = ['spam', 'eggs', 'ham']
>>> for i in range(len(seq)):
...     print(f'{seq[i]} is at index {i}')
... 
spam is at index 0
eggs is at index 1
ham is at index 2
There is a pythonic way to do this if you need the index for each sequence. And this is a rare case in python. This is to use the builtin enumerate function in conjunction with tuple target shown earlier which is explained in the next section.

Enumerate

Enumerate is a built-in generator object. It has a __next__ method called by the next buildin function which returns an (index, value) tuple each time through the loop. We can unpack these tuples with tuple assignment in the for loop.

>>> for index, target in enumerate(['spam', 'eggs', 'ham']):
...     print(f'{target} is at index {index}')
... 
spam is at index 0
eggs is at index 1
ham is at index 2
With enumerate function, index variable is incremented as target steps through the sequence (default starting at 0). You can give a starting number as a second argument to change the default start counter. In this case we start at 10 by giving a second argument of 10
>>> for index, target in enumerate(['spam', 'eggs', 'ham'], 10):
...     print(f'{target} is at index {index}')
... 
spam is at index 10
eggs is at index 11
ham is at index 12
Changing sequence mid-loop

Lets say we are wanting to modify the sequence that we are looping over. In this case we try this:
>>> l = ['spam', 'eggs', 'ham']
>>> for word in l:
...    if 's' in word:
...       l.remove(word)
... 
>>> l
['eggs', 'ham']
This is not what we want. What happened? It removed the first word with an s in it, but not eggs...which also has an s in it.

We need to make a copy to the reference of the list and loop over the copy and modify the original. In this way we are looping over every original item in the sequence and applying changes to the original list.
>>> l = ['spam', 'eggs', 'ham']
>>> for word in l[:]:
...    if 's' in word:
...       l.remove(word)
... 
>>> l
['ham']
It is important to note that [:] creates a shallow copy to the reference of the list. You could alternatively import the copy module and use copy.copy(sequence) though. Nested lists are an example that would require a deep copy (depending on what you are removing). This would require you to import the copy module to do copy.deepcopy(sequence). But that is a topic for another discussion. It is important to know its relation to for loops when removing objects while iterating over them as it can cause unexpected output (if you remove an element from a sequence in which you are looping over that very same sequence).

Zip

Zip allows us to use for loops to visit multiple sequences in parallel. In basic operation, zip takes one or more sequences as arguments and returns a series of tuples that pair up parallel items taken from those sequences. For example suppose we are working with two lists.

>>> l1 = [1,2,3,4]
>>> l2 = [5,6,7,8]
To combine these lists we can use zip to create a list of tuple pairs.
>>> for x,y in zip(l1, l2):
...     print(x,y, x+y)
... 
1 5 6
2 6 8
3 7 10
4 8 12
Here we are looping over two separate lists in one for loop. It is not restricted to two arguments either as shown here. You just have to make sure there is the equivalent number of targets to unpack for as many lists are given. Here we have two lists, so we only need x and y to unpack them to.

zip truncates result tuples at the length of the shortest sequence when an argument lengths differ.
>>> s1 = 'abc'
>>> s2 = 'xyz123'
>>> for x,y in zip(s1, s2):
...     print(x,y)
... 
a x
b y
c z
You can construct dictionaries at runtime using zip. The for loop would look like this
>>> keys = ['spam', 'eggs', 'ham']
>>> values = [1,3,5]
>>> d = {}
>>> for k,v in zip(keys, values):
...     d[k] = v
... 
>>> d
{'spam': 1, 'eggs': 3, 'ham': 5}
However you can skip the for loop completely and simply do this
>>> keys = ['spam', 'eggs', 'ham']
>>> values = [1,3,5]
>>> d2 = dict(zip(keys, values))
>>> d2
{'spam': 1, 'eggs': 3, 'ham': 5}
sorted
Often you want to sort a list as you loop through them. In this case we use the sorted built in function in combination of the for loop.
>>> abc = ['b','d','a','c','f','e']
>>> abc
['b', 'd', 'a', 'c', 'f', 'e']
>>> for letter in sorted(abc):
...     print(letter)
... 
a
b
c
d
e
f
sorted returns a sorted list in ascending order.
>>> list(sorted(abc))
['a', 'b', 'c', 'd', 'e', 'f']
If you want to reverse it (descending order), you can give a boolean flag to reverse
>>> for letter in sorted(abc, reverse=True):
...     print(letter)
... 
f
e
d
c
b
a
List Comprehensions

List comprehension is the cousin of the for loop. Any list comp can be converted to a for loop, but not vice-versa. List comp are written in square brackets because they are a way to construct a new list. List comps are roughly twice as fast as manual for loops because their iterations are performed at C language speed inside the interpreter, rather than with manual Python code. However making them too complicated reduces readability.

The syntax follows suit with line after line in an expanded for loop except for what is controlled.
This is a normal for loop doing nothing with x. This would be considered an expanded for loop.
for y in ['foo','bar']:
    for x in [y.lower(),y.upper()]:
        x
List comps condense this for loop in to this:
[x for y in ['foo','bar'] for x in [y.lower(),y.upper()]]
This is process of converting that for loop into a list comp:

move second line to first line "after"
for y in ['foo','bar']:for x in [y.lower(),y.upper()]:
        x
remove colons
for y in ['foo','bar'] for x in [y.lower(),y.upper()]
    x
move x first
x for y in ['foo','bar'] for x in [y.lower(),y.upper()]
enclose the entire thing in brackets
[x for y in ['foo','bar'] for x in [y.lower(),y.upper()]]
The following is an ordinary for loop. It creates a new list and inputs th string "ham" into as many times as it is in the original list.
>>> lst = ['spam', 'ham', 'eggs', 'ham']
>>> ham_lst = []
>>> for item in lst:
...     if item == 'ham':
...             ham_lst.append(item)
... 
>>> print(ham_lst)
['ham', 'ham']
The following is the same , but doing it with list comps.
lst = ['spam', 'ham', 'eggs', 'ham']
>>> [item for item in lst if item == 'ham']
['ham', 'ham']
Both create a new list "ham_lst". So problems that can occur if modify the original list in the loop sequence,will not affect when create a new list.

more info including if conditions mixed in with it here
more info on our list comp tutorial here

map
The built-in map function returns an iterator that calls a function on the values in the input iterators, and returns the results. It is important to note that you can do the same thing with a list comprehension.

Here we create a function to call. All it does is return 2 times the number its given.
>>> def times_two(x):
...     return 2 * x
... 
Now we are going to loop this using map to call this function on every number given by the range function.
>>> for i in map(times_two, range(5)):
...     print(i)
... 
0
2
4
6
8
The for loop is only looping the results. As map is doing all the work
>>> list(map(times_two, range(5)))
[0, 2, 4, 6, 8]
Now we do the exact same thing that map did, but with a list comprehension.
>>> [times_two(num) for num in range(5)]
[0, 2, 4, 6, 8]
or just a simple for loop with range
>>> for i in range(5):
...     times_two(i)
... 
0
2
4
6
8
As you can understand simple for loops. All map is doing is calling times_two function on every result from the sequence. Map exists so you have the option to not use a for loop at all.

break, continue, pass, else
  • break Jumps out of the closest enclosing loop (past then entire loop statement)
  • continue Jumps to the top of the closest enclosing loop (to the loops header line)
  • pass Does nothing at all. Its and empty statement placeholder
  • else Runs if and only if the loop is exited normally (i.e, without running into a break)

In this example the for loop stops midway through the iteration. Once the break statement is executed this for loop stops as shown by the output.
>>> for letter in 'Python':
...     if letter == 'o':
...         break
...     print(letter)
... 
P
y
t
h
In this example we swap out break with continue for illustration. The main difference here is the n iteration is executed. This is because the continue statement halts the for loop in that single iteration, and continues with the next iteration (being the letter n).
>>> for letter in 'Python':
...     if letter == 'o':
...         continue
...     print(letter)
... 
P
y
t
h
n
The following is an example of pass. In this example, nothing happens when letter == 'o'. It is as if the if condition was never there. It did execute, but nothing happened as a result of it. This is more of a placeholder. If i know there is an if condition going to be there, but i am going to write it later. I just haven't put in the meat of the if condition yet.
>>> for letter in 'Python':
...     if letter == 'o':
...             pass
...     print(letter)
... 
P
y
t
h
o
n
In these examples the else clause only executes when the for loop ended normally.
>>> for letter in 'Python':
...     if letter == 'o':
...         break
...     print(letter)
... else:
...     print('loop finished')
... 
P
y
t
h
>>> for letter in 'Python':
...     if letter == 'x':
...         break
...     print(letter)
... else:
...     print('loop finished')
... 
P
y
t
h
o
n
loop finished
Tutorials expanded on for loops here at the forum:
Comprehension Expressions
Generators/Iterators

Here are a series of loops and their more pythonic counterparts
Transforming Code into Beautiful, Idiomatic Python

More information regarding the itertools modules used in this tutorial to bypass nested for loops
Python module of the week: itertools

More reading material about loops that go more in depth.
Loop better: A deeper look at iteration in Python
Loop Like A Native
Larz60+, snippsat, wavic And 2 others like this post
Quote
#2
left intentionally blank so people dont get notices every time i edit the tutorial
Quote

Top Page

Forum Jump:


Users browsing this thread: 1 Guest(s)