Week 5: Files, Exceptions and Virtual Environments
Month 2 | Main goal: The student can read and write files (txt, CSV, JSON), handle errors with
try/except, and create a virtual environment with arequirements.txt.
Teacher Preparation (read before the week starts)
- Read:
- Python Tutorial — "Reading and Writing Files": https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
- Python Tutorial — "Errors and Exceptions": https://docs.python.org/3/tutorial/errors.html
venvdocs: https://docs.python.org/3/library/venv.html
- Practise it yourself first: type all the code from the "Type this code live" sections on your first computer BEFORE the session. Do not write code for the first time in front of the student.
- Prepare a folder:
C:\Users\<jina>\Documents\python_kozi\wiki05\and open that folder in VS Code (File → Open Folder). This matters because the files Python creates will show up on the left (Explorer). - Make sure:
python --versionshows 3.12 or higher in PowerShell. Make sure you have internet for Session 3 (pip will download packages). - Tip: On Windows, files can have different encodings. We will use
encoding="utf-8"EVERY time we open a text file — this prevents errors with characters like "é" or emoji.
Week Goals (by the end of the week the student will be able to...)
- Open a file with
open()andwith, and explain the modes"r","w","a". - Read a whole file, read it line by line, and write or append text.
- Use
pathlib.Pathto build paths, check whether a file exists, and read/write quickly. - Read and write CSV with
csv.reader,csv.writer,csv.DictReader,csv.DictWriter. - Save and read data with JSON (
json.dump,json.load,json.dumps,json.loads). - Use
try / except / else / finallyand choose the right exception. - Use
raiseand create custom exceptions. - Create and activate a virtual environment in PowerShell, install a package with
pip, and create arequirements.txt.
Session 1 — Files: open, with, txt and pathlib
Session flow
- Review of the Week 4 homework (modules) + greetings
- Why files?
open(), modes,close() - The
withstatement — live coding: writing and reading a file - Reading line by line,
strip(), mode"a" - Short break
pathlib.Path— paths,exists(),read_text(),write_text()- In-class exercise
- Q&A + homework
Explanation for the teacher (read this first)
Until now, all our programs' data has been lost when the program ends (variables live in RAM). A file is a way of saving data to disk so that it stays even when the computer is turned off.
open(jina, mode, encoding) returns a "file object". The important modes:
"r"— read. This is the default. If the file does not exist →FileNotFoundError."w"— write. It deletes the existing contents! If the file does not exist, it is created."a"— append (add to the end). It does not delete what was there."x"— create; it fails if the file already exists (FileExistsError).
After opening a file you must close it (f.close()), otherwise the data may not be written completely. That is why we use with — a "context manager" — which closes the file by itself, even if an error happens inside.
Ways of reading:
f.read()→ one string containing everything.f.readlines()→ a list of lines (each one has\nat the end).for line in f:→ one line at a time (best for large files).
f.write(text) does not add \n by itself — you have to put it in. (Unlike print.)
A relative path such as "data.txt" is looked up in the current working directory (the folder the terminal is in). In VS Code, when you click "Run" ▶, the terminal may be in a different folder from the script! That is why pathlib and Path(__file__).parent are important: they give us the folder the script itself is in.
pathlib.Path is the modern way of handling paths:
Path("data") / "majina.txt"— the/operator joins the parts of a path (it works on Windows and Linux).p.exists(),p.is_file(),p.name,p.suffix,p.stem,p.parentp.read_text(encoding="utf-8"),p.write_text(text, encoding="utf-8")p.mkdir(parents=True, exist_ok=True)— create a folderPath.cwd()— the current folder;p.glob("*.txt")— search for files
Say this to the student
- "Think of a file as a notebook.
openis opening the notebook. Mode"r"is only reading. Mode"w"is tearing out all the pages and starting again — dangerous! Mode"a"is carrying on writing on the next page." - "
withis like a self-closing door (automatic door). When you leave the room, the door closes itself, even if you were running because of a fire (an error)." - "RAM is the classroom blackboard — once it is wiped, it is gone. Disk is a notebook — you carry it home."
- "
Pathis like a house address: region / district / street / house. We join the parts with/."
Type this code live (share screen)
Step 1: Create the file files_intro.py.
# files_intro.py # Kuandika file kwa njia ya zamani (bila with) f = open("salamu.txt", "w", encoding="utf-8") f.write("Habari za asubuhi!\n") f.write("Karibu kwenye kozi ya Python.\n") f.close() print("Nimeandika file.")
Run it in the terminal: python files_intro.py. Show that salamu.txt has appeared in the VS Code Explorer. Open it.
Step 2: Change it to use with, and read the file.
with open("salamu.txt", "w", encoding="utf-8") as f: f.write("Habari za asubuhi!\n") f.write("Karibu kwenye kozi ya Python.\n") with open("salamu.txt", "r", encoding="utf-8") as f: maudhui = f.read() print(maudhui) print(type(maudhui))
Expected output:
Habari za asubuhi! Karibu kwenye kozi ya Python. <class 'str'>
Ask: "Why is there an empty line after the text?" (Answer: the final \n in the file + print adds its own \n.)
Step 3: Mode "a" and reading line by line.
with open("majina.txt", "w", encoding="utf-8") as f: f.write("Asha\n") f.write("Baraka\n") with open("majina.txt", "a", encoding="utf-8") as f: f.write("Neema\n") with open("majina.txt", encoding="utf-8") as f: for namba, mstari in enumerate(f, start=1): print(namba, mstari.strip())
Output:
1 Asha 2 Baraka 3 Neema
Show what happens without .strip() (empty lines in between) — print(repr(mstari)) shows 'Asha\n'.
Step 4: Writing a list in one go.
matunda = ["embe", "nanasi", "papai"] with open("matunda.txt", "w", encoding="utf-8") as f: for tunda in matunda: f.write(tunda + "\n") # au: f.write("\n".join(matunda) + "\n")
Step 5: pathlib.
from pathlib import Path folder_la_script = Path(__file__).parent print("Script iko:", folder_la_script) print("Terminal iko:", Path.cwd()) data = folder_la_script / "data" data.mkdir(exist_ok=True) faili = data / "kumbukumbu.txt" faili.write_text("Leo tumejifunza files.\n", encoding="utf-8") print(faili.exists()) # True print(faili.name) # kumbukumbu.txt print(faili.stem) # kumbukumbu print(faili.suffix) # .txt print(faili.read_text(encoding="utf-8")) for p in folder_la_script.glob("*.txt"): print("Nimeona:", p.name)
Explain: write_text opens, writes and closes — in one line. It is convenient for small files.
Step 6: Show an error on purpose:
with open("halipo.txt", encoding="utf-8") as f: print(f.read())
Output: FileNotFoundError: [Errno 2] No such file or directory: 'halipo.txt' — say: "In Session 2 we will learn to handle errors like this. For now we can check first with Path.exists()."
Questions to ask the student
- What does mode
"w"do if the file already has text in it? — It deletes everything and starts again. - Why do we prefer
with? — It closes the file by itself, even if an error happens. - What will
f.write("Asha")twice put in the file? —AshaAsha(no automatic\n). - What type of data does
f.read()return? —str. - What is the difference between
Path.cwd()andPath(__file__).parent? — cwd is the terminal's folder; the__file__parent is the script's folder. - How do you add a new name without deleting the old names? — Mode
"a".
Common mistakes
| Mistake | Error message | Fix |
|---|---|---|
| The file does not exist or is in another folder | FileNotFoundError: [Errno 2] No such file or directory: 'data.txt' |
Check the name; use Path(__file__).parent / "data.txt"; make sure the terminal is in the right folder (cd). |
| Writing something that is not a string | TypeError: write() argument must be str, not int |
f.write(str(namba)) or an f-string. |
Reading a file opened with "w" |
io.UnsupportedOperation: not readable |
Open it with "r". |
Forgetting encoding (strange characters like é) or UnicodeDecodeError: 'charmap' codec can't decode... |
— | Add encoding="utf-8". |
Backslash in a Windows path: "C:\new\test.txt" (\n is a newline!) |
Invalid path / OSError |
Use r"C:\new\test.txt", or /, or Path. |
Using f outside the with |
ValueError: I/O operation on closed file. |
Do all the file work inside the with block. |
In-class exercise
Exercise 1.1: Write a program that asks the user for 3 names (with input) and saves them to wanafunzi.txt, each name on its own line.
Exercise 1.2: Write a program that reads wanafunzi.txt and prints the number of names and the longest name.
Exercise 1.3: Create a "diary" (shajara): each time the program is run, it asks for one message and adds it to the end of shajara.txt together with the date (use from datetime import date).
Answer
# 1.1 with open("wanafunzi.txt", "w", encoding="utf-8") as f: for i in range(3): jina = input(f"Jina la mwanafunzi {i + 1}: ") f.write(jina + "\n") # 1.2 with open("wanafunzi.txt", encoding="utf-8") as f: majina = [mstari.strip() for mstari in f if mstari.strip()] # (Kama comprehension bado ni mpya, tumia loop ya kawaida na append) print("Idadi:", len(majina)) print("Jina refu zaidi:", max(majina, key=len)) # 1.3 from datetime import date from pathlib import Path shajara = Path(__file__).parent / "shajara.txt" ujumbe = input("Andika ujumbe wa leo: ") with open(shajara, "a", encoding="utf-8") as f: f.write(f"{date.today()}: {ujumbe}\n") print(shajara.read_text(encoding="utf-8"))
Note: max(..., key=len) is new — if the student does not know it, let them use a loop:
refu = "" for jina in majina: if len(jina) > len(refu): refu = jina
Session 2 — CSV, JSON and Exceptions
Session flow
- Review of the homework (word counter)
- CSV:
csv.writer,csv.reader,DictWriter,DictReader - JSON:
dump/load,dumps/loads - Break
- Exceptions:
try/except/else/finally - In-class exercise
- Q&A + homework
Explanation for the teacher (read this first)
CSV (Comma-Separated Values) is a text file containing a table: each line is a row, and the columns are separated by commas. Excel can open it. Why not just use split(",")? Because a value can contain a comma (e.g. "Dar es Salaam, Tanzania") — the csv module handles that with quotes.
Important on Windows: open a CSV with newline="" when writing, otherwise you will get empty lines between rows.
csv.writer(f)→writer.writerow([...]),writer.writerows([[...], [...]])csv.reader(f)→ each row is a list of strings (even numbers are strings! you must useint()/float()).csv.DictWriter(f, fieldnames=[...])→writeheader(),writerow({...})csv.DictReader(f)→ each row is a dict; the first line is used as the keys.
JSON (JavaScript Object Notation) is a format for saving structured data (dict, list) — it is used by web APIs everywhere. The type mapping:
| Python | JSON |
|--------|------|
| dict | object {} |
| list, tuple | array [] |
| str | string (must use double ") |
| int, float | number |
| True/False | true/false |
| None | null |
json.dump(data, f)— write to a file.json.load(f)— read from a file.json.dumps(data)— convert to a string.json.loads(text)— string → Python.- (Remember: "s" = string.) Use
indent=2for easy reading, andensure_ascii=Falseto store characters like "é" directly. - Dict keys always become strings in JSON:
{1: "a"}→{"1": "a"}. A set cannot be JSON (TypeError).
Exceptions: When an error happens while a program is running, Python "raises" an exception. If it is not caught, the program stops and shows a traceback. try/except lets us catch it.
try: -> code inayoweza kuleta kosa except X: -> inaendeshwa kama kosa la aina X limetokea else: -> inaendeshwa kama HAKUNA kosa finally: -> inaendeshwa DAIMA (kosa au la) — kwa usafi (cleanup)
Rule: catch specific exceptions (ValueError, FileNotFoundError), not a bare except: — that hides bugs and even Ctrl+C. except Exception as e: is acceptable as a last line of defence, if you print e.
Common exceptions: ValueError (int("abc")), ZeroDivisionError, KeyError (d["haipo"]), IndexError, TypeError ("a" + 1), FileNotFoundError, json.JSONDecodeError (broken JSON; it is a kind of ValueError).
Say this to the student
- "CSV is like an attendance register with rows and columns. JSON is like a form with named sections: 'Name: ...', 'Age: ...', and forms inside forms."
- "
try/exceptis like a goalkeeper. You try something; when the ball (the error) comes, the goalkeeper (except) catches it instead of a goal being scored (the program dying)." - "
finallyis like turning off the lights when you leave the office — you do it every day, whether work went well or badly." - "
elseis a reward: it runs only if everything went safely."
Type this code live (share screen)
Step 1: CSV writer/reader — csv_demo.py
import csv wanafunzi = [ ["jina", "umri", "mji"], ["Asha", 17, "Arusha"], ["Baraka", 18, "Dar es Salaam, Kinondoni"], ["Neema", 16, "Mwanza"], ] with open("wanafunzi.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerows(wanafunzi) with open("wanafunzi.csv", newline="", encoding="utf-8") as f: reader = csv.reader(f) kichwa = next(reader) # ruka mstari wa kwanza (header) print("Header:", kichwa) for row in reader: print(row)
Output:
Header: ['jina', 'umri', 'mji'] ['Asha', '17', 'Arusha'] ['Baraka', '18', 'Dar es Salaam, Kinondoni'] ['Neema', '16', 'Mwanza']
Open wanafunzi.csv and show: Baraka,18,"Dar es Salaam, Kinondoni" — quotes have been added. Show that '17' is a string.
Step 2: DictReader and a calculation
import csv with open("wanafunzi.csv", newline="", encoding="utf-8") as f: reader = csv.DictReader(f) jumla = 0 idadi = 0 for row in reader: print(row["jina"], "anaishi", row["mji"]) jumla += int(row["umri"]) idadi += 1 print("Wastani wa umri:", jumla / idadi)
Final output: Wastani wa umri: 17.0
Step 3: DictWriter
import csv bidhaa = [ {"jina": "Sukari", "bei": 3000, "idadi": 20}, {"jina": "Mchele", "bei": 2500, "idadi": 50}, ] with open("bidhaa.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["jina", "bei", "idadi"]) writer.writeheader() writer.writerows(bidhaa)
Step 4: JSON — json_demo.py
import json mwanafunzi = { "jina": "Asha", "umri": 17, "masomo": ["Python", "Hisabati"], "amelipa": True, "simu": None, } with open("mwanafunzi.json", "w", encoding="utf-8") as f: json.dump(mwanafunzi, f, indent=2, ensure_ascii=False) with open("mwanafunzi.json", encoding="utf-8") as f: data = json.load(f) print(data["masomo"][0]) # Python print(type(data)) # <class 'dict'> maandishi = json.dumps(mwanafunzi) print(maandishi) print(type(maandishi)) # <class 'str'> nyuma = json.loads('{"a": 1, "b": [true, null]}') print(nyuma) # {'a': 1, 'b': [True, None]}
Open mwanafunzi.json and show true, null and the double quotes.
Step 5: Exceptions — makosa_demo.py. First show the program dying:
umri = int(input("Umri wako: ")) # andika "kumi"
ValueError: invalid literal for int() with base 10: 'kumi'. Teach how to read a traceback: read the last line first (the error type and message), then the line with line N.
Then:
try: umri = int(input("Umri wako: ")) except ValueError: print("Tafadhali andika namba, mfano 17.") else: print(f"Mwakani utakuwa na miaka {umri + 1}.") finally: print("Asante kwa kutumia programu.")
Run it twice: 17 and kumi (ten, as a word).
Step 6: Multiple excepts and as e
def gawanya(): try: a = int(input("Namba ya kwanza: ")) b = int(input("Namba ya pili: ")) jibu = a / b except ValueError: print("Hiyo si namba.") except ZeroDivisionError as e: print("Huwezi kugawanya kwa sifuri:", e) else: print("Jibu:", jibu) gawanya()
Step 7: Loop until the user types something valid (a very important pattern):
while True: try: namba = int(input("Andika namba kati ya 1 na 10: ")) break except ValueError: print("Si namba, jaribu tena.") print("Umeandika", namba)
Step 8: File + JSON + exception together
import json from pathlib import Path faili = Path("mipangilio.json") try: with open(faili, encoding="utf-8") as f: mipangilio = json.load(f) except FileNotFoundError: print("Hakuna mipangilio, natumia default.") mipangilio = {"lugha": "sw", "rangi": "bluu"} except json.JSONDecodeError: print("File la JSON limeharibika!") mipangilio = {} print(mipangilio)
Show: write {lugha: sw (broken JSON) in mipangilio.json and then run it again → "File la JSON limeharibika!" (the JSON file is corrupted).
Questions to ask the student
- Does
csv.readerreturn numbers as int or string? — Always string; you have to convert them. - Why
newline=""? — To prevent empty lines between rows on Windows. - What is the difference between
json.dumpandjson.dumps? —dumpwrites to a file;dumpsreturns a string. - What does Python's
Truebecome in JSON? —true.None→null. - When does
elseon atryrun? — If no exception happened inside thetry. - When does
finallyrun? — Always. - Why is a bare
except:bad? — It catches everything, even coding mistakes (bugs) and Ctrl+C, so you do not know what went wrong.
Common mistakes
| Mistake | Error message | Fix |
|---|---|---|
| Forgetting to convert a CSV string | TypeError: unsupported operand type(s) for +=: 'int' and 'str' |
int(row["umri"]) |
| Wrong key in DictReader (e.g. a space or capital letters) | KeyError: 'Jina' |
Print reader.fieldnames to see the actual keys. |
| Empty lines in a CSV (Windows) | — | newline="" |
| Saving a set or object to JSON | TypeError: Object of type set is not JSON serializable |
Convert it to a list: list(s). |
JSON with single quotes {'a': 1} |
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes |
JSON needs double ". |
json.load("file.json") (passing the name instead of a file object) |
AttributeError: 'str' object has no attribute 'read' |
Open it first with with open(...) as f: json.load(f) |
except ValueError is in place but the error is ZeroDivisionError |
The program still dies | Add an except for that type. |
A variable created inside try is used after an error |
NameError: name 'umri' is not defined |
Use it inside else, or give it a default value before the try. |
In-class exercise
Exercise 2.1: Create alama.csv with columns jina,alama (name, marks) for 4 students (with csv.writer). Then read the file and print who got the highest mark and the average.
Exercise 2.2: Write a function hifadhi_mawasiliano(jina, simu) (save contact) that adds a person to mawasiliano.json (a dict of name → phone). If the file does not exist, start with an empty dict.
Exercise 2.3: Write a program that asks for a file name and prints the number of lines. If the file does not exist, say "File halipo" (file not found) instead of dying.
Answer
# 2.1 import csv with open("alama.csv", "w", newline="", encoding="utf-8") as f: w = csv.writer(f) w.writerow(["jina", "alama"]) w.writerows([["Asha", 88], ["Baraka", 74], ["Neema", 93], ["Juma", 65]]) bora_jina = None bora_alama = -1 jumla = 0 idadi = 0 with open("alama.csv", newline="", encoding="utf-8") as f: for row in csv.DictReader(f): alama = int(row["alama"]) jumla += alama idadi += 1 if alama > bora_alama: bora_alama = alama bora_jina = row["jina"] print(f"Bora: {bora_jina} ({bora_alama})") print(f"Wastani: {jumla / idadi:.1f}") # Bora: Neema (93) # Wastani: 80.0 # 2.2 import json from pathlib import Path FAILI = Path("mawasiliano.json") def hifadhi_mawasiliano(jina, simu): try: with open(FAILI, encoding="utf-8") as f: data = json.load(f) except FileNotFoundError: data = {} data[jina] = simu with open(FAILI, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) hifadhi_mawasiliano("Asha", "0712345678") hifadhi_mawasiliano("Juma", "0755111222") print(FAILI.read_text(encoding="utf-8")) # 2.3 jina = input("Jina la file: ") try: with open(jina, encoding="utf-8") as f: mistari = f.readlines() except FileNotFoundError: print("File halipo") else: print("Idadi ya mistari:", len(mistari))
Session 3 — raise, Custom Exceptions, venv and pip
Session flow
- Review of the homework (CSV/JSON)
raiseand custom exceptions- Virtual environment: why,
python -m venv, activate, execution policy - Break
pip install,pip list,pip freeze > requirements.txt,pip install -r- In-class exercise (small project: expenses book + the
richpackage) - Q&A + homework + summary of the week
Explanation for the teacher (read this first)
raise: Until now, Python has been the one raising exceptions. We can too: raise ValueError("Umri hauwezi kuwa hasi") (age cannot be negative). This is a function's way of saying "I have been given bad data, I cannot continue" — instead of quietly returning a strange value. The caller of the function decides what to do (with try/except).
Custom exceptions: we create our own type of error by inheriting from Exception:
class SalioHalitoshi(Exception): pass
(The student has not studied classes yet — that is Week 6! Just say: "this is a way of creating a new type of error; we will fully understand class next week." It is fine to use it as a "template".) The benefit: we can catch just our own errors, without mixing them up with ordinary ValueErrors.
raise with nothing after it inside an except raises the same error again (re-raise). raise NewError(...) from e links errors — just mention it briefly.
Virtual environment (venv): A folder holding a "private copy" of Python and packages for one project. Why? Project A needs requests 2.28, project B needs requests 2.32. Without a venv, all packages are installed in one place (global) and they clash. Each project → its own venv.
Steps in PowerShell (inside the project folder):
python -m venv .venv # tengeneza venv kwenye folder .venv .\.venv\Scripts\Activate.ps1 # activate
After activating, the prompt starts with (.venv). Now python and pip are the venv's. deactivate to leave.
A well-known Windows problem: activating can produce:
.\.venv\Scripts\Activate.ps1 : File C:\...\Activate.ps1 cannot be loaded because running scripts is disabled on this system.
This is because PowerShell blocks scripts by default. The fix (once only, for your user, does not need Administrator):
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Answer Y if it asks. A temporary alternative (for this terminal only): Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process.
VS Code: after creating .venv, press Ctrl+Shift+P → "Python: Select Interpreter" → choose the one with .venv. New VS Code terminals will activate the venv by themselves.
pip is the program for installing packages from PyPI (pypi.org). Use python -m pip ... — it makes sure pip belongs to the Python you are using.
python -m pip install rich— installpython -m pip list— listpython -m pip show rich— informationpython -m pip uninstall richpython -m pip freeze > requirements.txt— save the list of packages and versionspython -m pip install -r requirements.txt— install them all (e.g. on another computer)
Note: in Windows PowerShell 5.1, > writes the file as UTF-16, which pip install -r still reads fine in recent versions; but if there is a problem, use python -m pip freeze | Out-File -Encoding utf8 requirements.txt.
The .venv folder is not sent to others or to GitHub (Week 8: .gitignore); we only send requirements.txt.
Say this to the student
- "
raiseis like a referee blowing the whistle: 'Foul! Stop the game.' The function says 'this data is not valid!'" - "A custom exception is like making your own card — not the usual 'yellow card' but a 'SalioHalitoshi card' (insufficient balance). You can catch exactly that one."
- "A virtual environment is like each tradesman's toolbox. The electrician and the plumber do not mix their tools. Each project has its own box."
- "
requirements.txtis like a shopping list: you give someone the list, not the whole shop. They buy the items (pip install -r) themselves."
Type this code live (share screen)
Step 1: raise — raise_demo.py
def weka_umri(umri): if not isinstance(umri, int): raise TypeError("Umri lazima uwe namba kamili (int).") if umri < 0 or umri > 130: raise ValueError(f"Umri {umri} si sahihi.") return umri print(weka_umri(20)) try: weka_umri(-5) except ValueError as e: print("Kosa:", e) try: weka_umri("ishirini") except TypeError as e: print("Kosa:", e)
Output:
20 Kosa: Umri -5 si sahihi. Kosa: Umri lazima uwe namba kamili (int).
Step 2: Custom exception — benki_mini.py
class SalioHalitoshi(Exception): """Inarushwa pale mtu anapotoa pesa zaidi ya salio.""" pass class KiasiSiSahihi(Exception): pass def toa_pesa(salio, kiasi): if kiasi <= 0: raise KiasiSiSahihi("Kiasi lazima kiwe zaidi ya sifuri.") if kiasi > salio: raise SalioHalitoshi(f"Salio ni {salio}, umeomba {kiasi}.") return salio - kiasi salio = 10_000 for ombi in [3_000, 20_000, -50]: try: salio = toa_pesa(salio, ombi) except SalioHalitoshi as e: print("Samahani:", e) except KiasiSiSahihi as e: print("Kosa:", e) else: print(f"Umetoa {ombi}. Salio jipya: {salio}")
Output:
Umetoa 3000. Salio jipya: 7000 Samahani: Salio ni 7000, umeomba 20000. Kosa: Kiasi lazima kiwe zaidi ya sifuri.
Explain 10_000 — the underscore is only for easier reading, it is the same as 10000.
Step 3: venv — in the VS Code terminal (PowerShell). Create a new folder:
mkdir mradi_venv cd mradi_venv python -m venv .venv .\.venv\Scripts\Activate.ps1
If the "running scripts is disabled" error occurs — this is a good teaching opportunity:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser .\.venv\Scripts\Activate.ps1
Confirm:
python -c "import sys; print(sys.prefix)"
The output should end with ...\mradi_venv\.venv. Compare after deactivate — it will show the global Python's folder.
Step 4: pip
python -m pip install rich python -m pip list python -m pip freeze > requirements.txt Get-Content requirements.txt
requirements.txt will contain something like:
markdown-it-py==4.0.0 mdurl==0.1.2 Pygments==2.19.2 rich==14.2.0
(Versions may differ — that is not a problem.) Explain: rich needs other packages (dependencies), which is why there are four.
Create rangi.py inside mradi_venv:
from rich import print from rich.table import Table print("[bold green]Habari![/bold green] Karibu kwenye [red]Python[/red].") jedwali = Table(title="Wanafunzi") jedwali.add_column("Jina") jedwali.add_column("Alama", justify="right") jedwali.add_row("Asha", "88") jedwali.add_row("Baraka", "74") print(jedwali)
Run python rangi.py. Then deactivate and run it again → ModuleNotFoundError: No module named 'rich' (if rich is not installed globally). This proves the venv is working.
Step 5: Recreating the environment (simulating another computer):
deactivate Remove-Item -Recurse -Force .venv python -m venv .venv .\.venv\Scripts\Activate.ps1 python -m pip install -r requirements.txt python rangi.py
Questions to ask the student
- Why should a function use
raiseinstead ofprint("kosa")? — Print neither stops the program nor informs the caller;raiseforces the caller to deal with the error. - What does a custom exception inherit from? —
Exception. - How can you tell a venv is active? — The prompt has
(.venv)at the start;sys.prefixshows the venv folder. - Why not send the
.venvfolder to a friend? — It is large and contains paths from your computer; they create their own fromrequirements.txt. - Which command installs all the packages from requirements.txt? —
python -m pip install -r requirements.txt - How do you solve the "running scripts is disabled" error? —
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Common mistakes
| Mistake | Error message | Fix |
|---|---|---|
| Execution policy | Activate.ps1 cannot be loaded because running scripts is disabled on this system |
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser |
Typing source .venv/bin/activate (a Linux/Mac command) |
source : The term 'source' is not recognized... |
Windows: .\.venv\Scripts\Activate.ps1 |
| Installing without activating the venv | The package goes global; inside the venv ModuleNotFoundError |
Activate first, then python -m pip install. |
| VS Code is using a different interpreter | Import "rich" could not be resolved (yellow underline) |
Ctrl+Shift+P → Python: Select Interpreter → .venv |
pip is not recognised |
pip : The term 'pip' is not recognized... |
Use python -m pip |
Naming your own file json.py or csv.py |
AttributeError: module 'json' has no attribute 'dump' |
Rename your file; delete __pycache__. |
raise "kosa" (a string) |
TypeError: exceptions must derive from BaseException |
raise ValueError("kosa") |
In-class exercise
Exercise 3.1: Write a function angalia_alama(alama) (check marks) that raises ValueError if the mark is not between 0 and 100, and otherwise returns a grade ("A" ≥ 80, "B" ≥ 65, "C" ≥ 50, "F" otherwise). Test it with 85, 105, 40.
Exercise 3.2: Create a custom exception BidhaaHaipo (product not found). Write a function bei_ya(bidhaa, orodha) (price of) that raises BidhaaHaipo if the product is not in the price dict.
Exercise 3.3 (venv): Create a folder matumizi (expenses), a venv inside it, install rich, and create requirements.txt. Write a program matumizi.py that reads matumizi.csv (columns kitu,kiasi — item, amount) and shows a rich table together with the total. If a line has an amount that is not a number, skip it and show a warning.
Answer
# 3.1 def angalia_alama(alama): if not 0 <= alama <= 100: raise ValueError(f"Alama {alama} lazima iwe kati ya 0 na 100.") if alama >= 80: return "A" elif alama >= 65: return "B" elif alama >= 50: return "C" return "F" for a in [85, 105, 40]: try: print(a, "->", angalia_alama(a)) except ValueError as e: print("Kosa:", e) # 85 -> A # Kosa: Alama 105 lazima iwe kati ya 0 na 100. # 40 -> F # 3.2 class BidhaaHaipo(Exception): pass def bei_ya(bidhaa, orodha): if bidhaa not in orodha: raise BidhaaHaipo(f"'{bidhaa}' haipo dukani.") return orodha[bidhaa] bei = {"sukari": 3000, "chumvi": 800} try: print(bei_ya("sukari", bei)) print(bei_ya("unga", bei)) except BidhaaHaipo as e: print("Kosa:", e) # 3000 # Kosa: 'unga' haipo dukani.
3.3 — PowerShell commands:
mkdir matumizi; cd matumizi python -m venv .venv .\.venv\Scripts\Activate.ps1 python -m pip install rich python -m pip freeze > requirements.txt
matumizi.csv:
kitu,kiasi nauli,2000 chakula,5000 vocha,abc maji,1000
matumizi.py:
import csv from pathlib import Path from rich import print from rich.table import Table faili = Path(__file__).parent / "matumizi.csv" jedwali = Table(title="Matumizi ya Leo") jedwali.add_column("Kitu") jedwali.add_column("Kiasi (TSh)", justify="right") jumla = 0 try: with open(faili, newline="", encoding="utf-8") as f: for row in csv.DictReader(f): try: kiasi = int(row["kiasi"]) except ValueError: print(f"[yellow]Onyo:[/yellow] '{row['kiasi']}' si namba, nimeruka '{row['kitu']}'.") continue jumla += kiasi jedwali.add_row(row["kitu"], f"{kiasi:,}") except FileNotFoundError: print("[red]matumizi.csv halipo![/red]") else: jedwali.add_row("[bold]JUMLA[/bold]", f"[bold]{jumla:,}[/bold]") print(jedwali)
Homework
(Due: next session)
Assignment 1 — Word counter (after Session 1): Write a program that reads a text file (hadithi.txt — the student should write 2–3 paragraphs in Swahili) and prints: the number of lines, the number of words, and the most frequently used word (ignoring upper/lower case).
Assignment 2 — Marks book (after Session 2): A program with a menu: 1. Ongeza mwanafunzi (add student), 2. Onyesha wote (show all), 3. Toka (exit). The data should be saved in alama.json (a list of dicts {"jina":..., "alama":...}) so that it is not lost when the program closes. A mark that is not a number must not cause a crash.
Assignment 3 — Export (after Session 3): Write a function json_kwenda_csv(json_path, csv_path) that converts the alama.json from Assignment 2 into alama.csv. Raise a custom exception FailiTupu (empty file) if the JSON has an empty list. Put all the assignments inside a folder with a .venv and a requirements.txt (even if it is empty).
Answer
# Kazi 1 from pathlib import Path maandishi = Path("hadithi.txt").read_text(encoding="utf-8") mistari = maandishi.splitlines() maneno = maandishi.lower().split() hesabu = {} for neno in maneno: neno = neno.strip(".,!?;:\"'()") if neno: hesabu[neno] = hesabu.get(neno, 0) + 1 print("Mistari:", len(mistari)) print("Maneno:", len(maneno)) if hesabu: neno_bora = max(hesabu, key=hesabu.get) print(f"Neno linalorudiwa zaidi: '{neno_bora}' mara {hesabu[neno_bora]}")
# Kazi 2 import json from pathlib import Path FAILI = Path(__file__).parent / "alama.json" def pakia(): try: with open(FAILI, encoding="utf-8") as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return [] def hifadhi(wanafunzi): with open(FAILI, "w", encoding="utf-8") as f: json.dump(wanafunzi, f, indent=2, ensure_ascii=False) wanafunzi = pakia() while True: print("\n1. Ongeza mwanafunzi\n2. Onyesha wote\n3. Toka") chaguo = input("Chagua: ") if chaguo == "1": jina = input("Jina: ").strip() try: alama = int(input("Alama: ")) except ValueError: print("Alama lazima iwe namba.") continue wanafunzi.append({"jina": jina, "alama": alama}) hifadhi(wanafunzi) print("Imehifadhiwa.") elif chaguo == "2": if not wanafunzi: print("Hakuna mwanafunzi bado.") for w in wanafunzi: print(f"{w['jina']:<15}{w['alama']:>5}") elif chaguo == "3": print("Kwaheri!") break else: print("Chaguo si sahihi.")
# Kazi 3 import csv import json class FailiTupu(Exception): pass def json_kwenda_csv(json_path, csv_path): with open(json_path, encoding="utf-8") as f: data = json.load(f) if not data: raise FailiTupu(f"{json_path} haina data.") with open(csv_path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["jina", "alama"]) writer.writeheader() writer.writerows(data) return len(data) try: n = json_kwenda_csv("alama.json", "alama.csv") print(f"Nimehamisha wanafunzi {n}.") except FileNotFoundError: print("alama.json halipo.") except FailiTupu as e: print("Kosa:", e)
If you are asked a question you don't know
- Say honestly: "Good question — let's look into it together," then try it in the terminal (the
pythonREPL) or search: "pythonsite:docs.python.org" (replacing <swali>with the question) or Stack Overflow. You can also typehelp(open)orhelp(json.dump)in the REPL. - Docs: files https://docs.python.org/3/tutorial/inputoutput.html |
csvhttps://docs.python.org/3/library/csv.html |jsonhttps://docs.python.org/3/library/json.html |pathlibhttps://docs.python.org/3/library/pathlib.html | exceptions https://docs.python.org/3/library/exceptions.html | venv https://docs.python.org/3/library/venv.html | pip https://pip.pypa.io/en/stable/user_guide/ - If you did not find the answer in class, write it down and answer it at the start of the next session.
End-of-week checklist
- Explains the difference between
"r","w","a"without looking at notes. - Uses
with open(..., encoding="utf-8")out of habit. - Reads a file line by line and uses
.strip(). - Uses
Path(__file__).parent / "jina"andexists(). - Reads/writes CSV with
DictReader/DictWriterand remembersnewline="". - Saves and reads a dict/list with JSON.
- Writes
try/except/else/finallyand catches specific exceptions. - Reads a traceback (last line first).
- Uses
raiseand creates a custom exception. - Creates and activates
.venvin PowerShell without help. - Uses
pip install,pip freeze > requirements.txt,pip install -r. - The Week 5 homework has been handed in.