Contents

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 folder daftari for 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: return versus print and 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...)

  1. Write a function with def, call it, and understand why functions matter (DRY — Don't Repeat Yourself).
  2. Use parameters and arguments; distinguish return from print; return several values (a tuple).
  3. Use default arguments, keyword arguments, *args and **kwargs.
  4. Understand scope (local and global) and why to avoid global.
  5. Write docstrings and use help().
  6. Use import and modules: math, random, datetime, and create their own module.
  7. Build the CLI program "Daftari la Wanafunzi" using lists, dicts and functions.

Session 1 — Functions: def, parameters, return

Session flow

  1. Homework review (kamusi.py)
  2. Why functions? We already use: print, len, input
  3. def without parameters, then with parameters
  4. return versus print
  5. Break
  6. Returning several values; early return; a function calling a function
  7. Converting an old program (grade bands for marks) into functions
  8. In-class exercise
  9. 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:

    python
    def jina_la_function(parameter1, parameter2):
        # mwili (body) — indented
        return matokeo
    
  • Defining is not running. def tells 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).
  • return sends a value back to the caller and ends the function immediately. You can store it: x = jumlisha(3, 4) (jumlisha = add).
  • print only displays on the screen — it gives the caller nothing. A function with no return returns None.
  • 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)."
  • "return is like a waiter bringing food to your table — you can eat it, save it, give it to someone. print is 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

python
def salimu():
    print("Habari!")
    print("Karibu darasani.")

salimu()
salimu()
text
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

python
def salimu(jina):
    print(f"Habari {jina}!")

salimu("Amina")
salimu("Juma")
python
def eneo_mstatili(urefu, upana):
    print("Eneo ni", urefu * upana)

eneo_mstatili(5, 3)      # Eneo ni 15

Step 3 — return versus print (very important!)

python
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

python
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

python
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

python
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)}")
text
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

python
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

  1. The difference between defining and calling a function? — Answer: def stores the instructions; calling f() is what runs them.
  2. What does a function with no return return? — Answer: None.
  3. Parameter and argument? — Answer: A parameter is the name in the def; an argument is the value sent.
  4. Does code after return run? — Answer: No, return ends the function.
  5. Why is return better than print inside 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

  1. Write celsius_to_fahrenheit(c) that returns c * 9/5 + 32. Test it with 0, 37, 100.
  2. Write ni_shufwa(n) (is even) that returns True/False. Use it to print the even numbers 1–20.
  3. Write hesabu_vokali(neno) (count vowels) that returns the number of vowels.
  4. Write bei_baada_ya_punguzo(bei, asilimia) (price after discount; price, percentage) that returns the new price. E.g. (10000, 15) → 8500.0.
Answer
python
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

  1. Homework review
  2. Default arguments and keyword arguments
  3. *args and **kwargs
  4. Scope: local and global
  5. Break
  6. Docstrings and help()
  7. Modules: import math, random, datetime, from ... import
  8. Our own module: zana.py (tools) + main.py
  9. Q&A + homework

Explanation for the teacher (read this first)

  • Default argument: def salimu(jina, salamu="Habari"): — if the caller doesn't send salamu (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/kwargs are 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 x allows changing a global inside a function — avoid it; it is better to pass it as a parameter and use return.
  • 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 .py file 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, pow
    • random: randint, choice, shuffle, random
    • datetime: date.today(), datetime.now(), .strftime("%d/%m/%Y"), subtracting two dates → timedelta
  • Your own module: a file zana.py in the same folder → import zana in main.py. Don't name your file math.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."
  • "*args is like a basket — put in as many fruits as you like. **kwargs is 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 math is 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)

python
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

python
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")
text
--- Amina ---
umri: 20
mji: Dodoma
kozi: Python

Step 3 — Scope

python
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:

python
hesabu = 0
def ongeza():
    hesabu += 1        # UnboundLocalError
ongeza()
text
UnboundLocalError: cannot access local variable 'hesabu' where it is not associated with a value

The better way (without global):

python
def ongeza(hesabu):
    return hesabu + 1

hesabu = 0
hesabu = ongeza(hesabu)
hesabu = ongeza(hesabu)
print(hesabu)          # 2

And when a list is passed (mutable):

python
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

python
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)

python
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)
python
from math import sqrt, pi
print(sqrt(16), pi)         # 4.0 3.141592653589793

Step 6 — datetime

python
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

python
# 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:

python
import zana

