Learning Goals
3 minBy the end of this lesson you can:
- Pass a list as a single argument and use
for item in nums:inside the function body. - Build summary functions like
total(nums),count_even(nums)andbiggest(nums)that return one value. - Call your own list functions and compose them — e.g.
average(nums) = total(nums) / len(nums).
Warm-Up
5 minYou already pass lists to built-in functions. Look:
nums = [3, 1, 4, 1, 5, 9, 2, 6] print(len(nums)) # 8 print(sum(nums)) # 31 print(max(nums)) # 9 print(min(nums)) # 1 print(sorted(nums)) # [1, 1, 2, 3, 4, 5, 6, 9]
Every one of those is a function that takes a list and returns a value. Today you'll write your own — and you'll discover that sum, max and friends are nothing magical. They're short for-loops with a return at the end.
From PY-L1-22, you already wrote total / max / count by hand. Today is the upgrade: wrap each one in a def + return, and suddenly they're tools you can drop into any program.
New Concept · A List as One Argument
14 minDefine and call
The def looks identical to a one-parameter function — Python doesn't care whether the value is a number or a list:
def total(nums): answer = 0 for n in nums: answer = answer + n return answer scores = [85, 92, 71, 99, 60] print(total(scores)) # → 407
Inside the function, nums is the parameter — and it happens to hold a list. We loop through it the same way we did in PY-L1-16, accumulate into answer, and return.
Three classics, side by side
def total(nums): answer = 0 for n in nums: answer = answer + n return answer def biggest(nums): answer = nums[0] for n in nums: if n > answer: answer = n return answer def count_even(nums): answer = 0 for n in nums: if n % 2 == 0: answer = answer + 1 return answer
Same recipe each time. Initialise answer, walk the list, update answer as you go, return at the end. Memorise the pattern — you'll use it forever.
Compose them · average from total and len
def average(nums): return total(nums) / len(nums) print(average([85, 92, 71, 99, 60])) # → 81.4
One line of body, because total already does the hard work. len is built in. The whole function reads like the formula it implements.
Empty list — a real-world gotcha
print(average([])) # ZeroDivisionError: division by zero
If someone passes you an empty list, len(nums) is 0 and the division blows up. The polite fix is to handle that case explicitly:
def average(nums): if len(nums) == 0: return 0 return total(nums) / len(nums)
This early-return pattern is one of the most useful tricks return gives us — "handle the weird case, hand back something safe, get out."
The list parameter is the same list the caller gave you
This is important. When you pass a list into a function, the function receives the same list — not a copy. If your function appends to it, the caller's list grows too:
def add_one_more(items): items.append("teh tarik") # no return needed — the original list was modified menu = ["nasi lemak", "roti canai"] add_one_more(menu) print(menu) # ['nasi lemak', 'roti canai', 'teh tarik']
For Level 1 we'll mostly stick to functions that read the list and return a number — that's the safest pattern.
List → in, single value → out. total, biggest, count_even, average all collapse a list down to one number you can store and reuse.
Worked Example · Step Counter Report
12 minThe job
You logged your daily step count for a week. You want a tiny program that takes the list and prints a report: total steps, best day, average per day, and how many days you hit your goal of 8000 steps.
Step 1 · The data
steps = [6543, 9120, 7456, 11240, 5023, 8800, 9430] # Mon Tue Wed Thu Fri Sat Sun
Step 2 · Four small functions
def total(nums): answer = 0 for n in nums: answer = answer + n return answer def biggest(nums): answer = nums[0] for n in nums: if n > answer: answer = n return answer def average(nums): if len(nums) == 0: return 0 return total(nums) / len(nums) def count_at_or_above(nums, target): answer = 0 for n in nums: if n >= target: answer = answer + 1 return answer
Notice count_at_or_above takes two parameters — the list and a number. That's nothing new; it's the same multi-parameter pattern from PY-L1-28.
Step 3 · The report
Save as step_report.py:
Code
# step_report.py — summarise a week of step counts def total(nums): answer = 0 for n in nums: answer = answer + n return answer def biggest(nums): answer = nums[0] for n in nums: if n > answer: answer = n return answer def average(nums): if len(nums) == 0: return 0 return total(nums) / len(nums) def count_at_or_above(nums, target): answer = 0 for n in nums: if n >= target: answer = answer + 1 return answer # main program steps = [6543, 9120, 7456, 11240, 5023, 8800, 9430] goal = 8000 print("This week's report") print("=" * 30) print("Total steps: ", total(steps)) print("Best day: ", biggest(steps)) print("Average per day:", round(average(steps))) print("Days at goal: ", count_at_or_above(steps, goal), "/", len(steps))
Sample run
This week's report ============================== Total steps: 57612 Best day: 11240 Average per day: 8230 Days at goal: 4 / 7
The functions are reusable. Next week you could call biggest(rainfall_mm) or average(temperatures) or count_at_or_above(test_scores, 80) and they'd all just work. You've built a tiny data-analysis library and you're still in Level 1.
Try It Yourself
13 minThree tasks. Every function must take a list as one of its parameters and return a single value.
Write my_sum(nums) that returns the total of all items in nums. Don't use the built-in sum(). Test it on [10, 20, 30] and [1, 2, 3, 4, 5].
Hint
def my_sum(nums): answer = 0 for n in nums: answer = answer + n return answer print(my_sum([10, 20, 30])) # 60 print(my_sum([1, 2, 3, 4, 5])) # 15 print(my_sum([])) # 0 (empty list — starts at 0)
Notice the empty-list call works because answer starts at 0 and the loop simply doesn't run. Free behaviour from the way we set up answer.
Write count_negatives(nums) that returns how many numbers in the list are less than 0. Test on [3, -1, 4, -1, 5, -9, 2].
Hint
def count_negatives(nums): answer = 0 for n in nums: if n < 0: answer = answer + 1 return answer print(count_negatives([3, -1, 4, -1, 5, -9, 2])) # 3 print(count_negatives([1, 2, 3])) # 0
The same pattern as count_even in the lesson — initialise to 0, increment when a condition matches, return. Change the if and you have count_evens, count_above_100, anything.
Write longest(words) that returns the longest string in a list of words. If two are equally long, you can return either one. Test on ["roti", "canai", "teh", "nasi lemak", "maggi"].
Hint
def longest(words): answer = words[0] for w in words: if len(w) > len(answer): answer = w return answer menu = ["roti", "canai", "teh", "nasi lemak", "maggi"] print(longest(menu)) # → "nasi lemak"
Same shape as biggest, just comparing len(w) instead of the values themselves. The skeleton is reusable.
Mini-Challenge · Lina's Forever-Loop Average
8 minLina's average function gives the wrong answer and her teacher is convinced. Find the three bugs.
# lina_average.py — buggy
def average(nums):
answer = 0
for n in nums:
answer = n
return answer / len(nums)
print(average([10, 20, 30])) # should be 20.0
print(average([5, 5, 5, 5])) # should be 5.0
print(average([])) # currently crashes- Bug 1. Inside the loop she wrote
answer = ninstead ofanswer = answer + n. Every iteration overwrites the running total — by the end,answeris just the last item. - Bug 2. No early return for the empty list.
average([])divides by zero. - Bug 3 (subtle). The function never uses
totalas a helper, even though we built one. Not a crash bug — just a missed opportunity to reuse.
Show one possible fix
# lina_average.py — fixed (using a helper) def total(nums): answer = 0 for n in nums: answer = answer + n return answer def average(nums): if len(nums) == 0: return 0 return total(nums) / len(nums) print(average([10, 20, 30])) # 20.0 print(average([5, 5, 5, 5])) # 5.0 print(average([])) # 0
The answer = n vs answer = answer + n swap is the most-asked-about beginner bug at the Hub — the difference between "set to" and "add on" matters a lot inside a loop. The empty-list guard is the safety net every list function should have.
Recap
3 minA list can be passed into a function exactly like any other value — one argument, one parameter, one slot. Inside the body you usually loop through the items and accumulate a single answer into a variable, then return it. The pattern repeats with tiny variations to give you total, biggest, count_even, longest and friends. Once you have them, you can compose them — average is just total / len. Two safety habits to grow: handle the empty-list case with an early return, and remember that lists passed in are not copied — what the function does to them happens to the caller's list. Next lesson we start a two-part game: the Quiz Engine.
Vocabulary Card
- list parameter
- A function parameter whose value is a list. Use a
forloop inside the function to inspect its items. - accumulator
- A variable like
answerthat starts at a sensible value (often 0) and is updated inside a loop. Returned once the loop finishes. - early return
- A
returnused near the top of a function to handle a special case (like an empty list) and exit immediately. - summary function
- A function that takes a list and returns a single value — total, max, count, average. The building blocks of data work.
- composing on lists
- Calling one list-function inside another:
average(nums) = total(nums) / len(nums).
Homework
4 minCreate a new file class_stats.py. You're a teacher with a list of test scores.
- Set
scores = [82, 95, 67, 100, 73, 88, 54, 91, 79, 60]at the top. - Define
total(nums)— returns the sum of the list. - Define
average(nums)— returnstotal(nums) / len(nums), with an empty-list guard that returns 0. - Define
biggest(nums)— returns the largest item. - Define
smallest(nums)— same shape asbiggestbut with<. - Define
count_passing(nums, pass_mark=50)— returns how many items are>= pass_mark. Notice the default parameter. - In the main program, print a five-line report: total, average (rounded to 1 decimal), highest, lowest, and "X out of N passed (mark >= 50)".
Bring class_stats.py next class. In PY-L1-32 we'll wrap the next-most-useful pattern — "ask a question, score it" — into a function and start building a Quiz Engine.
Sample · class_stats.py
# class_stats.py — five list functions + one report def total(nums): answer = 0 for n in nums: answer = answer + n return answer def average(nums): if len(nums) == 0: return 0 return total(nums) / len(nums) def biggest(nums): answer = nums[0] for n in nums: if n > answer: answer = n return answer def smallest(nums): answer = nums[0] for n in nums: if n < answer: answer = n return answer def count_passing(nums, pass_mark=50): answer = 0 for n in nums: if n >= pass_mark: answer = answer + 1 return answer # main program scores = [82, 95, 67, 100, 73, 88, 54, 91, 79, 60] print("Total : ", total(scores)) print("Average: ", round(average(scores), 1)) print("Highest: ", biggest(scores)) print("Lowest : ", smallest(scores)) print(count_passing(scores), "out of", len(scores), "passed (mark >= 50)")
Five functions, all the same skeleton — initialise, loop, compare/add, return. Once you see the pattern, you can write a sixth (median? mode? count distinct?) just by changing the body. That's why functions matter.