5. Loops and Comprehensions
5.1. Loops
This chapter provides an overview of loops and iterations in Python, specifically focusing on the for, while, and else statements.
5.1.1. For Loops
In this section, we will cover the following statements and functions:
- Basic Usage: The for loop iterates over a list of pets and prints each pet's name. This is the simplest usage of a for loop.
breakStatement: The for loop is used in combination with a conditional statement and break, which terminates the loop once a specific condition is met.continueStatement: The continue statement is used to skip the rest of the current loop iteration and immediately start the next one.- Nested for Loops: This demonstrates the concept of nested loops, where a for loop is contained within another for loop.
range()Function: Therange()function generates a sequence of numbers over which the for loop iterates. The function can be called with different numbers of arguments to change the start, end, and step size of the sequence.
This chapter provides an excellent foundation for understanding loops in Python.
Basic Usage
This code creates a list of pet names, then uses afor loop to iterate over each item in the list. Each time through the loop, it prints the current pet name.
break Statement
pets = ['Tub', 'Barkalot', 'Furrytail']
for pet in pets:
if pet == 'Barkalot':
print('We got Barkalot!')
break
print(pet)
if statement that checks whether the current pet name is Barkalot. If it is, the code prints a special message and then uses the break statement to immediately exit the loop.
continue Statement
# `continue` statement skip the current iteration and continue with the next.
# Here we skip the `Barkalot` pet and print `We got Barkalot!` instead.
pets = ['Tub', 'Barkalot', 'Furrytail']
for pet in pets:
if pet == 'Barkalot':
print('We got Barkalot!')
continue
print(pet)
Again, this code is similar to the previous examples. The difference is that when the current pet name is Barkalot, it uses the continue statement to immediately start the next iteration of the loop, skipping the print(pet) statement for Barkalot.
Nested for Loops
Here, the outerfor loop iterates over the list of pet names, and the inner for loop iterates over the string 'ab'. For each combination of pet name and letter, it prints the letter and the pet name.
range() Function
This code is the same as the previous example, but the inner loop iterates over the numbers produced by range(2), which are 0 and 1.
range() Function with Start and End
This code uses the range() function to generate a sequence of numbers from 0 to 4. The for loop iterates over these numbers, printing each one.
range() Function with Start, End, and Step
This code is similar to the previous example, but it adds a step size of 2 to the range() function. This means it only generates every second number in the range from 0 to 4, so the for loop prints the numbers 0, 2, and 4.
5.1.2. While Loops
In Python, a while loop repeatedly executes a target statement as long as a given condition is true.
For example:
The while loop above continues executing until the condition num < 5 is no longer true, which occurs after the fourth iteration.
Using break with While Loop
The break statement is used to exit a while loop prematurely. When a break statement is encountered inside the loop, the loop is immediately terminated, and program control resumes at the next statement following the loop.
In the following code, break is triggered when num equals 3, causing an early exit from the loop.
break in an Infinite Loop
An infinite while loop can be created using while True:. This loop will run indefinitely unless it encounters a break statement.
break is used to stop an otherwise infinite loop when num equals 3:
If Statement versus While Loop
An if statement checks a condition once, whereas a while loop continues to execute the block of code as long as the condition is true.
In this code, the if statement checks the condition num < 5 once and then executes the block of code if the condition is true. After that, it doesn't check the condition again or repeat the block of code. This is the main difference between a while loop and an if statement.
5.1.3. Else Clause with Loops
The else clause in Python can also be used with loops. Unlike in an if statement, where else executes when the if condition is false, with loops, the else clause executes after the loop completes normally, i.e., when no break statement has been encountered.
If-Else
The else clause executes after the loop completes normally.
Here's a simple example of an if-else statement:
The else clause is executed because the if condition is false.
Else with For Loop
In the context of a loop, an else statement can be thought of as a "no break" statement. It will execute once the loop has finished iterating over the items.
Another example may not be so obvious. And always makes people confused:
pets = ['Tub', 'Barkalot', 'Furrytail']
for pet in pets:
print(pet)
else: # More like a `no break` statement
print('No more pets.')
The else statement associated with a loop (either a for or while loop) in Python might initially seem confusing since in many other programming languages else is associated only with if statements.
You may think why the else statement is executed?
In Python, the else clause in a loop executes when the loop has finished iterating over all items (in a for loop) or when the condition becomes false (in a while loop), but not when the loop is prematurely ended by a break statement.
Let's break down the above example:
-
The
forloop begins iterating over thepetslist. For each pet inpets, it prints the pet's name. -
When there are no more items left in the
petslist, the loop's condition becomes false (it has run out of items to process). At this point, instead of just ending, it checks if there is anelseclause associated with it. -
Since there is an
elseclause, it runs the code inside theelseblock. The print statement inside theelseblock runs, outputtingNo more pets..
So, the phrase no break used in comments beside the else statement means that if the loop finishes its iterations normally without encountering a break statement (i.e., it was not forced to stop prematurely), the code inside the else block will be executed.
This behavior is particularly useful when you use a loop to search for an item in a list or another data structure. If the item is found, you can use the break statement to stop the loop, and the else block will be ignored. If the item isn't found and the loop ends normally after checking all items, the else block will run, allowing you to handle the case where the item isn't found.
To understand this, let's have a more concrete example:
In for loops
else statement is not executed because the loop is terminated by the break statement.
This is also the same as for while loop.
In this exercise, you'll create a function that searches for a specific number in a list using a for loop and an else clause. The function should print a message indicating whether or not the number was found in the list.
Exercise 1: Search Number in List
Tasks:
-
Write a function
search_number_in_listthat takes two parameters: a list of numbers (numbers_list) and a number to search (search_number). -
Inside the function, start a
forloop that iterates over each number innumbers_list. -
Inside the loop, use an
ifstatement to check if the current number equalssearch_number. If it does, print a message likeNumber {search_number} found in the list!, and then use thebreakstatement to immediately exit the loop. -
After the
forloop, write anelseclause that prints a message likeNumber {search_number} not found in the list.. Thiselseclause should be executed if theforloop completes all its iterations without hitting thebreakstatement. -
Test your function with a list of numbers and a search number of your choice.
Here's a skeleton of the function to get you started:
In this exercise, you'll modify the function find_first_even to return the index of the first even number found in the list. If no even number is found, the function should return None.
Exercise 2: Find First Even Number Function
Tasks:
-
Modify the function
find_first_eventhat takes a list of numbers (nums) as parameter. -
Inside the function, start a
forloop that iterates over number/s innums. -
Inside the loop, use an
ifstatement to check if the current number is even. If it is, return the currentnumand then use thebreakstatement to immediately exit the loop. -
After the
forloop, write anelseclause that returnsNone. Thiselseclause should be executed if theforloop iterates over all the numbers from the list without hitting thebreakstatement. In other words, we didn't find any even number in the list. -
Test your function with a list of numbers of your choice.
Here's a skeleton of the function to get you started:
def find_first_even(nums)-->str:
# Your code here
pass
# Test the function
nums = [1, 3, 5, 7, 9, 11]
# It should print `First even number is: 8`
print('First even number is: {}'.format(first_even))
nums = [1, 3, 5, 2, 9, 11]
# It should print `First even number is: None`
print('First even number is: {}'.format(first_even))
5.2. enumerate() function
The enumerate function is a built-in function in Python that allows you to loop over something and have an automatic counter, or index tracker. It adds a counter as the key of the enumerate object, alongside the items of the iterable, returning an enumerate object which you can convert to a list, tuple or other data structures. The function signature is as follows:
enumerate(iterable, start=0)
iterable: any object that supports iterationstart: the index value from which the counter should start, default is 0
5.2.1. Basic Usage of enumerate()
Here,enumerate(pets) returns a sequence of tuples, and each tuple consists of two items: the index and the value of the corresponding item in the iterable. i and pet are tuple unpacking the result returned by enumerate.
5.2.2. Using enumerate with a Different Start Index
In this example, the index starts at 1 (instead of the default 0) because we've set start=1.
5.2.3. Practical Usage of enumerate
enumerate is particularly useful when you need to track the index of items within a loop. For example, if you want to replace an item in a list:
pets = ['Tub', 'Barkalot', 'Furrytail']
for i, pet in enumerate(pets):
if pet == 'Tub':
pets[i] = 'Cat'
print(pets)
enumerate to get the index of each pet. When we find the pet Tub, we use the index to replace Tub with Cat in the original list.
Now it's time to try using enumerate in your own code! Try to think of situations where you need both the item and its index within a loop.
Exercise: Find First Even Number and Its Index
Tasks:
-
Modify the function
find_first_eventhat takes a list of numbers (nums) as parameter. -
Inside the function, start a
forloop that iterates over each number innums. You'll need to use theenumeratefunction so that you have access to the index of each number. -
Inside the loop, use an
ifstatement to check if the current number is even. If it is, return the current index and number as atuple(index, number), and then use thebreakstatement to immediately exit the loop. -
After the
forloop, write anelseclause that returns(None, None). Thiselseclause should be executed if theforloop completes all its iterations without hitting thebreakstatement. -
Test your function with a list of numbers of your choice.
Here's a skeleton of the function to get you started:
def find_first_even(nums)->tuple(int, int):
# Your code here
return (i, num)
nums = [1, 3, 8, 7, 3, 2, 3]
first_even = find_first_even(nums)
print('The index and value of the first even number are: {}'.format(first_even))
# Output: The index and value of the first even number are: (2, 8)
nums = [1, 3, 1, 7, 3, 9, 3]
first_even = find_first_even(nums)
print('The index and value of the first even number are: {}'.format(first_even))
# Output: The index and value of the first even number are: ('None', 'None')
5.3. List Comprehensions
List comprehensions are a powerful feature in Python, allowing you to create lists from existing lists or other iterable objects. They provide a concise way to apply operations to the values in a sequence.
5.3.1. Basic List Comprehensions
Consider the following example where we have a list of ages, and we want to create a new list with the same ages:
Using For loop
ages = [5, 12, 3, 56, 24, 78, 1, 15, 44]
age_list = []
for age in ages:
age_list.append(age)
print(age_list)
Now, we make the above for loop into a list comprehension
Another example, we also apply operations to the values in the sequence. For example, we can add 1 to each age:
Using For loop
ages = [5, 12, 3, 56, 24, 78, 1, 15, 44]
age_list = []
for age in ages:
age_list.append(age + 1)
print(age_list)
List comprehension
5.3.2. Comparing List Comprehensions and map
map() is a built-in function that applies a function to each item in an iterable object. It returns a map object, which can be converted into a list or tuple.
Let's see how we can use map to add 1 to each age as the previous example:
map is faster than list comprehension, but list comprehension is more readable.
5.3.3. List Comprehensions with Conditionals
You can also include conditions in your list comprehension. For example, we can create a list that only contains even ages:
Using For loop
ages = [5, 12, 3, 56, 24, 78, 1, 15, 44]
age_list = []
for age in ages:
if age % 2 == 0:
age_list.append(age)
print(age_list)
List comprehension
# [expression for item in iterable if condition]
age_list = [age for age in ages if age % 2 == 0]
print(age_list)
Using lambda with filter
5.3.4. Real World Example
List comprehensions can be used to solve real-world problems more concisely. For example, let's say you want to create an unordered HTML list from a list of pet names:
Using For loop
pets = ['Tub', 'Furrytail', 'Cat', 'Barkalot', 'Bumblefluff ', 'Whiskerfloof']
output = '<ul>\n'
for pet in pets:
output += '\t<li>{}</li>\n'.format(pet)
# print('Address of output is {}'.format(id(output)))
output += '</ul>'
print(output)
<ul>
<li>Tub</li>
<li>Furrytail</li>
<li>Cat</li>
<li>Barkalot</li>
<li>Bumblefluff </li>
<li>Whiskerfloof</li>
</ul>
- Tub
- Furrytail
- Cat
- Barkalot
- Bumblefluff
- Whiskerfloof
List comprehension
pets = ['Tub', 'Furrytail', 'Cat', 'Barkalot', 'Bumblefluff ', 'Whiskerfloof']
output = '<ul>\n'
# List comprehension to create a list of formatted list items
formatted_pet_names = ['\t<li>{}</li>'.format(pet) for pet in pets]
# Join the list items with newline characters and add the closing </ul> tag
output += '\n'.join(formatted_pet_names) + '\n</ul>'
print(output)
<ul>
<li>Tub</li>
<li>Furrytail</li>
<li>Cat</li>
<li>Barkalot</li>
<li>Bumblefluff </li>
<li>Whiskerfloof</li>
</ul>
5.3.5. Performance Comparison: For Loop vs List Comprehension
Let's compare the performance of using a for loop vs a list comprehension to generate a list of formatted numbers:
import timeit
import random
# Create a list of 10,000 random numbers between 0 and 100
nums = [random.randint(0, 100) for i in range(10000)]
# For loop implementation
def for_loop():
output = '<ul>\n'
for num in nums:
output += '\t<li>{}</li>\n'.format(num)
output += '</ul>'
return output
# List comprehension implementation
def list_comprehension():
output = '<ul>\n' + ''.join(['\t<li>{}</li>\n'.format(num) for num in nums]) + '</ul>'
return output
# Measure the execution time of both implementations
for_loop_time = timeit.timeit(for_loop, number=1000)
list_comprehension_time = timeit.timeit(list_comprehension, number=1000)
print("For loop execution time: ", for_loop_time)
print("List comprehension execution time: ", list_comprehension_time)
For loop execution time: 0.01699999999999999
List comprehension execution time: 0.012999999999999956
When running this code, you'll find that the list comprehension implementation is typically faster than the for loop implementation. This is because list comprehensions are optimized for performance in Python, and they can often perform the same task more quickly than the equivalent for loop. However, the difference in speed may not be significant unless you're dealing with very large data sets.
It's also worth noting that while list comprehensions can be faster and more concise, they can also be harder to read if they become too complex. Therefore, it's important to strike a balance between performance and readability when writing your code.
-
Succinctness: List comprehensions provide a concise way to create lists. They can often achieve the same result as a for-loop in a single, short line of code.
-
Speed: List comprehensions are generally faster than for-loops because they are specifically optimized for creating new lists.
-
Functionality: List comprehensions can incorporate conditionals and multiple for-loops, enabling quite complex list creation in a single line.
-
Readability: List comprehensions can be harder to read than for-loops, especially if they are complex.
-
MemoryUsage: List comprehensions create new lists in memory, which can cause problems if you're working with very large data sets.
-
Debugging: List comprehensions can be harder to debug than for-loops, especially if they are complex.
5.4. zip() Function
In Python, the zip() function is used to combine corresponding elements from multiple iterables (like lists or tuples) into tuples. Let's first understand it with an example:
ages = [5, 12, 3, 56, 24, 78, 1, 15, 44]
names = ['Tub', 'Barkalot', 'Furrytail']
print(zip(ages, names))
print(list(zip(ages, names)))
list() function.
5.4.1. zip() with for Loop
In the following example, the zip() function pairs up the elements from ages and names lists by their indices.
ages = [5, 12, 3, 56, 24, 78, 1, 15, 44]
names = ['Tub', 'Barkalot', 'Furrytail']
my_list = []
for age in range(3):
for name in names:
my_list.append((age, name))
print(my_list)
[(0, 'Tub'), (0, 'Barkalot'), (0, 'Furrytail'), (1, 'Tub'), (1, 'Barkalot'), (1, 'Furrytail'), (2, 'Tub'), (2, 'Barkalot'), (2, 'Furrytail')]
5.4.2. zip() with List Comprehension
Similarly, we can usezip() with list comprehensions. Let's create a list of tuples where each tuple consists of a number and a pet name:
[(0, 'Tub'), (0, 'Barkalot'), (0, 'Furrytail'), (1, 'Tub'),
(1, 'Barkalot'), (1, 'Furrytail'), (2, 'Tub'), (2, 'Barkalot'), (2, 'Furrytail')]
This code creates a tuple for each combination of age and name, and adds it to the list. Here, age ranges from 0 to 2, and name is taken from the names list.
5.5. Dictionary Comprehensions
A dictionary comprehension is similar to a list comprehension, but it constructs a dictionary instead of a list.
If we want to create a dictionary that maps each pet's name to its age, we can use a for-loop like this:
Regular For Loop with zip()
Dictionary comprehension with zip()
# {key: value for item in iterable}
my_dict = {name: age for name, age in zip(names, ages)}
print(my_dict)
name: age is the key-value pair for each item in the dictionary.
We can also add a condition in the dictionary comprehension to filter the items:
# {key: value for item in iterable if condition}
my_dict = {name: age for name, age in zip(names, ages) if age > 10}
print(my_dict)
This will include only the pets that are older than 10 in the dictionary.
5.6. Set Comprehensions
Set comprehensions work just like list and dictionary comprehensions, but they produce a set, which is an unordered collection of unique elements.
Let's start with an example where we want to create a set from the ages list:
ages = [5, 12, 3, 56, 24, 78, 1, 15, 44]
names = ['Tub', 'Barkalot', 'Furrytail']
my_set = set()
for age in ages:
my_set.add(age)
print(my_set)
We're creating a set and adding each age to it. The resulting set includes each age once, even if it appeared multiple times in the list.
We can achieve the same result more succinctly with a set comprehension:
Here, age for age in ages is the expression for each item in the set.
Similar to list and dictionary comprehensions, we can also include a condition in the set comprehension:
# {expression for item in iterable if condition}
my_set = {age for age in ages if age > 10}
print(my_set)
This will include only the ages that are greater than 10 in the set.
In summary, set comprehensions provide a concise way to create sets in Python. They can be especially useful when you need to remove duplicates from a list or other iterable, because sets automatically discard duplicate values.
5.6. Generator Compreshensions
Generator comprehensions are an elegant way to create generators using a syntax that is similar to list comprehensions. In fact, you can convert a list comprehension into a generator comprehension just by replacing the square brackets [] with parentheses ().
Generators are a powerful feature in Python for creating iterable objects. They are a key component of Python's approach to handling large data streams or sequences of data because they enable you to create an iterable object without needing to store all of the values in memory at once. This can provide substantial performance benefits when dealing with large data sets.
We will cover more details in the next Chapter.
5.6.1. Traditional Generator Function
The gen_func function is a generator function that takes a list of ages as an argument. Inside this function, we iterate over the ages list using a for loop. For each age in ages, we yield the value of age incremented by 1. The yield keyword is used in Python generator functions as a sort of "return" that does not end the function, but instead provides a value and pauses the function's execution until the next value is requested.
ages = [5, 12, 3, 56, 24, 78, 1, 15, 44]
def gen_func(ages):
for age in ages:
yield age+1
my_gen = gen_func(ages)
for item in my_gen:
print(item)
The gen_func function is a generator function that takes a list of ages as an argument. Inside this function, we iterate over the ages list using a for loop. For each age in ages, we yield the value of age incremented by 1. The yield keyword is used in Python generator functions as a sort of "return" that does not end the function, but instead provides a value and pauses the function's execution until the next value is requested.
We call our generator function gen_func with the ages list as the argument, and the result (a generator object) is assigned to my_gen. Then we use a for loop to iterate over the generator object, which causes the generator function to execute and yield its values one by one. Each yielded value is printed out.
5.6.2. Generator Comprehension
The second part of the code shows a generator comprehension, which is a more compact way of creating a generator. The syntax is very similar to list comprehensions, but uses parentheses () instead of square brackets [].
# (expression for item in iterable)
my_gen = (age for age in ages)
print(my_gen)
for item in my_gen:
print(item)
This line creates a generator object that will generate the same values as the ages list, but on-demand, not storing the entire list in memory.
Generators, both through traditional functions and comprehensions, are a powerful tool in Python. They provide an efficient way to work with large data sets or streams of data that would be inefficient or impractical to store in memory all at once.