def average(nums: list[int]) -> float:
total = 0
count = 0
for num in nums:
total = total + num
count = count + 1
return total / count
print(average([1, 4, 5]))
Sometimes, we might need to find the mean (or average) of something/a value. For example, finding the average temperatures for a particular month or the number of cupcakes sold each year at a bake sale. We calculate the mean by adding together the elements and dividing that total by the number of elements.
You can calculate the mean, or average, of a list by adding together the elements and dividing by the number of elements. Finding the average is a common operation for a list. You could write this function by creating a loop that combines the sum and count patterns. This is a valid solution but ends up being a little complicated inside of the loop with the two statements.
def sum(nums: list[int]) -> int:
sum = 0
for num in nums:
sum = sum + num
return sum
def count(nums: list[int]) -> int:
count = 0
for num in nums:
count = count + 1
return count
def average(nums: list[int]) -> float:
return sum(nums) / count(nums)
Alternatively, you could define functions to sum and count and then use those functions in your average function. This version does not require a loop directly in the average function because the loop happens indirectly in a helper function. Although this version takes more code, each function only must do one thing, making it easier to test and debug each individual component.

Earlier, we learned to break up complex functions into multiple parts using functional decomposition. With loops, we finally see why that becomes necessary. The control flow for a function with a loop is complicated. When you start nesting loops and if statements inside of functions, mistakes are likely to occur. Functional decomposition allows us to tame that complexity by focusing on one conceptual step at a time.

Let’s say that we wanted to add up all the negative numbers in a list. You could write a for loop that combines the filter pattern and the sum pattern. You might even succeed. But what if you also needed to convert all the elements from strings to integers first? What if you needed to also find the average? Suddenly, combining all these patterns becomes difficult.
def map_strs_to_ints(numbers: list[str]) -> list[int]:
integers = []
for number in numbers:
integers.append(int(number))
return integers
def remove_negatives(numbers: list[int]) -> list[int]:
positives = []
for number in numbers:
if number >= 0:
positives.append(number)
return positives
def average(nums: list[int]) -> int:
total = 0
count = 0
for num in nums:
total = total + num
count = count + 1
return total / count
def average_positives(numbers: list[str]) -> float:
return average(remove_negatives(map_strs_to_ints(numbers)))
Instead, let’s focus on defining functions to solve each part of the puzzle in turn.
We define a map_strs_to_ints function that maps string conversion over a list of strings.
We define a remove_negatives function that filters out negative integers.
We use the count and sum patterns to define the average function that consume lists of integers to accumulate.
Then, we can compose those functions as needed into an average_positives function.
This may seem like a lot more work — suddenly we must define and test five functions instead of one.
But the advantage is that each function is relatively easy to define and test.
If you make a mistake, you will be able to track it down very quickly.
def only_questions(words: list[str]) -> list[str]:
questions = []
for word in words:
if "?" in word:
questions.append(word)
return questions
def last_word(words: list[str]) -> str:
last = words[0]
for word in words:
if word > last:
last = word
return last
# Main function
def last_question_word(words: list[str]) -> str:
questions = only_questions(words)
# DEFENSIVE GUARD IS CRITICAL
if not questions:
return "no question words!"
return last_word(questions)
Both the filter and take patterns consume a list and return a new list with potentially fewer elements. This can cause trouble when composing these patterns with the min/max patterns. Recall that the min and max patterns expect a non-empty list. Usually, we must guard against empty lists before using these patterns. But, if you guard too early, then you run the risk of still potentially having an empty list. It is critical to delay the guard until just before you are going to index the list.