Yaliyomo

Wiki 8: Advanced Python, Git & GitHub, na Mradi wa Mwezi 2

Mwezi 2 | Lengo kuu: Mwanafunzi aandike Python ya "kisasa" (comprehensions, lambda, generators, decorators), atumie Git & GitHub, na akamilishe Mradi wa Mwezi 2: "Mfumo wa Maktaba" uliohifadhiwa GitHub.

Maandalizi ya Mwalimu (soma kabla ya wiki kuanza)

Malengo ya Wiki (mwisho wa wiki mwanafunzi ataweza...)

  1. Kuandika list, dict na set comprehensions (pamoja na if).
  2. Kutumia lambda pamoja na sorted(key=...), map, filter, max/min(key=...).
  3. Kueleza iterator ni nini (iter, next, StopIteration) na kuandika generator kwa yield.
  4. Kuandika decorator rahisi (pamoja na *args, **kwargs na functools.wraps).
  5. Kutumia Git: init, status, add, commit, log, .gitignore.
  6. Kutumia GitHub: kutengeneza repo, remote add, push.
  7. Kujenga na kupakia GitHub mradi wa maktaba unaotumia OOP + JSON + exceptions.

Kipindi 1 — Comprehensions, lambda, map/filter/sorted

Mtiririko wa kipindi

  1. Marudio ya kazi ya nyumbani ya Wiki 7 (hasa Rafu — tutaihitaji Kipindi 3)
  2. List comprehensions (+ if, + if/else)
  3. Dict na set comprehensions; generator expression ndani ya sum()
  4. Mapumziko
  5. lambda
  6. sorted(key=...), max/min(key=...), map, filter
  7. Zoezi darasani
  8. Q&A + kazi ya nyumbani

Elezo kwa mwalimu (soma kwanza wewe)

List comprehension ni njia fupi ya kutengeneza list mpya kutoka iterable:

text
[KITU for kipengele in iterable]
[KITU for kipengele in iterable if SHARTI]          # chuja
[A if SHARTI else B for kipengele in iterable]      # badilisha kwa masharti

Ni sawa kabisa na loop ya for + append. Kanuni ya kusoma: anza na for, kisha if, mwisho angalia kitu cha mwanzo ("kwa kila x kwenye namba, kama x ni shufwa, chukua x*x").

Tahadhari ya mpangilio: if ya kuchuja inakuja mwishoni; if/else ya kubadilisha inakuja mwanzoni (kabla ya for). [x for x in n if x > 0 else 0] ni SyntaxError.

  • Dict comprehension: {k: v for ... in ...} → dict.
  • Set comprehension: {x for ... in ...} → set (bila marudio).
  • Generator expression: (x for ...) — haitengenezi list nzima; inatoa kimoja kimoja. Ndani ya function: sum(x * x for x in namba) (bila mabano ya ziada).
  • Usitumie comprehension ndefu sana — kama inazidi mstari mmoja na inakuwa ngumu kusoma, rudi kwenye loop ya kawaida.

lambda ni function ndogo isiyo na jina, yenye expression moja (hakuna return, hakuna mistari mingi):

python
mraba = lambda x: x * x      # sawa na def mraba(x): return x * x

Kwa kawaida hatuiweki kwenye variable (PEP 8 inasema tumia def); tunaitumia papo hapo kama argument kwa sorted, max, map, filter.

sorted(iterable, key=f, reverse=False) — key ni function inayoitwa kwa kila kipengele, na kupanga kunafanywa kwa thamani inayorudishwa. Mf. sorted(maneno, key=len). Pia list.sort(key=...) (inabadilisha list yenyewe, inarudisha None). max(..., key=...) na min(..., key=...) vivyo hivyo.

map(f, iterable) — inaita f kwa kila kipengele. filter(f, iterable) — inabaki na vipengele ambavyo f inarudisha True. Zote zinarudisha iterator (si list) — lazima list(...) kuona matokeo. Kwa Python ya kisasa, comprehensions mara nyingi zinasomeka vizuri zaidi kuliko map/filter — lakini lazima uzijue kwa sababu utaziona.

Sema hivi kwa mwanafunzi

  • "Comprehension ni kama kumwambia msaidizi kwa sentensi moja: 'Kwa kila mwanafunzi darasani, kama amefaulu, niletee jina lake.' Badala ya kumpa maagizo hatua kwa hatua."
  • "lambda ni kama kuandika kwenye kikaratasi kidogo (sticky note) — maagizo mafupi ya mara moja, hayahitaji daftari zima (def)."
  • "key= ni kumwambia sorted 'panga kwa nini': kwa urefu? kwa bei? kwa umri? Ni kama kupanga wanafunzi mstarini kwa urefu wao badala ya kwa majina."

Andika code hii live (share screen)

Hatua 1: Loop vs comprehension — comprehensions.py

python
namba = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Njia ya zamani
mraba = []
for n in namba:
    mraba.append(n * n)
print(mraba)

# Comprehension
mraba = [n * n for n in namba]
print(mraba)

shufwa = [n for n in namba if n % 2 == 0]
print(shufwa)

hali = ["shufwa" if n % 2 == 0 else "witiri" for n in namba[:4]]
print(hali)

Output:

text
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[2, 4, 6, 8, 10]
['witiri', 'shufwa', 'witiri', 'shufwa']

Hatua 2: Mifano halisi

python
majina = ["  asha ", "JUMA", "neema  ", ""]
safi = [j.strip().title() for j in majina if j.strip()]
print(safi)                                  # ['Asha', 'Juma', 'Neema']

bei_usd = [10, 25, 40]
bei_tzs = [b * 2600 for b in bei_usd]
print(bei_tzs)                               # [26000, 65000, 104000]

maandishi = "python ni rahisi na python ni nzuri"
herufi_za_kwanza = {neno[0] for neno in maandishi.split()}
print(sorted(herufi_za_kwanza))              # ['n', 'p', 'r']

urefu = {neno: len(neno) for neno in ["embe", "nanasi", "papai"]}
print(urefu)                                 # {'embe': 4, 'nanasi': 6, 'papai': 5}

alama = {"Asha": 88, "Juma": 42, "Neema": 67}
waliofaulu = {jina: a for jina, a in alama.items() if a >= 50}
print(waliofaulu)                            # {'Asha': 88, 'Neema': 67}

print(sum(a for a in alama.values()))        # 197  (generator expression)

jedwali = [[i * j for j in range(1, 4)] for i in range(1, 4)]
print(jedwali)                               # [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

Hatua 3: lambda + sorted — lambda_demo.py

python
def mraba(x):
    return x * x

mraba2 = lambda x: x * x
print(mraba(5), mraba2(5))          # 25 25

maneno = ["nanasi", "embe", "papai", "ndizi"]
print(sorted(maneno))                       # kwa alfabeti
print(sorted(maneno, key=len))              # kwa urefu
print(sorted(maneno, key=len, reverse=True))

wanafunzi = [("Asha", 88), ("Juma", 42), ("Neema", 67)]
print(sorted(wanafunzi, key=lambda w: w[1], reverse=True))
print(max(wanafunzi, key=lambda w: w[1]))

bidhaa = [
    {"jina": "Sukari", "bei": 3000},
    {"jina": "Chumvi", "bei": 800},
    {"jina": "Mafuta", "bei": 7500},
]
nafuu_kwanza = sorted(bidhaa, key=lambda b: b["bei"])
print([b["jina"] for b in nafuu_kwanza])

Output:

