Week 4: Functions, Scope, Modules and the "Daftari la Wanafunzi" Project
Month 1 | Main goal: The student can split a program into reusable functions, use modules (Python's and their own), and complete a first full CLI project ("Daftari la Wanafunzi" = Student Register).
Teacher Preparation (read before the week starts)
- Create the folder
python_darasa\wiki04, and inside it the folderdaftarifor the project. - Read: https://docs.python.org/3/tutorial/controlflow.html#defining-functions (up to 4.9) and https://docs.python.org/3/tutorial/modules.html (section 6.1 only).
- Write the "Daftari la Wanafunzi" project yourself before Session 3 (the complete code is in Session 3). Run it, and test it with bad input. If you understand every line, you will be able to help the student when they get stuck.
- This week's hard concepts:
returnversusprintand scope (local vs global). Try the examples from Session 1 Step 3 and Session 2 Step 1 in the REPL until you can explain them in your own words. - Session 3 is also a review of Month 1 — prepare the quick questions (they are in Session 3).
Week Goals (by the end of the week the student will be able to...)
- Write a function with
def, call it, and understand why functions matter (DRY — Don't Repeat Yourself). - Use parameters and arguments; distinguish
returnfromprint; return several values (a tuple). - Use default arguments, keyword arguments,
*argsand**kwargs. - Understand scope (local and global) and why to avoid
global. - Write docstrings and use
help(). - Use
importand modules:math,random,datetime, and create their own module. - Build the CLI program "Daftari la Wanafunzi" using lists, dicts and functions.
Session 1 — Functions: def, parameters, return
Session flow
- Homework review (kamusi.py)
- Why functions? We already use:
print,len,input defwithout parameters, then with parametersreturnversusprint- Break
- Returning several values; early
return; a function calling a function - Converting an old program (grade bands for marks) into functions
- In-class exercise
- Q&A + homework
Explanation for the teacher (read this first)
- A function is a named piece of code that you write once and use (call) many times.
- We have already used Python's built-in functions:
print(),len(),input(),int(). Now we create our own. -
Structure:
def jina_la_function(parameter1, parameter2): # mwili (body) — indented return matokeo
-
Defining is not running.
deftells Python "remember these instructions". They don't happen until you call it:jina_la_function(3, 4). - A parameter is the variable name in the
def(a,b). An argument is the actual value you send when calling (3,4). returnsends a value back to the caller and ends the function immediately. You can store it:x = jumlisha(3, 4)(jumlisha= add).printonly displays on the screen — it gives the caller nothing. A function with noreturnreturnsNone.- A simple rule: a function should do one job; if it calculates something, return it; let the code calling it decide whether to print it.
- Returning several values:
return ndogo, kubwa(smallest, largest) → returns a tuple →a, b = function(). - A function must be defined before it is called (higher up in the file).
Say this to the student
- "A function is like a juice machine: you put in fruit (arguments), it works inside (body), and it gives you juice (return). You don't care how it works inside — you just use it."
- "Or like a chapati recipe written in a notebook. Writing the recipe (def) isn't cooking. Cooking is calling it:
pika_chapati(5)." - "
returnis like a waiter bringing food to your table — you can eat it, save it, give it to someone.printis like a waiter showing you the food through a window and then walking off with it!" - "DRY — Don't Repeat Yourself. If you find yourself copying the same code three times, it's time to create a function."
Type this code live (share screen)
Step 1 — functions.py: the first function
def salimu(): print("Habari!") print("Karibu darasani.") salimu() salimu()
Habari! Karibu darasani. Habari! Karibu darasani.
Delete the last two salimu() calls (salimu = greet) and run — nothing happens. "Defining is not running."
Step 2 — Parameters
def salimu(jina): print(f"Habari {jina}!") salimu("Amina") salimu("Juma")
def eneo_mstatili(urefu, upana): print("Eneo ni", urefu * upana) eneo_mstatili(5, 3) # Eneo ni 15
Step 3 — return versus print (very important!)
def jumlisha_print(a, b): print(a + b) def jumlisha_return(a, b): return a + b x = jumlisha_print(3, 4) # inaonyesha 7 print("x ni:", x) # x ni: None ← !! y = jumlisha_return(3, 4) # haionyeshi kitu print("y ni:", y) # y ni: 7 print(jumlisha_return(10, 5) * 2) # 30 — tunaweza kuitumia kwenye hesabu
Ask: "Why is x None?" → the function didn't return anything.
Step 4 — return ends the function
def ni_mtu_mzima(umri): if umri >= 18: return True return False # inafikiwa tu kama juu haikurudisha print(ni_mtu_mzima(20)) # True print(ni_mtu_mzima(12)) # False def gawa(a, b): if b == 0: return "Huwezi kugawa kwa sifuri" return a / b print("Mstari huu haufikiwi kamwe") print(gawa(10, 2)) # 5.0 print(gawa(10, 0)) # Huwezi kugawa kwa sifuri
Step 5 — Several values
def takwimu(namba): return min(namba), max(namba), sum(namba) / len(namba) ndogo, kubwa, wastani = takwimu([45, 82, 67, 90]) print(ndogo, kubwa, wastani) # 45 90 71.0
Step 6 — Converting an old program into functions
def daraja(alama): if alama >= 75: return "A" elif alama >= 65: return "B" elif alama >= 45: return "C" elif alama >= 30: return "D" else: return "F" def wastani(alama_list): if len(alama_list) == 0: return 0 return sum(alama_list) / len(alama_list) alama = [78, 64, 50, 29] for a in alama: print(a, "->", daraja(a)) w = wastani(alama) print(f"Wastani: {w:.1f}, daraja: {daraja(w)}")
78 -> A 64 -> C 50 -> C 29 -> F Wastani: 55.2, daraja: C
Emphasise: the function daraja (grade) is used 5 times without writing if/elif again.
Step 7 — A function calling a function
def eneo_duara(r): return 3.14159 * r ** 2 def gharama_rangi(r, bei_kwa_m2): return eneo_duara(r) * bei_kwa_m2 print(f"{gharama_rangi(2, 5000):,.0f} TZS") # 62,832 TZS
Questions to ask the student
- The difference between defining and calling a function? — Answer:
defstores the instructions; callingf()is what runs them. - What does a function with no
returnreturn? — Answer:None. - Parameter and argument? — Answer: A parameter is the name in the
def; an argument is the value sent. - Does code after
returnrun? — Answer: No,returnends the function. - Why is
returnbetter thanprintinside a calculating function? — Answer: So the result can be reused (stored, calculated with, compared).
Common mistakes
| Mistake | Error message | Fix |
|---|---|---|
| Calling before defining | NameError: name 'salimu' is not defined |
Put the def above the place you call it. |
| Wrong number of arguments | TypeError: eneo_mstatili() missing 1 required positional argument: 'upana' |
Send all the arguments. |
| Too many arguments | TypeError: salimu() takes 1 positional argument but 2 were given |
Match them to the parameters. |
| Forgetting parentheses when calling | salimu → nothing happens; print(salimu) → <function salimu at 0x...> |
salimu() |
Using print instead of return |
The result is None; TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' |
return the value. |
Forgetting : or indentation |
SyntaxError: expected ':' / IndentationError: expected an indented block after function definition |
def f(): + 4 spaces. |
In-class exercise
- Write
celsius_to_fahrenheit(c)that returnsc * 9/5 + 32. Test it with 0, 37, 100. - Write
ni_shufwa(n)(is even) that returnsTrue/False. Use it to print the even numbers 1–20. - Write
hesabu_vokali(neno)(count vowels) that returns the number of vowels. - Write
bei_baada_ya_punguzo(bei, asilimia)(price after discount; price, percentage) that returns the new price. E.g.(10000, 15)→8500.0.
Answer
def celsius_to_fahrenheit(c): return c * 9 / 5 + 32 for c in [0, 37, 100]: print(c, "C =", celsius_to_fahrenheit(c), "F") # 0 C = 32.0 F, 37 C = 98.6 F, 100 C = 212.0 F def ni_shufwa(n): return n % 2 == 0 for i in range(1, 21): if ni_shufwa(i): print(i, end=" ") print() def hesabu_vokali(neno): idadi = 0 for h in neno.lower(): if h in "aeiou": idadi += 1 return idadi print(hesabu_vokali("Tanzania")) # 4 def bei_baada_ya_punguzo(bei, asilimia): return bei - bei * asilimia / 100 print(bei_baada_ya_punguzo(10000, 15)) # 8500.0
Session 2 — Default/Keyword Arguments, *args/**kwargs, Scope, Docstrings, Modules
Session flow
- Homework review
- Default arguments and keyword arguments
*argsand**kwargs- Scope: local and global
- Break
- Docstrings and
help() - Modules:
import math,random,datetime,from ... import - Our own module:
zana.py(tools) +main.py - Q&A + homework
Explanation for the teacher (read this first)
- Default argument:
def salimu(jina, salamu="Habari"):— if the caller doesn't sendsalamu(greeting), it uses"Habari". Parameters with a default must come after those without one. - Keyword argument: when calling, state the name:
salimu(salamu="Shikamoo", jina="Bibi")— the order doesn't matter. You have already used these:print(..., sep="-", end=""). *args: collects any extra (positional) arguments into a tuple.def jumla(*namba):→jumla(1, 2, 3)→namba = (1, 2, 3).**kwargs: collects extra keyword arguments into a dict.def wasifu(**taarifa):→wasifu(jina="Ali", mji="Moshi")→taarifa = {"jina": "Ali", "mji": "Moshi"}.- The names
args/kwargsare just a convention; what matters is*and**. - Scope: a variable created inside a function is local — it lives only inside and disappears when the function ends. A variable created outside (at the top of the file) is global — it can be read inside a function. But if you assign it a value inside a function (
x = ...), Python creates a new local with that name; the global doesn't change. - The keyword
global xallows changing a global inside a function — avoid it; it is better to pass it as a parameter and usereturn. - Caution (mutable): if a global is a list/dict and you change it with a method (
.append) inside a function, it really does change (there is no new assignment). We will use this in the project in Session 3 (we pass the list as a parameter). - Docstring: a
"""..."""string on the first line inside a function, explaining what the function does.help(jina_la_function)shows it. VS Code shows it when you hover the mouse over the name. - A module is a
.pyfile with functions/variables.import math→math.sqrt(16).from math import sqrt→sqrt(16).import datetime as dt→ a short name.math:sqrt,pi,ceil,floor,powrandom:randint,choice,shuffle,randomdatetime:date.today(),datetime.now(),.strftime("%d/%m/%Y"), subtracting two dates →timedelta
- Your own module: a file
zana.pyin the same folder →import zanainmain.py. Don't name your filemath.py,random.py,datetime.py! if __name__ == "__main__":— the code under it runs only when you run that file directly, not when it is imported. Teach it briefly; we will use it in the project.
Say this to the student
- "A default argument is like ordering tea: if you don't say anything, you get milk tea (the default). If you say 'black tea', you get that."
- "A keyword argument is like filling in a form by naming each field: 'Name: ..., Town: ...' — it doesn't matter which one you start with."
- "
*argsis like a basket — put in as many fruits as you like.**kwargsis like a basket with a label on each fruit." - "Scope is like an exam room: whatever is written on the scrap paper inside the room (local) stays there. The notice board outside (global) can be read by everyone, but not changed without special permission."
- "A module is like a toolbox.
import mathis opening the box of maths tools. You don't need to make the hammer yourself."
Type this code live (share screen)
Step 1 — Default and keyword (arguments.py)
def salimu(jina, salamu="Habari"): print(f"{salamu}, {jina}!") salimu("Amina") # Habari, Amina! salimu("Bibi", "Shikamoo") # Shikamoo, Bibi! salimu(salamu="Mambo", jina="Juma") # Mambo, Juma! def bei_jumla(bei, idadi=1, kodi=0.18): return bei * idadi * (1 + kodi) print(bei_jumla(1000)) # 1180.0 print(bei_jumla(1000, 3)) # 3540.0 print(bei_jumla(1000, kodi=0)) # 1000.0
Step 2 — *args and **kwargs
def jumla(*namba): print("namba ni:", namba) total = 0 for n in namba: total += n return total print(jumla(1, 2)) # namba ni: (1, 2) -> 3 print(jumla(5, 10, 15, 20)) # namba ni: (5, 10, 15, 20) -> 50 print(jumla()) # namba ni: () -> 0 def wasifu(jina, **taarifa): print(f"--- {jina} ---") for key, value in taarifa.items(): print(f"{key}: {value}") wasifu("Amina", umri=20, mji="Dodoma", kozi="Python")
--- Amina --- umri: 20 mji: Dodoma kozi: Python
Step 3 — Scope
x = 10 # global def onyesha(): print("Ndani, x =", x) # inaweza kusoma global onyesha() # Ndani, x = 10 def badilisha(): x = 99 # hii ni LOCAL mpya, si global print("Ndani, x =", x) badilisha() # Ndani, x = 99 print("Nje, x =", x) # Nje, x = 10 ← haijabadilika! def tengeneza(): siri = "local" return siri tengeneza() # print(siri) # NameError: name 'siri' is not defined
Show the common mistake:
hesabu = 0 def ongeza(): hesabu += 1 # UnboundLocalError ongeza()
UnboundLocalError: cannot access local variable 'hesabu' where it is not associated with a value
The better way (without global):
def ongeza(hesabu): return hesabu + 1 hesabu = 0 hesabu = ongeza(hesabu) hesabu = ongeza(hesabu) print(hesabu) # 2
And when a list is passed (mutable):
def ongeza_jina(orodha, jina): orodha.append(jina) # inabadilisha list ya asili majina = [] ongeza_jina(majina, "Amina") ongeza_jina(majina, "Juma") print(majina) # ['Amina', 'Juma']
Step 4 — Docstrings
def bmi(uzito, urefu): """Hesabu Body Mass Index. uzito: kilo (float) urefu: mita (float) Inarudisha BMI kama float. """ return uzito / urefu ** 2 help(bmi) print(bmi(65, 1.7))
Also show: hover the mouse over bmi in VS Code — the docstring appears.
Step 5 — math and random (modules_demo.py)
import math import random print(math.sqrt(81)) # 9.0 print(math.pi) # 3.141592653589793 print(math.ceil(4.1)) # 5 print(math.floor(4.9)) # 4 print(round(math.pi, 2)) # 3.14 print(random.randint(1, 6)) # mf. 4 print(random.choice(["Simba", "Yanga", "Azam"])) # mf. Yanga kadi = [1, 2, 3, 4, 5] random.shuffle(kadi) print(kadi) # mf. [3, 1, 5, 2, 4] print(random.random()) # mf. 0.5372... (0 hadi chini ya 1)
from math import sqrt, pi print(sqrt(16), pi) # 4.0 3.141592653589793
Step 6 — datetime
from datetime import date, datetime leo = date.today() print(leo) # mf. 2026-11-25 print(leo.year, leo.month, leo.day) print(leo.strftime("%d/%m/%Y")) # mf. 25/11/2026 sasa = datetime.now() print(sasa.strftime("%H:%M")) # mf. 18:30 mwaka_mpya = date(2027, 1, 1) siku_zilizobaki = (mwaka_mpya - leo).days print(f"Siku {siku_zilizobaki} hadi mwaka mpya") def umri_kutoka_tarehe(mwaka, mwezi, siku): """Rudisha umri (miaka kamili) kutoka tarehe ya kuzaliwa.""" leo = date.today() umri = leo.year - mwaka if (leo.month, leo.day) < (mwezi, siku): # bado hajafikisha siku ya kuzaliwa umri -= 1 return umri print(umri_kutoka_tarehe(2005, 12, 15))
Notice: (leo.month, leo.day) < (mwezi, siku) compares tuples — it starts with the first element, and if they are equal it moves to the second. A good example for revisiting the tuples of Week 3.
Step 7 — Our module: create zana.py
# zana.py — functions zetu za msaada """Zana ndogo za kusaidia programu zetu.""" KIWANGO_USD = 2600 def tzs_to_usd(tzs): """Badilisha shilingi kuwa dola.""" return round(tzs / KIWANGO_USD, 2) def mstari(urefu=30, alama="="): """Rudisha mstari wa alama kwa ajili ya kupamba.""" return alama * urefu if __name__ == "__main__": # inaendeshwa tu ukiendesha zana.py moja kwa moja print("Kujaribu zana:", tzs_to_usd(26000))
Then main.py in the same folder:
import zana print(zana.mstari()) print("Dola:", zana.tzs_to_usd(52000)) print("Kiwango:", zana.KIWANGO_USD) print(zana.mstari(20, "-"))
Run main.py:
============================== Dola: 20.0 Kiwango: 2600 --------------------
Then run zana.py directly → Kujaribu zana: 10.0. Explain: "When you run main.py, the if __name__ == "__main__": part of zana.py doesn't run."
Questions to ask the student
def f(a, b=2)— inf(5), what isb? — Answer: 2.- Does
def f(a=1, b):work? — Answer: No:SyntaxError: parameter without a default follows parameter with a default. - What type does
*argsbecome inside a function?**kwargs? — Answer: tuple; dict. - Is a variable created inside a function available outside? — Answer: No (it is local).
- The difference between
import mathandfrom math import sqrt? — Answer: With the first you writemath.sqrt(); with the second,sqrt()directly. - Why shouldn't you name your file
random.py? — Answer: Python will import your file instead of Python's module.
Common mistakes
| Mistake | Error message | Fix |
|---|---|---|
| A default before a non-default | SyntaxError: parameter without a default follows parameter with a default |
Put those with defaults last. |
Changing a global without global |
UnboundLocalError: cannot access local variable 'hesabu' where it is not associated with a value |
Pass it as a parameter + return. |
| Using a local outside | NameError: name 'siri' is not defined |
return the value. |
| Forgetting the module name | sqrt(9) after import math → NameError: name 'sqrt' is not defined |
math.sqrt(9) or from math import sqrt. |
| Module missing / spelling | ModuleNotFoundError: No module named 'zana' |
The file must be in the same folder; VS Code must have that folder open; check the spelling. |
| Your file has a module's name | AttributeError: module 'random' has no attribute 'randint' (often also (consider renaming '...\random.py' since it has the same name as the standard library module named 'random'...)) |
Rename the file, delete __pycache__. |
datetime.now() after import datetime |
AttributeError: module 'datetime' has no attribute 'now' |
datetime.datetime.now() or from datetime import datetime. |
| A keyword argument that doesn't exist | TypeError: salimu() got an unexpected keyword argument 'jna' |
Check the parameter's spelling. |
In-class exercise
- Write
tengeneza_email(jina, mwisho, kikoa="shule.ac.tz")(create email; first name, last name, domain) that returnsjina.mwisho@kikoain lowercase. - Write
wastani(*alama)(average of marks) that returns the average of any number of marks; it should return 0 if there are none. - With
random, writetengeneza_password(urefu=8)(urefu= length) that returns a password of random letters and numbers. (Hint:random.choice("abc...xyz0123456789").) - Write
siku_hadi(mwaka, mwezi, siku)(days until; year, month, day) that returns the days left from today until that date.
Answer
import random from datetime import date def tengeneza_email(jina, mwisho, kikoa="shule.ac.tz"): """Rudisha email kwa mfumo jina.mwisho@kikoa.""" return f"{jina.strip().lower()}.{mwisho.strip().lower()}@{kikoa}" print(tengeneza_email("Amina", "Juma")) # amina.juma@shule.ac.tz print(tengeneza_email("Ali", "Hassan", kikoa="kazi.co.tz")) def wastani(*alama): """Rudisha wastani wa alama zozote zilizotumwa.""" if len(alama) == 0: return 0 return sum(alama) / len(alama) print(wastani(70, 80, 90)) # 80.0 print(wastani()) # 0 def tengeneza_password(urefu=8): """Rudisha password ya bahati ya herufi ndogo, kubwa na namba.""" herufi = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" password = "" for _ in range(urefu): password += random.choice(herufi) return password print(tengeneza_password()) # mf. aZ3kP9qL print(tengeneza_password(12)) def siku_hadi(mwaka, mwezi, siku): """Rudisha idadi ya siku kutoka leo hadi tarehe iliyotolewa.""" return (date(mwaka, mwezi, siku) - date.today()).days print(siku_hadi(2026, 12, 25), "siku hadi Krismasi")
Remember: _ in for _ in range(...) is the name of a variable we don't need — a common convention. For real passwords Python has the secrets module (more secure) — we will mention it later.
Session 3 — Mini Project: CLI "Daftari la Wanafunzi" + Month 1 Review
Session flow
- Homework review
- Explaining the project: what it does, the data structure, the list of functions (plan first!)
- Steps 1–2: data + helper functions (
daraja,wastani,pata_namba) - Step 3: add a student, show all
- Break
- Step 4: search, add a mark, delete
- Step 5: class statistics + main menu (
main) - Month 1 review: quick questions
- Q&A + homework + feedback on the month
Explanation for the teacher (read this first)
- What the project does: a terminal program (CLI = Command Line Interface) with which a teacher can:
- Add a student (name + subject marks)
- Show all students (table: name, average, grade)
- Search for a student by name
- Add/change a subject mark
- Delete a student
- See class statistics (class average, best student, number in each grade)
- Exit
-
Data structure: a list of dicts; each student has a dict of marks (subject → mark):
wanafunzi = [ {"jina": "Amina Juma", "alama": {"Hisabati": 78, "Kiingereza": 85}}, ]
This uses everything from this month: list, dict, nested structure, loops, if, f-strings, functions.
-
Code structure rules:
- Each job = one function (
ongeza_mwanafunzi(add student),onyesha_wote(show all), ...). - Functions receive the list
wanafunzi(students) as a parameter (we don't useglobal). Because a list is mutable,append/removeinside a function change the original list. - Small helper functions (
daraja,wastani) return values; menu functions display (print). pata_namba()(get number) asks again until the user types a valid number — so the program doesn't crash with aValueError. We usetry/excepthere for the first time, only briefly: "try converting; if it fails (ValueError), do this." We will study it in depth next month.main()has the menu; at the end of the file:if __name__ == "__main__": main().
- Each job = one function (
- Teaching method: don't write the whole file at once. Write one step, run it, show that it works, then add more. The student should type along with you on their own computer (or follow you and ask questions). If you run short of time, finish steps 1–4 and make step 5 homework.
Say this to the student
- "Today we build a real program — like one a class teacher would use instead of a paper register."
- "A builder doesn't build a house without a plan. Before the code, we plan: how will the data be stored? What functions do we need?"
- "Each function is like one employee in an office: one receives new students, another searches, another calculates statistics.
main()is the manager who takes requests and sends the right employee." - "A good program doesn't crash when the user makes a mistake. That's why
pata_nambapolitely asks again."
Type this code live (share screen)
Create daftari\daftari.py. Below is the complete version; type it in the stages marked in the comments.
"""Daftari la Wanafunzi — programu ya CLI ya kusimamia alama za wanafunzi. Mradi wa Mwezi 1: lists, dicts, loops, if, f-strings na functions. """ # ========== HATUA 1: Data ya kuanzia ========== MASOMO = ("Hisabati", "Kiingereza", "Kiswahili", "Python") # tuple: haibadiliki def data_ya_mfano(): """Rudisha list ya wanafunzi wa mfano ili tuanze na data.""" return [ {"jina": "Amina Juma", "alama": {"Hisabati": 78, "Kiingereza": 85, "Kiswahili": 90, "Python": 88}}, {"jina": "Baraka Mushi", "alama": {"Hisabati": 55, "Kiingereza": 62, "Kiswahili": 70, "Python": 48}}, {"jina": "Neema Said", "alama": {"Hisabati": 92, "Kiingereza": 74, "Kiswahili": 81, "Python": 95}}, ] # ========== HATUA 2: Functions za msaada ========== def wastani(alama): """Rudisha wastani wa dict ya alama {somo: alama}. Rudisha 0 kama ni tupu.""" if len(alama) == 0: return 0 return sum(alama.values()) / len(alama) def daraja(alama): """Rudisha daraja (A-F) la alama moja.""" if alama >= 75: return "A" elif alama >= 65: return "B" elif alama >= 45: return "C" elif alama >= 30: return "D" else: return "F" def pata_namba(ujumbe, chini=0, juu=100): """Uliza namba mpaka mtumiaji aandike namba sahihi kati ya chini na juu.""" while True: jibu = input(ujumbe).strip() try: namba = int(jibu) except ValueError: print(" ! Tafadhali andika namba kamili.") continue if chini <= namba <= juu: return namba print(f" ! Namba iwe kati ya {chini} na {juu}.") def tafuta(wanafunzi, jina): """Rudisha dict ya mwanafunzi mwenye jina hilo (bila kujali herufi kubwa), au None.""" for m in wanafunzi: if m["jina"].lower() == jina.strip().lower(): return m return None # ========== HATUA 3: Ongeza na onyesha ========== def ongeza_mwanafunzi(wanafunzi): """Uliza jina na alama za kila somo, kisha ongeza mwanafunzi kwenye list.""" jina = input("Jina kamili: ").strip().title() if jina == "": print(" ! Jina haliwezi kuwa tupu.") return if tafuta(wanafunzi, jina) is not None: print(f" ! {jina} tayari yupo.") return alama = {} for somo in MASOMO: alama[somo] = pata_namba(f" Alama ya {somo} (0-100): ") wanafunzi.append({"jina": jina, "alama": alama}) print(f" ✓ {jina} ameongezwa. Wastani: {wastani(alama):.1f}") def onyesha_wote(wanafunzi): """Onyesha jedwali la wanafunzi wote: jina, wastani, daraja.""" if not wanafunzi: print(" Hakuna wanafunzi bado.") return print(f"\n {'#':<3}{'JINA':<20}{'WASTANI':>8} DARAJA") print(" " + "-" * 39) for i, m in enumerate(wanafunzi, start=1): w = wastani(m["alama"]) print(f" {i:<3}{m['jina']:<20}{w:>8.1f} {daraja(w)}") print() def onyesha_mmoja(m): """Onyesha alama zote za mwanafunzi mmoja.""" print(f"\n == {m['jina']} ==") for somo, a in m["alama"].items(): print(f" {somo:<12}{a:>4} {daraja(a)}") w = wastani(m["alama"]) print(f" {'WASTANI':<12}{w:>4.0f} {daraja(w)}\n") # ========== HATUA 4: Tafuta, badilisha alama, futa ========== def tafuta_mwanafunzi(wanafunzi): """Uliza jina na onyesha taarifa za mwanafunzi.""" jina = input("Jina la kutafuta: ") m = tafuta(wanafunzi, jina) if m is None: print(" ! Hakupatikana.") else: onyesha_mmoja(m) def badilisha_alama(wanafunzi): """Ongeza au badilisha alama ya somo moja kwa mwanafunzi.""" m = tafuta(wanafunzi, input("Jina la mwanafunzi: ")) if m is None: print(" ! Hakupatikana.") return for i, somo in enumerate(MASOMO, start=1): print(f" {i}. {somo} (sasa: {m['alama'].get(somo, '-')})") chaguo = pata_namba(" Chagua somo: ", 1, len(MASOMO)) somo = MASOMO[chaguo - 1] m["alama"][somo] = pata_namba(f" Alama mpya ya {somo}: ") print(f" ✓ Imebadilishwa. Wastani mpya: {wastani(m['alama']):.1f}") def futa_mwanafunzi(wanafunzi): """Futa mwanafunzi baada ya kuthibitisha.""" m = tafuta(wanafunzi, input("Jina la kufuta: ")) if m is None: print(" ! Hakupatikana.") return thibitisha = input(f" Una uhakika unataka kumfuta {m['jina']}? (ndiyo/hapana): ") if thibitisha.strip().lower() == "ndiyo": wanafunzi.remove(m) print(" ✓ Amefutwa.") else: print(" Imesitishwa.") # ========== HATUA 5: Takwimu na menyu kuu ========== def takwimu_darasa(wanafunzi): """Onyesha wastani wa darasa, mwanafunzi bora, na idadi kwa kila daraja.""" if not wanafunzi: print(" Hakuna wanafunzi bado.") return wastani_list = [wastani(m["alama"]) for m in wanafunzi] wastani_darasa = sum(wastani_list) / len(wastani_list) bora = wanafunzi[0] for m in wanafunzi: if wastani(m["alama"]) > wastani(bora["alama"]): bora = m madaraja = {"A": 0, "B": 0, "C": 0, "D": 0, "F": 0} for w in wastani_list: madaraja[daraja(w)] += 1 print(f"\n Idadi ya wanafunzi: {len(wanafunzi)}") print(f" Wastani wa darasa: {wastani_darasa:.1f} ({daraja(wastani_darasa)})") print(f" Mwanafunzi bora: {bora['jina']} ({wastani(bora['alama']):.1f})") print(" Madaraja:") for d, idadi in madaraja.items(): print(f" {d}: {'#' * idadi} {idadi}") print(" Wastani kwa somo:") for somo in MASOMO: alama_somo = [m["alama"][somo] for m in wanafunzi if somo in m["alama"]] if alama_somo: print(f" {somo:<12}{sum(alama_somo) / len(alama_somo):>6.1f}") print() def main(): """Menyu kuu ya programu.""" wanafunzi = data_ya_mfano() vitendo = { "1": ongeza_mwanafunzi, "2": onyesha_wote, "3": tafuta_mwanafunzi, "4": badilisha_alama, "5": futa_mwanafunzi, "6": takwimu_darasa, } print("=" * 40) print(" DAFTARI LA WANAFUNZI v1.0") print("=" * 40) while True: print("1) Ongeza mwanafunzi 2) Onyesha wote") print("3) Tafuta 4) Badilisha alama") print("5) Futa 6) Takwimu za darasa") print("0) Toka") chaguo = input("Chagua: ").strip() if chaguo == "0": print("Kwaheri! Daftari limefungwa.") break elif chaguo in vitendo: vitendo[chaguo](wanafunzi) else: print(" ! Chaguo si sahihi, jaribu tena.") if __name__ == "__main__": main()
Notes on the difficult lines (read beforehand, explain in class):
-
vitendo = {"1": ongeza_mwanafunzi, ...}(vitendo= actions) — functions are objects too; we can put them in a dict without parentheses (we aren't calling them yet).vitendo[chaguo](wanafunzi)picks the right function and calls it. If this is hard for the student, show theif/elifversion (exactly equivalent):if chaguo == "1": ongeza_mwanafunzi(wanafunzi) elif chaguo == "2": onyesha_wote(wanafunzi) # ... n.k.
-
try: ... except ValueError:— "tryint(jibu); if the answer isn't a number, instead of the program crashing, do what is underexcept." is not None/is None— the correct way to check forNone.m['alama'].get(somo, '-')— if the subject has no mark yet, show-.
Sample run:
========================================
DAFTARI LA WANAFUNZI v1.0
========================================
1) Ongeza mwanafunzi 2) Onyesha wote
3) Tafuta 4) Badilisha alama
5) Futa 6) Takwimu za darasa
0) Toka
Chagua: 2
# JINA WASTANI DARAJA
---------------------------------------
1 Amina Juma 85.2 A
2 Baraka Mushi 58.8 C
3 Neema Said 85.5 A
Chagua: 1
Jina kamili: zawadi ally
Alama ya Hisabati (0-100): sabini
! Tafadhali andika namba kamili.
Alama ya Hisabati (0-100): 70
Alama ya Kiingereza (0-100): 150
! Namba iwe kati ya 0 na 100.
Alama ya Kiingereza (0-100): 66
Alama ya Kiswahili (0-100): 80
Alama ya Python (0-100): 72
✓ Zawadi Ally ameongezwa. Wastani: 72.0
Chagua: 6
Idadi ya wanafunzi: 4
Wastani wa darasa: 75.4 (A)
Mwanafunzi bora: Neema Said (85.5)
Madaraja:
A: ## 2
B: # 1
C: # 1
D: 0
F: 0
Wastani kwa somo:
Hisabati 73.8
Kiingereza 71.8
Kiswahili 80.2
Python 75.8
Chagua: 0
Kwaheri! Daftari limefungwa.
(The menu repeats after each choice — it has been left out of this example to keep it short.)
Month 1 review — quick questions:
- What is
type(input())always? —str 7 // 2and7 % 2? —3and1"Python"[::-1]? —"nohtyP"- What does
range(2, 8, 2)give? — 2, 4, 6 - The difference between a list and a tuple? — list is mutable, tuple is immutable
- For looking up a price by product name, which structure? — dict
- What does a function with no
returnreturn? —None - Why do we use functions? — to reuse code, organise the program, and make fixes easier (DRY)
- What error do you get with
int("abc")? —ValueError - In the project, why do we pass
wanafunzias a parameter instead of usingglobal? — clearer code, the function stands on its own, easier to test
Questions to ask the student
- Why is each student's
alamaa dict instead of a list? — Answer: So we know which subject each mark belongs to by name (alama["Python"]), not by index. - Why is
MASOMO(subjects) a tuple? — Answer: The subjects don't change while the program runs; a tuple prevents accidental changes. ongeza_mwanafunzihas noreturn— how does the new student reach the list inmain? — Answer: A list is mutable;appendinside the function changes the same list that was passed in.- What would happen without
pata_nambaif the user typed "sabini" (seventy)? — Answer: AValueError, and the program would crash. - How would you add a new subject "Fizikia" (physics)? — Answer: Add it to the
MASOMOtuple (and to the sample data if you want). - The data is lost when we close the program — why? How will we solve it? — Answer: It is only in memory (RAM). Next month we will learn to save to a file (JSON/CSV).
Common mistakes
| Mistake | Error message | Fix |
|---|---|---|
| Putting a function in a dict with parentheses | vitendo = {"1": ongeza_mwanafunzi(wanafunzi)} → calls it immediately when creating the dict |
Without parentheses: "1": ongeza_mwanafunzi |
| Quotes inside an f-string (Python ≤3.11) | f"{m["jina"]}" → SyntaxError: f-string: unmatched '[' |
f"{m['jina']}" |
| Forgetting to pass the list | TypeError: onyesha_wote() missing 1 required positional argument: 'wanafunzi' |
onyesha_wote(wanafunzi) |
Using tafuta (search) without checking for None |
TypeError: 'NoneType' object is not subscriptable |
if m is None: ... return |
| Average of an empty dict | ZeroDivisionError: division by zero |
Check len(alama) == 0 first. |
Forgetting to call main() |
The program runs without showing anything | Add if __name__ == "__main__": main() at the end. |
Indentation of return inside for |
tafuta returns None after only the first student |
return None must be outside the for (at the same level as for). |
Emoji/the ✓ symbol displaying badly in an old terminal |
Strange characters | Change ✓ to [OK]. |
In-class exercise
- Add an option
7) Panga kwa wastani(sort by average) that displays students from the highest average to the lowest (without changing the original list). - Change
tafutaso it finds a student even if you type part of the name (e.g. "ami" → "Amina Juma"). It should return a list of all matches. - Add to
takwimu_darasa(class statistics) the number who passed (average ≥ 45) and who failed.
Answer
# zoezi 1 — ongeza function hii, na "7": panga_kwa_wastani kwenye dict ya vitendo, # na mstari "7) Panga kwa wastani" kwenye menyu. def panga_kwa_wastani(wanafunzi): """Onyesha wanafunzi kuanzia wastani mkubwa, bila kubadilisha list ya asili.""" jozi = [] for m in wanafunzi: jozi.append((wastani(m["alama"]), m["jina"])) jozi.sort(reverse=True) # tuples zinapangwa kwa kipengele cha kwanza (wastani) for nafasi, (w, jina) in enumerate(jozi, start=1): print(f" {nafasi}. {jina:<20}{w:>6.1f}")
A more advanced version (for curiosity only): sorted(wanafunzi, key=lambda m: wastani(m["alama"]), reverse=True) — we will learn lambda later.
# zoezi 2 def tafuta_sehemu(wanafunzi, kipande): """Rudisha list ya wanafunzi ambao majina yao yana kipande hicho.""" kipande = kipande.strip().lower() matokeo = [] for m in wanafunzi: if kipande in m["jina"].lower(): matokeo.append(m) return matokeo def tafuta_mwanafunzi(wanafunzi): matokeo = tafuta_sehemu(wanafunzi, input("Jina (au sehemu): ")) if not matokeo: print(" ! Hakupatikana.") for m in matokeo: onyesha_mmoja(m)
# zoezi 3 — ongeza ndani ya takwimu_darasa, baada ya wastani_list waliofaulu = len([w for w in wastani_list if w >= 45]) walioshindwa = len(wastani_list) - waliofaulu print(f" Waliofaulu: {waliofaulu}, Walioshindwa: {walioshindwa}")
Homework
HW1 (after Session 1, bring to Session 2): maumbo.py (shapes) — write functions that return: eneo_mraba(upande) (area of a square; side), eneo_mstatili(urefu, upana) (rectangle; length, width), eneo_pembetatu(kitako, kimo) (triangle; base, height), eneo_duara(r) (circle). Then a small menu that asks the user for the shape and measurements, and displays the area to 2 decimal places.
HW2 (after Session 2, bring to Session 3): Create a module hesabu_zangu.py (my calculations) containing: wastani(*namba) (average), kubwa_zaidi(*namba) (largest, without max), ni_prime(n) (returns True/False), with a docstring for each. Then jaribu.py (try) imports that module and uses it. Bonus: make ni_prime use math.sqrt so that it is faster.
HW3 (after Session 3, bring to the next session): Improve "Daftari la Wanafunzi":
(a) an option to change a student's name;
(b) show the subject in which the class did best;
(c) a report for one student with today's date at the top (use datetime, format dd/mm/yyyy).
Answer
# maumbo.py import math def eneo_mraba(upande): return upande ** 2 def eneo_mstatili(urefu, upana): return urefu * upana def eneo_pembetatu(kitako, kimo): return 0.5 * kitako * kimo def eneo_duara(r): return math.pi * r ** 2 print("1) Mraba 2) Mstatili 3) Pembetatu 4) Duara") chaguo = input("Chagua umbo: ").strip() if chaguo == "1": eneo = eneo_mraba(float(input("Upande: "))) elif chaguo == "2": eneo = eneo_mstatili(float(input("Urefu: ")), float(input("Upana: "))) elif chaguo == "3": eneo = eneo_pembetatu(float(input("Kitako: ")), float(input("Kimo: "))) elif chaguo == "4": eneo = eneo_duara(float(input("Nusu kipenyo (r): "))) else: eneo = None if eneo is None: print("Chaguo si sahihi.") else: print(f"Eneo: {eneo:.2f}")
# hesabu_zangu.py """Functions zangu za hisabati.""" import math def wastani(*namba): """Rudisha wastani wa namba zote; 0 kama hakuna.""" if not namba: return 0 return sum(namba) / len(namba) def kubwa_zaidi(*namba): """Rudisha namba kubwa zaidi bila kutumia max(); None kama hakuna.""" if not namba: return None kubwa = namba[0] for n in namba: if n > kubwa: kubwa = n return kubwa def ni_prime(n): """Rudisha True kama n ni namba tasa (prime).""" if n < 2: return False for i in range(2, int(math.sqrt(n)) + 1): if n % i == 0: return False return True
# jaribu.py import hesabu_zangu as hz print(hz.wastani(10, 20, 30)) # 20.0 print(hz.kubwa_zaidi(4, 19, 7, 3)) # 19 primes = [n for n in range(1, 30) if hz.ni_prime(n)] print(primes) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] help(hz.ni_prime)
# KN3 — vipande vya kuongeza kwenye daftari.py from datetime import date # weka juu ya file # (a) def badilisha_jina(wanafunzi): """Badilisha jina la mwanafunzi.""" m = tafuta(wanafunzi, input("Jina la sasa: ")) if m is None: print(" ! Hakupatikana.") return jipya = input("Jina jipya: ").strip().title() if jipya == "": print(" ! Jina haliwezi kuwa tupu.") elif tafuta(wanafunzi, jipya) is not None: print(" ! Jina hilo tayari lipo.") else: m["jina"] = jipya print(" ✓ Jina limebadilishwa.") # (b) ongeza ndani ya takwimu_darasa, baada ya "Wastani kwa somo" somo_bora = None wastani_bora = -1 for somo in MASOMO: alama_somo = [m["alama"][somo] for m in wanafunzi if somo in m["alama"]] if alama_somo: w = sum(alama_somo) / len(alama_somo) if w > wastani_bora: somo_bora, wastani_bora = somo, w print(f" Somo bora la darasa: {somo_bora} ({wastani_bora:.1f})") # (c) badilisha onyesha_mmoja def onyesha_mmoja(m): """Onyesha ripoti ya mwanafunzi mmoja yenye tarehe ya leo.""" print(f"\n RIPOTI — {date.today().strftime('%d/%m/%Y')}") print(f" == {m['jina']} ==") for somo, a in m["alama"].items(): print(f" {somo:<12}{a:>4} {daraja(a)}") w = wastani(m["alama"]) print(f" {'WASTANI':<12}{w:>4.0f} {daraja(w)}\n") # Usisahau kuongeza "7": badilisha_jina kwenye vitendo na mstari kwenye menyu.
If you are asked a question you don't know
- Use
help(jina)ordir(module)in the REPL (e.g.import datetime; dir(datetime)) — it shows everything inside. For an error, copy the last line and search for it. - Search: "python function return vs print", "python args kwargs explained", "python datetime strftime", "python import own module same folder".
- Docs: https://docs.python.org/3/tutorial/controlflow.html#defining-functions , https://docs.python.org/3/tutorial/modules.html , https://docs.python.org/3/library/math.html , https://docs.python.org/3/library/random.html , https://docs.python.org/3/library/datetime.html
End-of-week checklist
- Writes a function with
def, calls it, and knows defining isn't running - Distinguishes parameter from argument
- Understands
returnversusprint, and that withoutreturn→None - Returns several values and does unpacking
- Uses default arguments and keyword arguments
- Understands
*args(tuple) and**kwargs(dict) - Explains local vs global and why to avoid
global - Writes docstrings and uses
help() - Uses
math,random,datetimewithimportandfrom ... import - Has created their own module and imported it into another file
- Has completed "Daftari la Wanafunzi" and can explain what each function does
- Month 1: can write a small complete program without copying (input → processing → output)