print(zana.mstari())
print("Dola:", zana.tzs_to_usd(52000))
print("Kiwango:", zana.KIWANGO_USD)
print(zana.mstari(20, "-"))

Run main.py:

text
==============================
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

  1. def f(a, b=2) — in f(5), what is b? — Answer: 2.
  2. Does def f(a=1, b): work? — Answer: No: SyntaxError: parameter without a default follows parameter with a default.
  3. What type does *args become inside a function? **kwargs? — Answer: tuple; dict.
  4. Is a variable created inside a function available outside? — Answer: No (it is local).
  5. The difference between import math and from math import sqrt? — Answer: With the first you write math.sqrt(); with the second, sqrt() directly.
  6. 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

  1. Write tengeneza_email(jina, mwisho, kikoa="shule.ac.tz") (create email; first name, last name, domain) that returns jina.mwisho@kikoa in lowercase.
  2. Write wastani(*alama) (average of marks) that returns the average of any number of marks; it should return 0 if there are none.
  3. With random, write tengeneza_password(urefu=8) (urefu = length) that returns a password of random letters and numbers. (Hint: random.choice("abc...xyz0123456789").)
  4. Write siku_hadi(mwaka, mwezi, siku) (days until; year, month, day) that returns the days left from today until that date.
Answer
python
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

  1. Homework review
  2. Explaining the project: what it does, the data structure, the list of functions (plan first!)
  3. Steps 1–2: data + helper functions (daraja, wastani, pata_namba)
  4. Step 3: add a student, show all
  5. Break
  6. Step 4: search, add a mark, delete
  7. Step 5: class statistics + main menu (main)
  8. Month 1 review: quick questions
  9. 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:
    1. Add a student (name + subject marks)
    2. Show all students (table: name, average, grade)
    3. Search for a student by name
    4. Add/change a subject mark
    5. Delete a student
    6. See class statistics (class average, best student, number in each grade)
    7. Exit
  • Data structure: a list of dicts; each student has a dict of marks (subject → mark):

    python
    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 use global). Because a list is mutable, append/remove inside 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 a ValueError. We use try/except here 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().
  • 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_namba politely 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.

python
"""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 the if/elif version (exactly equivalent):

    python
    if chaguo == "1":
        ongeza_mwanafunzi(wanafunzi)
    elif chaguo == "2":
        onyesha_wote(wanafunzi)
    # ... n.k.
    
  • try: ... except ValueError: — "try int(jibu); if the answer isn't a number, instead of the program crashing, do what is under except."

  • is not None / is None — the correct way to check for None.
  • m['alama'].get(somo, '-') — if the subject has no mark yet, show -.

Sample run:

text
========================================
      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:

  1. What is type(input()) always? — str
  2. 7 // 2 and 7 % 2? — 3 and 1
  3. "Python"[::-1]? — "nohtyP"
  4. What does range(2, 8, 2) give? — 2, 4, 6
  5. The difference between a list and a tuple? — list is mutable, tuple is immutable
  6. For looking up a price by product name, which structure? — dict
  7. What does a function with no return return? — None
  8. Why do we use functions? — to reuse code, organise the program, and make fixes easier (DRY)
  9. What error do you get with int("abc")? — ValueError
  10. In the project, why do we pass wanafunzi as a parameter instead of using global? — clearer code, the function stands on its own, easier to test

Questions to ask the student

  1. Why is each student's alama a dict instead of a list? — Answer: So we know which subject each mark belongs to by name (alama["Python"]), not by index.
  2. Why is MASOMO (subjects) a tuple? — Answer: The subjects don't change while the program runs; a tuple prevents accidental changes.
  3. ongeza_mwanafunzi has no return — how does the new student reach the list in main? — Answer: A list is mutable; append inside the function changes the same list that was passed in.
  4. What would happen without pata_namba if the user typed "sabini" (seventy)? — Answer: A ValueError, and the program would crash.
  5. How would you add a new subject "Fizikia" (physics)? — Answer: Add it to the MASOMO tuple (and to the sample data if you want).
  6. 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

  1. 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).
  2. Change tafuta so 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.
  3. Add to takwimu_darasa (class statistics) the number who passed (average ≥ 45) and who failed.
Answer
python
# 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.

python
# 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)
python
# 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
python
# 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}")
python
# 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
python
# 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)
python
# 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

End-of-week checklist

  • Writes a function with def, calls it, and knows defining isn't running
  • Distinguishes parameter from argument
  • Understands return versus print, and that without return → 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, datetime with import and from ... 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)