text
25 25
['embe', 'nanasi', 'ndizi', 'papai']
['embe', 'papai', 'ndizi', 'nanasi']
['nanasi', 'papai', 'ndizi', 'embe']
[('Asha', 88), ('Neema', 67), ('Juma', 42)]
('Asha', 88)
['Chumvi', 'Sukari', 'Mafuta']

Eleza: sorted ni "stable" — maneno yenye urefu sawa (papai, ndizi) yanabaki kwa mpangilio wa awali.

Hatua 4: map na filter

python
namba = [1, 2, 3, 4, 5]
print(map(lambda x: x * 10, namba))              # <map object at 0x...>
print(list(map(lambda x: x * 10, namba)))        # [10, 20, 30, 40, 50]
print(list(filter(lambda x: x % 2 == 1, namba))) # [1, 3, 5]

maandishi = ["12", "7", "30"]
print(list(map(int, maandishi)))                 # [12, 7, 30]
print(sum(map(int, input("Andika namba kwa nafasi: ").split())))

Sema: "map(int, ...) ni njia ya haraka sana ya kugeuza strings kuwa namba — ukumbuke input().split()."

Hatua 5: Objects (Wiki 7) + key

python
from dataclasses import dataclass

@dataclass
class Mwanafunzi:
    jina: str
    alama: int

darasa = [Mwanafunzi("Asha", 88), Mwanafunzi("Juma", 42), Mwanafunzi("Neema", 67)]
for m in sorted(darasa, key=lambda m: m.alama, reverse=True):
    print(m.jina, m.alama)
print("Wastani:", sum(m.alama for m in darasa) / len(darasa))

Maswali ya kumuuliza mwanafunzi

  1. Andika [x * 2 for x in range(5)] inatoa nini? — [0, 2, 4, 6, 8].
  2. if ya kuchuja inakaa wapi kwenye comprehension? — Mwishoni, baada ya for.
  3. {x for x in [1, 1, 2]} inatoa nini? — Set {1, 2}.
  4. Lambda inaweza kuwa na mistari mingi? — Hapana, expression moja tu.
  5. key=len kwenye sorted inamaanisha nini? — Panga kwa urefu wa kila kipengele.
  6. Kwa nini print(map(...)) inaonyesha <map object>? — map inarudisha iterator; tumia list().
  7. Tofauti ya sorted(lst) na lst.sort()? — sorted inarudisha list mpya; .sort() inabadilisha ile ile na kurudisha None.

Makosa ya kawaida

Kosa Ujumbe wa error Suluhisho
if/else mwishoni: [x for x in n if x > 0 else 0] SyntaxError: invalid syntax (au expected 'else' after 'if' expression) [x if x > 0 else 0 for x in n]
lambda yenye return SyntaxError: invalid syntax lambda x: x * 2 (bila return)
x = lst.sort() kisha kutumia x TypeError: 'NoneType' object is not iterable x = sorted(lst)
key=len() (kuita function) TypeError: len() takes exactly one argument (0 given) key=len bila ()
Kutumia map object mara mbili Mara ya pili ni tupu [] Iterator inaisha; ihifadhi kama list(...).
Kupanga list yenye str na int pamoja TypeError: '<' not supported between instances of 'int' and 'str' Geuza zote kwa aina moja, au key=str.

Zoezi darasani

Zoezi 1.1: Kutoka joto_c = [22, 25, 31, 18, 29] tengeneza list ya Fahrenheit kwa comprehension, na list ya siku zenye joto zaidi ya 26°C.

Zoezi 1.2: Kutoka sentensi, tengeneza dict ya neno: idadi_ya_herufi kwa maneno yenye herufi zaidi ya 3 tu.

Zoezi 1.3: bidhaa = [("Sukari", 3000, 20), ("Mchele", 2500, 6), ("Sabuni", 1200, 3), ("Mafuta", 7500, 12)] (jina, bei, idadi). (a) Panga kwa thamani ya stock (bei×idadi) kutoka kubwa. (b) Tafuta bidhaa ya bei ghali zaidi kwa max(key=...). (c) Majina ya bidhaa zenye idadi < 10 kwa filter kisha kwa comprehension.

Jibu
python
# 1.1
joto_c = [22, 25, 31, 18, 29]
joto_f = [c * 9 / 5 + 32 for c in joto_c]
print(joto_f)                       # [71.6, 77.0, 87.8, 64.4, 84.2]
print([c for c in joto_c if c > 26])   # [31, 29]

# 1.2
sentensi = "Leo ni siku nzuri ya kujifunza Python"
print({n: len(n) for n in sentensi.split() if len(n) > 3})
# {'siku': 4, 'nzuri': 5, 'kujifunza': 9, 'Python': 6}

# 1.3
bidhaa = [("Sukari", 3000, 20), ("Mchele", 2500, 6), ("Sabuni", 1200, 3), ("Mafuta", 7500, 12)]
print(sorted(bidhaa, key=lambda b: b[1] * b[2], reverse=True))
# [('Mafuta', 7500, 12), ('Sukari', 3000, 20), ('Mchele', 2500, 6), ('Sabuni', 1200, 3)]
print(max(bidhaa, key=lambda b: b[1]))                 # ('Mafuta', 7500, 12)
print([b[0] for b in filter(lambda b: b[2] < 10, bidhaa)])   # ['Mchele', 'Sabuni']
print([b[0] for b in bidhaa if b[2] < 10])                   # ['Mchele', 'Sabuni']

Kipindi 2 — Iterators, Generators, Decorators, *args/**kwargs na Git & GitHub

Mtiririko wa kipindi

  1. Marudio mafupi ya kazi ya nyumbani
  2. Iterators: iter, next, StopIteration
  3. Generators: yield
  4. Marudio ya *args, **kwargs
  5. Decorators
  6. Mapumziko
  7. Git: config, init, status, add, commit, log, .gitignore
  8. GitHub: tengeneza repo, remote add, push
  9. Q&A + kazi ya nyumbani (maandalizi ya mradi)

Kipindi hiki kina mambo mengi — kama muda unabana, punguza decorators kuwa mfano mmoja (@muda), lakini USIRUKE Git: Kipindi 3 tunaihitaji.

Elezo kwa mwalimu (soma kwanza wewe)

Iterable vs iterator. Iterable ni kitu unachoweza kuzunguka kwa for (list, str, dict, file). Iterator ni "kidole" kinachokumbuka uko wapi: it = iter(lst), kisha next(it) inatoa kinachofuata; vikiisha → StopIteration. for loop inafanya hivi nyuma ya pazia. Iterator ikishaisha haianzi tena (ndiyo maana map ya Kipindi 1 ilikuwa tupu mara ya pili).

Generator ni function yenye yield badala ya (au pamoja na) return. Ukiiita, haiendeshi code mara moja — inarudisha generator object (aina ya iterator). Kila next() inaendesha code mpaka yield inayofuata, inatoa thamani, na inasimama (pause) ikikumbuka hali yake. Faida: inatumia memory kidogo — inatoa kimoja kimoja badala ya kujenga list nzima (mf. kusoma file la GB 2 mstari kwa mstari, au mfululizo usio na mwisho).

*args na **kwargs (marudio ya Wiki 4): *args inakusanya positional arguments za ziada kuwa tuple; **kwargs inakusanya keyword arguments kuwa dict. Majina args/kwargs ni convention; * na ** ndizo muhimu. Pia zinatumika kufungua: f(*lst), f(**d).

Decorator ni function inayopokea function nyingine na kurudisha function mpya "iliyofunikwa" (wrapper) yenye tabia ya ziada — bila kubadilisha code ya function ya asili. Hatua za kuelewa:

  1. Functions ni objects — zinaweza kupitishwa kama arguments (tumefanya na key=!).
  2. Function inaweza kufafanuliwa ndani ya function nyingine na kurudishwa.
  3. @decorator juu ya def f ni kifupi cha f = decorator(f).

Wrapper inatumia *args, **kwargs ili ifanye kazi na function yoyote, na @functools.wraps(func) ili jina na docstring ya function ya asili zisipotee. Tumeshaona decorators: @property, @dataclass, @abstractmethod.

Git ni programu ya "version control" — inahifadhi historia ya mabadiliko ya code yako kama "picha" (commits). Unaweza kurudi nyuma, kuona nini kilibadilika, na kufanya kazi na wengine. GitHub ni tovuti inayohifadhi repositories za Git mtandaoni (backup + kushiriki + portfolio ya kazi). Git ≠ GitHub: Git iko kwenye kompyuta yako; GitHub ni "seva" mtandaoni.

Maeneo matatu ya Git:

text
Working directory  --git add-->  Staging area  --git commit-->  Repository (historia)
 (unahariri files)              (umechagua cha kuhifadhi)       (picha imehifadhiwa)
                                                    --git push--> GitHub

Amri (PowerShell, ndani ya folder la mradi):

powershell
git config --global user.name "Jina Lako"            # mara moja tu kwa kompyuta
git config --global user.email "email@ya-github.com"
git config --global init.defaultBranch main
git init                    # anzisha repo (folder lililofichwa .git)
git status                  # nini kimebadilika?
git add jina.py             # au: git add .   (kila kitu)
git commit -m "Ujumbe unaoeleza mabadiliko"
git log --oneline           # historia

.gitignore ni file lenye orodha ya files/folders Git isizifuatilie: .venv/, __pycache__/, *.pyc, .vscode/, na data binafsi. Litengenezwe kabla ya git add . ya kwanza.

Kupakia GitHub: kwenye github.com → "+" → New repository → jina (mf. maktaba) → Public → USICHAGUE "Add a README" (repo iwe tupu, ili kuepuka migongano) → Create. Kisha:

powershell
git remote add origin https://github.com/JINA_LAKO/maktaba.git
git branch -M main
git push -u origin main

Mara ya kwanza, Windows itafungua dirisha la Git Credential Manager — chagua "Sign in with your browser" na uruhusu. Baada ya hapo, git push peke yake inatosha. (GitHub haikubali password ya akaunti kwenye terminal tangu 2021 — browser login au Personal Access Token tu.)

Sema hivi kwa mwanafunzi

  • "Iterator ni kama kitabu chenye alama ya ukurasa (bookmark): unasoma ukurasa mmoja, unaweka alama, kesho unaendelea pale pale. Ukimaliza kitabu, umemaliza."
  • "Generator ni kama mpishi wa chapati anayekupa moja moja zikiiva, badala ya kusubiri apike 100 ndipo akuletee zote. Anapumzika (yield) baada ya kila moja mpaka uombe nyingine (next)."
  • "Decorator ni kama kufunga zawadi kwa karatasi ya rangi: zawadi (function) haibadiliki ndani, lakini sasa ina kitu cha ziada nje."
  • "Git ni kama kitufe cha 'Save Game' kwenye mchezo wa video. Kila commit ni sehemu unayoweza kurudi ukikosea. GitHub ni kuhifadhi 'save' zako kwenye cloud ili hata kompyuta ikiibiwa, kazi yako iko salama."
  • "git add ni kuweka vitu kwenye boksi; git commit ni kufunga boksi na kuandika lebo; git push ni kulituma posta."

Andika code hii live (share screen)

Hatua 1: Iterator — iterators.py

python
matunda = ["embe", "nanasi", "papai"]
it = iter(matunda)
print(next(it))      # embe
print(next(it))      # nanasi
print(next(it))      # papai
try:
    print(next(it))
except StopIteration:
    print("Vimeisha!")

Hatua 2: Generator

python
def hesabu_hadi(n):
    print("Naanza...")
    i = 1
    while i <= n:
        yield i
        i += 1
    print("Nimemaliza.")

g = hesabu_hadi(3)
print(g)                 # <generator object hesabu_hadi at 0x...>
print(next(g))           # Naanza...  1
print(next(g))           # 2
for x in g:              # inaendelea pale ilipoachwa
    print("loop:", x)

Output:

text
<generator object hesabu_hadi at 0x...>
Naanza...
1
2
loop: 3
Nimemaliza.

Sisitiza: "Naanza..." haikuchapishwa tulipoita hesabu_hadi(3) — ilisubiri next ya kwanza.

Mfano wa manufaa:

python
import sys

def namba_shufwa(kikomo):
    for n in range(kikomo):
        if n % 2 == 0:
            yield n

list_kubwa = [n for n in range(1_000_000) if n % 2 == 0]
gen = namba_shufwa(1_000_000)
print(sys.getsizeof(list_kubwa), "bytes")   # takriban 4,000,000+
print(sys.getsizeof(gen), "bytes")          # takriban 200
print(sum(namba_shufwa(1_000_000)))         # 249999500000


def soma_mistari(path):
    with open(path, encoding="utf-8") as f:
        for mstari in f:
            mstari = mstari.strip()
            if mstari and not mstari.startswith("#"):
                yield mstari

(Namba halisi za getsizeof zinatofautiana kidogo kati ya matoleo — muhimu ni tofauti kubwa.)

Mfululizo usio na mwisho:

python
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
print([next(fib) for _ in range(10)])   # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Hatua 3: *args, **kwargs — args_kwargs.py

python
def jumla(*args):
    print("args:", args)
    return sum(args)

print(jumla(1, 2, 3))

def wasifu(jina, **kwargs):
    print("kwargs:", kwargs)
    for k, v in kwargs.items():
        print(f"  {k}: {v}")

wasifu("Asha", umri=17, mji="Arusha")

namba = [4, 5, 6]
print(jumla(*namba))                    # kufungua list
taarifa = {"umri": 20, "kazi": "Mwalimu"}
wasifu("Juma", **taarifa)               # kufungua dict

Output:

text
args: (1, 2, 3)
6
kwargs: {'umri': 17, 'mji': 'Arusha'}
  umri: 17
  mji: Arusha
args: (4, 5, 6)
15
kwargs: {'umri': 20, 'kazi': 'Mwalimu'}
  umri: 20
  kazi: Mwalimu

Hatua 4: Decorators — decorators.py. Jenga hatua kwa hatua:

python
# (a) functions ni objects
def salimia(jina):
    return f"Habari {jina}"

f = salimia
print(f("Asha"))

# (b) function inayorudisha function
def tengeneza_kizidishi(n):
    def zidisha(x):
        return x * n
    return zidisha

mara3 = tengeneza_kizidishi(3)
print(mara3(10))          # 30
python
# (c) decorator kwa mkono
def kelele(func):
    def wrapper(*args, **kwargs):
        jibu = func(*args, **kwargs)
        return jibu.upper() + "!!!"
    return wrapper

def salimia(jina):
    return f"Habari {jina}"

salimia = kelele(salimia)       # kufunika kwa mkono
print(salimia("Asha"))          # HABARI ASHA!!!


# (d) syntax ya @ -- sawa kabisa na hapo juu
@kelele
def aga(jina):
    return f"Kwaheri {jina}"

print(aga("Juma"))              # KWAHERI JUMA!!!

Sasa decorators za manufaa:

python
import functools
import time


def muda(func):
    """Inapima muda wa function kuendeshwa."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        mwanzo = time.perf_counter()
        jibu = func(*args, **kwargs)
        mwisho = time.perf_counter()
        print(f"[{func.__name__}] ilichukua sekunde {mwisho - mwanzo:.4f}")
        return jibu
    return wrapper


def rekodi(func):
    """Inaandika function ilipoitwa na arguments zake."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"-> {func.__name__} args={args} kwargs={kwargs}")
        jibu = func(*args, **kwargs)
        print(f"<- {func.__name__} imerudisha {jibu!r}")
        return jibu
    return wrapper


@muda
def hesabu_kubwa(n):
    return sum(i * i for i in range(n))


@rekodi
def ongeza(a, b=0):
    return a + b


print(hesabu_kubwa(1_000_000))
ongeza(2, b=3)
print(ongeza.__name__)       # ongeza  (shukrani kwa functools.wraps)

Output (muda utatofautiana):

text
[hesabu_kubwa] ilichukua sekunde 0.0612
333332833333500000
-> ongeza args=(2,) kwargs={'b': 3}
<- ongeza imerudisha 5
ongeza

Onyesha: ondoa @functools.wraps(func) → ongeza.__name__ inakuwa wrapper. "Ndiyo maana tunaweka wraps."

Hatua 5: Git — kwenye terminal ya VS Code, ndani ya wiki08:

powershell
git --version
git config --global user.name "Mwalimu Selanim"
git config --global user.email "email-yako@example.com"
git config --global init.defaultBranch main
git config --list

(Mwambie mwanafunzi atumie jina na email ya akaunti yake ya GitHub.)

Tengeneza folder la majaribio:

powershell
mkdir git_majaribio
cd git_majaribio
git init
git status

Output: Initialized empty Git repository in C:/.../git_majaribio/.git/ kisha On branch main ... nothing to commit.

Tengeneza salamu.py (print("Habari Git!")), kisha:

powershell
git status                 # salamu.py ni "Untracked" (nyekundu)
git add salamu.py
git status                 # "Changes to be committed" (kijani)
git commit -m "Ongeza programu ya salamu"
git log --oneline

Badilisha salamu.py (ongeza mstari), kisha:

powershell
git status                 # modified
git diff                   # inaonyesha mistari iliyobadilika (+ kijani, - nyekundu)
git add .
git commit -m "Ongeza salamu ya pili"
git log --oneline

Output ya mfano:

text
b4e1c2a (HEAD -> main) Ongeza salamu ya pili
7a9d3f0 Ongeza programu ya salamu

.gitignore — tengeneza venv na uonyeshe tatizo:

powershell
python -m venv .venv
git status                 # .venv/ inaonekana -- hatutaki!

Tengeneza file .gitignore (kwenye VS Code: New File → .gitignore):

text
.venv/
__pycache__/
*.pyc
.vscode/
powershell
git status                 # .venv imetoweka; .gitignore yenyewe inaonekana
git add .gitignore
git commit -m "Ongeza .gitignore"

Onyesha pia kitufe cha Source Control (Ctrl+Shift+G) kwenye VS Code — kinafanya add/commit kwa kubofya. Lakini tunajifunza amri kwanza.

Hatua 6: GitHub — share browser: github.com → "+" → New repository → git_majaribio → Public → bila README → Create. Nakili amri za "…or push an existing repository from the command line":

powershell
git remote add origin https://github.com/JINA_LAKO/git_majaribio.git
git branch -M main
git push -u origin main

Login kwa browser ikiombwa. Refresh ukurasa wa GitHub — files zimefika! Badilisha kitu, add, commit, kisha git push peke yake.

Maswali ya kumuuliza mwanafunzi

  1. Kinatokea nini ukiita next() kwenye iterator iliyoisha? — StopIteration.
  2. Tofauti ya return na yield? — return inamaliza function; yield inatoa thamani na kusimama, ikiendelea next ijayo.
  3. Kwa nini generator inatumia memory kidogo? — Haijengi list nzima; inatoa kimoja kimoja.
  4. *args ni aina gani ndani ya function? **kwargs? — Tuple; dict.
  5. @kelele juu ya def aga ni sawa na nini? — aga = kelele(aga).
  6. Kwa nini wrapper itumie *args, **kwargs? — Ili ifanye kazi na function yoyote, yenye arguments zozote.
  7. Hatua tatu za kuhifadhi mabadiliko kwenye GitHub? — git add, git commit -m "...", git push.
  8. Kwa nini .venv/ iwe kwenye .gitignore? — Ni kubwa na inaweza kutengenezwa upya kwa requirements.txt.

Makosa ya kawaida

Kosa Ujumbe wa error Suluhisho
Git haijasakinishwa / terminal ya zamani git : The term 'git' is not recognized as the name of a cmdlet... Sakinisha Git, funga na ufungue upya VS Code.
Hukuweka jina/email Author identity unknown *** Please tell me who you are. git config --global user.name ... na user.email ...
Commit bila -m Inafungua editor (Vim/VS Code) — mwanafunzi anakwama Kwenye Vim: andika :q! Enter; kisha tumia -m "ujumbe".
git commit bila add nothing added to commit but untracked files present git add . kwanza
git init kwenye folder lisilo sahihi (mf. Documents nzima) Maelfu ya files "untracked" Futa folder .git lililofichwa (Remove-Item -Recurse -Force .git) na cd kwenye folder sahihi.
Repo ya GitHub ina README, push inakataliwa ! [rejected] main -> main (fetch first) git pull origin main --allow-unrelated-histories kisha git push, au tengeneza repo bila README.
remote origin tayari ipo error: remote origin already exists. git remote set-url origin <URL sahihi>
Kosa la jina la branch error: src refspec main does not match any Bado hakuna commit, au branch ni master: fanya commit kwanza / git branch -M main
Kuita generator na kutarajia list print(gen) inaonyesha <generator object ...> list(gen) au for
Decorator inasahau return jibu Function iliyopambwa inarudisha None return func(*args, **kwargs)

Zoezi darasani

Zoezi 2.1: Generator hesabu_kurudi_nyuma(n) inayotoa n, n-1, ..., 1 kisha "Uzinduzi!".

Zoezi 2.2: Generator vipande(lst, ukubwa) inayogawa list kuwa vipande: list(vipande([1,2,3,4,5], 2)) → [[1, 2], [3, 4], [5]].

Zoezi 2.3: Decorator jaribu_tena(func) — kama function inarusha exception, ijaribu tena hadi mara 3 kabla ya kuacha (rusha kosa la mwisho).

Zoezi 2.4 (Git): Mwanafunzi apeleke kazi zake za Wiki 7 (folder wiki07) kwenye repo mpya ya GitHub python-kozi, akiwa na .gitignore na angalau commits 2.

Jibu
python
# 2.1
def hesabu_kurudi_nyuma(n):
    while n > 0:
        yield n
        n -= 1
    yield "Uzinduzi!"

for x in hesabu_kurudi_nyuma(3):
    print(x)      # 3 2 1 Uzinduzi!

# 2.2
def vipande(lst, ukubwa):
    for i in range(0, len(lst), ukubwa):
        yield lst[i:i + ukubwa]

print(list(vipande([1, 2, 3, 4, 5], 2)))   # [[1, 2], [3, 4], [5]]

# 2.3
import functools
import random

def jaribu_tena(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        kosa_la_mwisho = None
        for jaribio in range(1, 4):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                print(f"Jaribio {jaribio} limeshindwa: {e}")
                kosa_la_mwisho = e
        raise kosa_la_mwisho
    return wrapper

@jaribu_tena
def mtandao_dhaifu():
    if random.random() < 0.6:
        raise ConnectionError("Mtandao umekatika")
    return "Data imepakuliwa"

try:
    print(mtandao_dhaifu())
except ConnectionError:
    print("Imeshindikana baada ya majaribio 3.")

2.4 — amri:

powershell
cd ..\wiki07
git init
# tengeneza .gitignore (.venv/, __pycache__/, *.pyc, .vscode/)
git add .gitignore
git commit -m "Ongeza .gitignore"
git add .
git commit -m "Kazi za Wiki 7: inheritance na dataclasses"
git remote add origin https://github.com/JINA_LAKO/python-kozi.git
git branch -M main
git push -u origin main

Kipindi 3 — Mradi wa Mwezi 2: "Mfumo wa Maktaba"

Mtiririko wa kipindi

  1. Marudio: Git ilifanya kazi? Rafu ya Wiki 7 iko tayari?
  2. Maelezo ya mradi, muundo wa files, git init + .gitignore
  3. models.py — dataclasses Kitabu, Mwanachama + exceptions (makosa.py)
  4. maktaba.py — class Maktaba (logic) + kuhifadhi/kupakia JSON
  5. Mapumziko (commit kwanza!)
  6. main.py — menu ya terminal, kushughulikia makosa
  7. README, commit ya mwisho, git push kwenye GitHub
  8. Tathmini ya Mwezi 2, maelekezo ya likizo.

Elezo kwa mwalimu (soma kwanza wewe)

Mradi huu unaunganisha kila kitu cha Mwezi 2: | Wiki | Kinachotumika kwenye mradi | |------|-----------------------------| | 5 | JSON, pathlib, try/except, custom exceptions | | 6 | Classes, methods, encapsulation (_vitabu), __str__ | | 7 | @dataclass, inheritance ya exceptions, type hints, __len__ | | 8 | Comprehensions, lambda + sorted, generator, decorator, Git/GitHub |

Muundo wa mradi:

text
maktaba/
├── .gitignore
├── README.md
├── makosa.py      # custom exceptions
├── models.py      # Kitabu, Mwanachama (dataclasses)
├── maktaba.py     # class Maktaba: logic + JSON
├── main.py        # menu ya mtumiaji
└── data.json      # inaundwa yenyewe (iko kwenye .gitignore? -- tazama chini)

Uamuzi kuhusu data.json: tunaiweka kwenye .gitignore kwa sababu ni data ya mtumiaji, si code. (Mwambie mwanafunzi: kwenye mradi halisi, data ya watu haiwekwi GitHub.)

Jinsi ya kufundisha: Wewe unaandika makosa.py na models.py live huku ukieleza. maktaba.py andikeni pamoja — mwanafunzi aandike methods rudisha na tafuta mwenyewe ukimwongoza. main.py — mpe muundo, aandike sehemu ya menu. Commit baada ya kila file! Kama muda utaisha, mwanafunzi amalize wakati wa likizo (iko kwenye kazi ya hiari).

Mambo mapya madogo kwenye mradi:

  • asdict(obj) (kutoka dataclasses) → dict kwa JSON; Kitabu(**d) → kurudisha dict kuwa object (hapa ndipo **kwargs ya Kipindi 2 inaonekana kwa manufaa!).
  • @classmethod — method inayopokea class (cls) badala ya object; inatumiwa kama "constructor mbadala": Maktaba.pakia(path). Eleza: "Ni njia ya kuunda object kwa njia nyingine — hapa kutoka file."
  • date.today().isoformat() → "2026-12-24" (string, inafaa JSON). date.fromisoformat(s) kurudisha.

Sema hivi kwa mwanafunzi

  • "Leo wewe ni developer wa maktaba ya shule. Mkutubi anataka kompyuta ikumbuke vitabu vyote, wanachama, nani ameazima nini, na kitabu kimechelewa siku ngapi."
  • "Tunagawa code kwenye files kama idara za kampuni: models ni idara ya kumbukumbu, maktaba ni meneja anayefanya maamuzi, main ni mapokezi yanayoongea na wateja."
  • "Commit kila hatua — kama kuhifadhi mchezo kabla ya level ngumu."

Andika code hii live (share screen)

Hatua 1: Anza mradi

powershell
mkdir maktaba
cd maktaba
git init
code .gitignore

.gitignore:

text
.venv/
__pycache__/
*.pyc
.vscode/
data.json

(Mradi huu hautumii packages za nje — requirements.txt si lazima. Kama mwanafunzi anataka, python -m venv .venv bado ni mazoea mazuri.)

Hatua 2: makosa.py

python
"""Makosa (exceptions) maalum ya mfumo wa maktaba."""


class KosaLaMaktaba(Exception):
    """Mzazi wa makosa yote ya maktaba."""


class KitabuHakipo(KosaLaMaktaba):
    pass


class MwanachamaHayupo(KosaLaMaktaba):
    pass


class KimeshaAzimwa(KosaLaMaktaba):
    pass


class HakijaAzimwa(KosaLaMaktaba):
    pass


class KikomoKimefikiwa(KosaLaMaktaba):
    pass


class TayariKipo(KosaLaMaktaba):
    pass

Eleza: docstring ndani ya class inatosha — hatuhitaji pass kama kuna docstring.

Hatua 3: models.py

python
"""Data za mfumo: Kitabu na Mwanachama."""
from dataclasses import dataclass, field


@dataclass
class Kitabu:
    isbn: str
    kichwa: str
    mwandishi: str
    mwaka: int
    aliyeazima: str | None = None          # namba ya mwanachama
    tarehe_ya_kuazima: str | None = None   # "YYYY-MM-DD"

    @property
    def kimeazimwa(self) -> bool:
        return self.aliyeazima is not None

    def __str__(self) -> str:
        hali = f"kimeazimwa na {self.aliyeazima}" if self.kimeazimwa else "kipo"
        return f"[{self.isbn}] {self.kichwa} - {self.mwandishi} ({self.mwaka}) | {hali}"


@dataclass
class Mwanachama:
    namba: str
    jina: str
    vitabu: list[str] = field(default_factory=list)    # ISBN za vitabu alivyoazima

    def __str__(self) -> str:
        return f"{self.namba}: {self.jina} (ameazima {len(self.vitabu)})"

Jaribu haraka kwenye terminal:

powershell
python -c "from models import Kitabu; k = Kitabu('1', 'Kinjeketile', 'E. Hussein', 1969); print(k)"

Output: [1] Kinjeketile - E. Hussein (1969) | kipo

powershell
git add .
git commit -m "Ongeza models na makosa"

Hatua 4: maktaba.py (moyo wa mradi)

python
"""Logic ya maktaba na kuhifadhi kwenye JSON."""
import functools
import json
from dataclasses import asdict
from datetime import date
from pathlib import Path
from typing import Iterator

from makosa import (
    HakijaAzimwa,
    KikomoKimefikiwa,
    KimeshaAzimwa,
    KitabuHakipo,
    MwanachamaHayupo,
    TayariKipo,
)
from models import Kitabu, Mwanachama

SIKU_ZA_KUAZIMA = 14
FAINI_KWA_SIKU = 200       # TSh


def hifadhi_baadaye(method):
    """Decorator: baada ya method kufanikiwa, hifadhi maktaba kwenye file."""
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        jibu = method(self, *args, **kwargs)
        self.hifadhi()
        return jibu
    return wrapper


class Maktaba:
    KIKOMO_CHA_VITABU = 3

    def __init__(self, path: Path):
        self.path = Path(path)
        self._vitabu: dict[str, Kitabu] = {}
        self._wanachama: dict[str, Mwanachama] = {}

    # ---------- kuhifadhi / kupakia ----------
    def hifadhi(self) -> None:
        data = {
            "vitabu": [asdict(k) for k in self._vitabu.values()],
            "wanachama": [asdict(m) for m in self._wanachama.values()],
        }
        with open(self.path, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)

    @classmethod
    def pakia(cls, path: Path) -> "Maktaba":
        maktaba = cls(path)
        try:
            with open(maktaba.path, encoding="utf-8") as f:
                data = json.load(f)
        except FileNotFoundError:
            return maktaba                     # maktaba mpya, tupu
        except json.JSONDecodeError:
            print("Onyo: data.json imeharibika, naanza na maktaba tupu.")
            return maktaba
        for d in data.get("vitabu", []):
            k = Kitabu(**d)
            maktaba._vitabu[k.isbn] = k
        for d in data.get("wanachama", []):
            m = Mwanachama(**d)
            maktaba._wanachama[m.namba] = m
        return maktaba

    # ---------- kutafuta ----------
    def kitabu(self, isbn: str) -> Kitabu:
        try:
            return self._vitabu[isbn]
        except KeyError:
            raise KitabuHakipo(f"Hakuna kitabu chenye ISBN '{isbn}'.") from None

    def mwanachama(self, namba: str) -> Mwanachama:
        try:
            return self._wanachama[namba]
        except KeyError:
            raise MwanachamaHayupo(f"Hakuna mwanachama namba '{namba}'.") from None

    def tafuta(self, neno: str) -> list[Kitabu]:
        neno = neno.lower()
        return [
            k for k in self._vitabu.values()
            if neno in k.kichwa.lower() or neno in k.mwandishi.lower()
        ]

    def vitabu_vilivyopangwa(self, kwa: str = "kichwa") -> list[Kitabu]:
        return sorted(self._vitabu.values(), key=lambda k: getattr(k, kwa))

    def vinavyopatikana(self) -> list[Kitabu]:
        return [k for k in self._vitabu.values() if not k.kimeazimwa]

    def vilivyochelewa(self, leo: date | None = None) -> Iterator[tuple[Kitabu, int]]:
        """Generator: inatoa (kitabu, siku_za_kuchelewa) kwa vitabu vilivyopitiliza muda."""
        leo = leo or date.today()
        for k in self._vitabu.values():
            if k.tarehe_ya_kuazima:
                siku = (leo - date.fromisoformat(k.tarehe_ya_kuazima)).days
                if siku > SIKU_ZA_KUAZIMA:
                    yield k, siku - SIKU_ZA_KUAZIMA

    # ---------- kubadilisha ----------
    @hifadhi_baadaye
    def ongeza_kitabu(self, kitabu: Kitabu) -> None:
        if kitabu.isbn in self._vitabu:
            raise TayariKipo(f"ISBN '{kitabu.isbn}' tayari ipo.")
        self._vitabu[kitabu.isbn] = kitabu

    @hifadhi_baadaye
    def sajili(self, mwanachama: Mwanachama) -> None:
        if mwanachama.namba in self._wanachama:
            raise TayariKipo(f"Mwanachama '{mwanachama.namba}' tayari yupo.")
        self._wanachama[mwanachama.namba] = mwanachama

    @hifadhi_baadaye
    def azima(self, isbn: str, namba: str, leo: date | None = None) -> None:
        k = self.kitabu(isbn)
        m = self.mwanachama(namba)
        if k.kimeazimwa:
            raise KimeshaAzimwa(f"'{k.kichwa}' kimeshaazimwa na {k.aliyeazima}.")
        if len(m.vitabu) >= self.KIKOMO_CHA_VITABU:
            raise KikomoKimefikiwa(
                f"{m.jina} tayari ana vitabu {self.KIKOMO_CHA_VITABU}."
            )
        k.aliyeazima = m.namba
        k.tarehe_ya_kuazima = (leo or date.today()).isoformat()
        m.vitabu.append(k.isbn)

    @hifadhi_baadaye
    def rudisha(self, isbn: str, leo: date | None = None) -> int:
        """Inarudisha faini (TSh) kama kitabu kimechelewa, vinginevyo 0."""
        k = self.kitabu(isbn)
        if not k.kimeazimwa:
            raise HakijaAzimwa(f"'{k.kichwa}' hakijaazimwa.")
        leo = leo or date.today()
        siku = (leo - date.fromisoformat(k.tarehe_ya_kuazima)).days
        faini = max(0, siku - SIKU_ZA_KUAZIMA) * FAINI_KWA_SIKU
        m = self.mwanachama(k.aliyeazima)
        m.vitabu.remove(k.isbn)
        k.aliyeazima = None
        k.tarehe_ya_kuazima = None
        return faini

    # ---------- dunders ----------
    def __len__(self) -> int:
        return len(self._vitabu)

    def __contains__(self, isbn: str) -> bool:
        return isbn in self._vitabu

    def __str__(self) -> str:
        return (f"Maktaba: vitabu {len(self)}, vinavyopatikana "
                f"{len(self.vinavyopatikana())}, wanachama {len(self._wanachama)}")

Maelezo ya kusema ukiandika:

  • Kitabu(**d) — dict {"isbn": "1", "kichwa": ...} inafunguliwa kuwa keyword arguments. Hii ndiyo sababu keys za JSON lazima zilingane na majina ya fields.
  • @hifadhi_baadaye — decorator yetu wenyewe! Kila mabadiliko yanahifadhiwa yenyewe; tukisahau kuita hifadhi() hakuna tatizo. Kama method inarusha exception, self.hifadhi() haifikiwi — data mbaya haihifadhiwi.
  • vilivyochelewa ni generator (yield).
  • Parameter leo inaturuhusu "kujifanya" ni tarehe nyingine ili kujaribu faini (testing).

Jaribio la haraka — jaribio.py (si sehemu ya mradi, unaweza kuifuta baadaye):

python
from datetime import date
from pathlib import Path

from maktaba import Maktaba
from makosa import KosaLaMaktaba
from models import Kitabu, Mwanachama

path = Path(__file__).parent / "jaribio.json"
path.unlink(missing_ok=True)            # anza upya kila mara

m = Maktaba.pakia(path)
m.ongeza_kitabu(Kitabu("978-1", "Kinjeketile", "Ebrahim Hussein", 1969))
m.ongeza_kitabu(Kitabu("978-2", "Rosa Mistika", "Euphrase Kezilahabi", 1971))
m.ongeza_kitabu(Kitabu("978-3", "Siku ya Watenzi Wote", "Shaaban Robert", 1968))
m.sajili(Mwanachama("M001", "Asha"))

m.azima("978-1", "M001", leo=date(2026, 12, 1))
print(m)
try:
    m.azima("978-1", "M001")
except KosaLaMaktaba as e:
    print("Kosa:", e)

for k, siku in m.vilivyochelewa(leo=date(2026, 12, 24)):
    print(f"Kimechelewa: {k.kichwa} siku {siku}")

faini = m.rudisha("978-1", leo=date(2026, 12, 24))
print("Faini:", faini)

m2 = Maktaba.pakia(path)                # pakia upya kutoka JSON
print(m2, "| '978-2' in m2:", "978-2" in m2)
print([k.kichwa for k in m2.vitabu_vilivyopangwa("mwaka")])
print([str(k) for k in m2.tafuta("robert")])

Output:

text
Maktaba: vitabu 3, vinavyopatikana 2, wanachama 1
Kosa: 'Kinjeketile' kimeshaazimwa na M001.
Kimechelewa: Kinjeketile siku 9
Faini: 1800
Maktaba: vitabu 3, vinavyopatikana 3, wanachama 1 | '978-2' in m2: True
['Siku ya Watenzi Wote', 'Kinjeketile', 'Rosa Mistika']
['[978-3] Siku ya Watenzi Wote - Shaaban Robert (1968) | kipo']

Hesabu ya faini: Des 1 → Des 24 = siku 23; 23 − 14 = 9 × 200 = 1,800. Fungua jaribio.json uonyeshe data. Ongeza jaribio.json kwenye .gitignore (au futa file). Commit:

powershell
git add .
git commit -m "Ongeza class Maktaba na kuhifadhi JSON"

Hatua 5: main.py

python
"""Mfumo wa Maktaba - menu ya terminal."""
from pathlib import Path

from makosa import KosaLaMaktaba
from maktaba import Maktaba
from models import Kitabu, Mwanachama

DATA = Path(__file__).parent / "data.json"

MENU = """
========= MFUMO WA MAKTABA =========
1. Ongeza kitabu        6. Rudisha kitabu
2. Sajili mwanachama    7. Tafuta kitabu
3. Onyesha vitabu       8. Vitabu vilivyochelewa
4. Onyesha wanachama    9. Takwimu
5. Azima kitabu         0. Toka
"""


def soma_namba(ujumbe: str) -> int:
    while True:
        try:
            return int(input(ujumbe))
        except ValueError:
            print("Tafadhali andika namba.")


def onyesha(vitu) -> None:
    vitu = list(vitu)
    if not vitu:
        print("(hakuna)")
    for kitu in vitu:
        print(" ", kitu)


def main() -> None:
    maktaba = Maktaba.pakia(DATA)
    print(maktaba)
    while True:
        print(MENU)
        chaguo = input("Chagua: ").strip()
        try:
            if chaguo == "1":
                kitabu = Kitabu(
                    isbn=input("ISBN: ").strip(),
                    kichwa=input("Kichwa: ").strip(),
                    mwandishi=input("Mwandishi: ").strip(),
                    mwaka=soma_namba("Mwaka: "),
                )
                maktaba.ongeza_kitabu(kitabu)
                print("Kitabu kimeongezwa.")
            elif chaguo == "2":
                m = Mwanachama(input("Namba (mf. M001): ").strip(), input("Jina: ").strip())
                maktaba.sajili(m)
                print("Mwanachama amesajiliwa.")
            elif chaguo == "3":
                onyesha(maktaba.vitabu_vilivyopangwa())
            elif chaguo == "4":
                onyesha(maktaba._wanachama.values())
            elif chaguo == "5":
                maktaba.azima(input("ISBN: ").strip(), input("Namba ya mwanachama: ").strip())
                print("Kimeazimwa. Kirudishwe ndani ya siku 14.")
            elif chaguo == "6":
                faini = maktaba.rudisha(input("ISBN: ").strip())
                print("Kimerudishwa." + (f" Faini: TSh {faini:,}" if faini else ""))
            elif chaguo == "7":
                onyesha(maktaba.tafuta(input("Neno la kutafuta: ")))
            elif chaguo == "8":
                onyesha(f"{k.kichwa} - siku {s}" for k, s in maktaba.vilivyochelewa())
            elif chaguo == "9":
                print(maktaba)
            elif chaguo == "0":
                print("Kwaheri! Data imehifadhiwa.")
                break
            else:
                print("Chaguo si sahihi.")
        except KosaLaMaktaba as e:
            print("KOSA:", e)


if __name__ == "__main__":
    main()

Endesha python main.py; ongeza vitabu 2, mwanachama 1, azima, jaribu kuazima tena (kosa linaonyeshwa vizuri, programu haifi), toka, endesha tena — data bado ipo!

Kumbuka: maktaba._wanachama.values() kwenye chaguo 4 ni kugusa attribute ya "ndani" — uliza mwanafunzi: "Hii inavunja kanuni gani ya Wiki 6? Tungeirekebishaje?" (Jibu: encapsulation; ongeza method wanachama() kwenye Maktaba inayorudisha list.) Ni zoezi zuri la likizo.

Hatua 6: README.md na push

markdown
# Mfumo wa Maktaba

Mradi wa Mwezi 2 wa kozi ya Python. Programu ya terminal ya kusimamia vitabu,
wanachama na kuazima, ikihifadhi data kwenye JSON.

## Jinsi ya kuendesha
```
python main.py
```
Inahitaji Python 3.12+. Hakuna packages za nje.

## Nilichojifunza
- OOP: dataclasses, encapsulation, custom exceptions
- Kuhifadhi data kwa JSON na pathlib
- Comprehensions, lambda, generators na decorators
- Git & GitHub
powershell
git add .
git status            # hakikisha data.json na .venv HAZIMO
git commit -m "Ongeza menu ya main.py na README"

Kwenye GitHub: New repository → maktaba → Public → bila README → Create:

powershell
git remote add origin https://github.com/JINA_LAKO/maktaba.git
git branch -M main
git push -u origin main

Fungua repo kwenye browser — README inaonekana chini ya files. Hongera! Hii ni kazi ya kwanza kwenye portfolio ya mwanafunzi.

Maswali ya kumuuliza mwanafunzi

  1. Kwa nini tumegawa code kwenye files 4? — Kila file lina jukumu moja; ni rahisi kusoma, kujaribu na kubadilisha.
  2. Kitabu(**d) inafanya nini? — Inafungua dict kuwa keyword arguments za __init__ ya dataclass.
  3. Decorator hifadhi_baadaye inatusaidiaje? — Inahifadhi data yenyewe baada ya kila mabadiliko yaliyofanikiwa.
  4. Kwa nini except KosaLaMaktaba moja inatosha kwenye main.py? — Makosa yote ya maktaba yanarithi kutoka kwake.
  5. Kwa nini data.json iko kwenye .gitignore? — Ni data ya watumiaji, si code.
  6. Faini ya kitabu kilichoazimwa Des 1 na kurudishwa Des 20 ni ngapi? — Siku 19 − 14 = 5 × 200 = TSh 1,000.
  7. Ukifunga programu na kuifungua tena, data inatoka wapi? — Maktaba.pakia(DATA) inasoma data.json.

Makosa ya kawaida

Kosa Ujumbe wa error Suluhisho
Kuendesha main.py kutoka folder jingine ModuleNotFoundError: No module named 'makosa' cd maktaba kwanza, kisha python main.py
Kuita file maktaba.py na folder maktaba kisha kufanya import ndani ya folder la juu ImportError / module isiyo sahihi Endesha ukiwa ndani ya folder la mradi.
JSON ina key isiyo field (mf. ulibadilisha jina la field) TypeError: Kitabu.__init__() got an unexpected keyword argument 'title' Futa data.json ya zamani au rekebisha keys.
Import ya mzunguko (circular): models.py ina-import maktaba.py na kinyume ImportError: cannot import name 'Kitabu' from partially initialized module 'models' models.py isi-import maktaba.py; mtiririko uwe mmoja: main → maktaba → models/makosa.
mwaka imeingizwa kama string Kupanga kwa mwaka kunaleta TypeError: '<' not supported between instances of 'str' and 'int' Tumia soma_namba()
data.json imepakiwa GitHub — git rm --cached data.json, ongeza kwenye .gitignore, commit.
Push inakataliwa (repo ina README) ! [rejected] main -> main (fetch first) git pull origin main --allow-unrelated-histories, kisha git push

Zoezi darasani

(Fanya kadri muda unavyoruhusu; yaliyobaki ni kazi ya likizo.)

Zoezi 3.1: Ongeza method wanachama(self) -> list[Mwanachama] kwenye Maktaba inayorudisha wanachama waliopangwa kwa jina, na itumie kwenye main.py badala ya maktaba._wanachama.

Zoezi 3.2: Ongeza chaguo "Futa kitabu" — kisiweze kufutwa kama kimeazimwa (rusha KimeshaAzimwa).

Zoezi 3.3: Ongeza takwimu kwenye chaguo 9: mwandishi mwenye vitabu vingi zaidi (dict comprehension / max(key=...)).

Jibu
python
# 3.1 -- ndani ya class Maktaba
def wanachama(self) -> list[Mwanachama]:
    return sorted(self._wanachama.values(), key=lambda m: m.jina.lower())

# main.py, chaguo 4:
#     onyesha(maktaba.wanachama())

# 3.2 -- ndani ya class Maktaba
@hifadhi_baadaye
def futa_kitabu(self, isbn: str) -> Kitabu:
    k = self.kitabu(isbn)
    if k.kimeazimwa:
        raise KimeshaAzimwa(f"Huwezi kufuta '{k.kichwa}' - kimeazimwa.")
    return self._vitabu.pop(isbn)

# main.py:
#     elif chaguo == "10":
#         k = maktaba.futa_kitabu(input("ISBN: ").strip())
#         print(f"Kimefutwa: {k.kichwa}")
# (na ongeza "10. Futa kitabu" kwenye MENU)

# 3.3 -- ndani ya class Maktaba
def mwandishi_bora(self) -> tuple[str, int] | None:
    if not self._vitabu:
        return None
    waandishi = {k.mwandishi for k in self._vitabu.values()}
    hesabu = {
        w: sum(1 for k in self._vitabu.values() if k.mwandishi == w)
        for w in waandishi
    }
    jina = max(hesabu, key=hesabu.get)
    return jina, hesabu[jina]

# main.py, chaguo 9:
#     print(maktaba)
#     bora = maktaba.mwandishi_bora()
#     if bora:
#         print(f"Mwandishi mwenye vitabu vingi: {bora[0]} ({bora[1]})")

Kumbuka kufanya git add ., git commit -m "...", git push baada ya kila zoezi.

Tathmini ya Mwezi 2

Muulize mwanafunzi (kwa mdomo) aeleze kwa maneno yake: (1) class vs object, (2) try/except, (3) kwa nini venv, (4) decorator ni nini, (5) git add → commit → push. Andika kwenye daftari lako mada alizoshindwa kueleza — zitarudiwa Wiki 9.


Kazi ya Nyumbani

(Kazi 1 inatakiwa Kipindi 2. Kazi 2 inatakiwa Kipindi 3. Kazi ya likizo — tazama hapa chini — inatakiwa: Kipindi 1 cha Wiki 9.)

Kazi 1 — Comprehensions (baada ya Kipindi 1): Kutoka list ya dicts ya wanafunzi [{"jina": ..., "alama": [..]}, ...] (angalau 5): (a) dict ya jina: wastani kwa dict comprehension, (b) majina ya waliopata wastani ≥ 60 yaliyopangwa kutoka juu, (c) set ya alama zote za kipekee.

Kazi 2 — Maandalizi ya mradi (baada ya Kipindi 2): (a) Generator soma_csv(path) inayotoa kila row ya CSV kama dict (tumia csv.DictReader). (b) Decorator thibitisha_chanya inayorusha ValueError kama argument yoyote ya namba ni hasi. (c) Hakikisha Git na akaunti ya GitHub ziko tayari, na repo python-kozi ina angalau commit 1.

Wiki ya Likizo (hakuna vipindi)

Mwambie mwanafunzi: "Pumzika, sherehekea sikukuu! Kama una muda, haya ni mazoezi ya hiari."

  1. Malizia mazoezi 3.1–3.3 ya mradi wa maktaba na push GitHub.
  2. Ongeza kwenye maktaba: kuhifadhi historia ya kila kuazima (list ya dicts kwenye JSON) na chaguo la kuionyesha.
  3. Rudia Wiki 5–8: andika upya bila kuangalia BankAccount (Wiki 6) kama @dataclass yenye @property.
  4. Soma README za repos 2 za Python kwenye GitHub (mf. https://github.com/TheAlgorithms/Python) — angalia jinsi wanavyopanga files.
  5. Swali la kufikiri: generator vilivyochelewa ingeweza kuandikwa kama list comprehension? Faida/hasara ni zipi?
Jibu
python
# Kazi 1
wanafunzi = [
    {"jina": "Asha", "alama": [80, 90, 85]},
    {"jina": "Juma", "alama": [40, 55, 50]},
    {"jina": "Neema", "alama": [70, 65, 75]},
    {"jina": "Baraka", "alama": [60, 58, 62]},
    {"jina": "Zawadi", "alama": [90, 95, 100]},
]
wastani = {w["jina"]: sum(w["alama"]) / len(w["alama"]) for w in wanafunzi}
print(wastani)
# {'Asha': 85.0, 'Juma': 48.33..., 'Neema': 70.0, 'Baraka': 60.0, 'Zawadi': 95.0}

waliofaulu = sorted((j for j, a in wastani.items() if a >= 60),
                    key=lambda j: wastani[j], reverse=True)
print(waliofaulu)     # ['Zawadi', 'Asha', 'Neema', 'Baraka']

kipekee = {a for w in wanafunzi for a in w["alama"]}
print(sorted(kipekee))
python
# Kazi 2
import csv
import functools


def soma_csv(path):
    with open(path, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            yield row


def thibitisha_chanya(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        for thamani in list(args) + list(kwargs.values()):
            if isinstance(thamani, (int, float)) and thamani < 0:
                raise ValueError(f"{func.__name__}: thamani hasi hairuhusiwi ({thamani})")
        return func(*args, **kwargs)
    return wrapper


@thibitisha_chanya
def eneo_la_mstatili(urefu, upana):
    return urefu * upana

print(eneo_la_mstatili(3, 4))      # 12
try:
    eneo_la_mstatili(3, upana=-1)
except ValueError as e:
    print(e)                        # eneo_la_mstatili: thamani hasi hairuhusiwi (-1)

Likizo, swali 5: Ndiyo — [(k, s) for ...] ingewezekana, lakini ingehitaji kuhesabu siku mara mbili au kutumia walrus operator, na ingejenga list nzima. Generator ni safi zaidi kusoma na inatumia memory kidogo; hasara ni kwamba inaweza kuzungukwa mara moja tu.

Ukiulizwa swali usilolijua

Checklist ya mwisho wa wiki

  • Anaandika list/dict/set comprehensions pamoja na if na if/else.
  • Anatumia lambda na sorted(key=...), max(key=...).
  • Anajua map/filter zinarudisha iterator (anatumia list()).
  • Anaeleza iter/next/StopIteration na anaandika generator kwa yield.
  • Anaandika decorator yenye *args, **kwargs na functools.wraps.
  • Ameweka git config (jina, email) na anatumia init, status, add, commit, log.
  • Ana .gitignore yenye .venv/ na __pycache__/.
  • Amepush repo kwenye GitHub bila msaada.
  • Mradi wa "Mfumo wa Maktaba" unafanya kazi (ongeza, sajili, azima, rudisha, tafuta, JSON inakumbuka data).
  • Mradi uko GitHub ukiwa na README.
  • Anajua kazi za hiari za wiki ya likizo.