Wiki 7: OOP Sehemu ya 2 — Inheritance, Polymorphism, Dataclasses na Type Hints
Mwezi 2 | Lengo kuu: Mwanafunzi atumie inheritance na
super(), aelewe polymorphism na abstract classes, aandike dunder methods (__len__,__eq__,__add__), na atumie@dataclasspamoja na type hints.
Maandalizi ya Mwalimu (soma kabla ya wiki kuanza)
- Soma:
- Tutorial — Inheritance: https://docs.python.org/3/tutorial/classes.html#inheritance
abcmodule: https://docs.python.org/3/library/abc.html (sehemu ya juu tu na mfano wa@abstractmethod)dataclasses: https://docs.python.org/3/library/dataclasses.html (mifano ya mwanzo tu)- Type hints cheat sheet (mypy): https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html
- Rudia Wiki 6: hakikisha unaweza kuandika class yenye
__init__,@property,__str__bila kuangalia — wiki hii inajenga juu yake. - Hiari: sakinisha
mypyndani ya venv (python -m pip install mypy) ili kuonyesha kuwa type hints zinaweza kukaguliwa. Python yenyewe HAIKAGUI type hints wakati wa kuendesha. - Andaa folder:
python_kozi\wiki07\.
Malengo ya Wiki (mwisho wa wiki mwanafunzi ataweza...)
- Kuunda subclass inayorithi kutoka parent class, na kutumia
super().__init__(...). - Ku-override method na kuita toleo la parent kwa
super(). - Kutumia
isinstance()naissubclass(). - Kueleza polymorphism na kuandika code inayofanya kazi na objects za aina tofauti.
- Kutengeneza abstract class kwa
ABCna@abstractmethod. - Kuandika
__len__,__eq__,__add__(na kuelewa dunders zinavyounganishwa na operators). - Kutumia
@dataclass(pamoja nafield(default_factory=list)naorder=True). - Kuandika type hints:
int,str,list[int],dict[str, float],Optional[str],str | None.
Kipindi 1 — Inheritance, super() na Method Overriding
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani ya Wiki 6 (Simu, Joto, Duka JSON)
- Tatizo: code inayojirudia kwenye classes zinazofanana
- Inheritance ya kwanza:
class Mbwa(Mnyama) super().__init__()- Mapumziko
- Overriding + kuita method ya parent;
isinstance,issubclass - Zoezi darasani
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
Inheritance (urithi) inamaanisha class mpya (child / subclass) inapata attributes na methods zote za class iliyopo (parent / base / superclass), kisha inaweza kuongeza mpya au kubadilisha zilizopo.
class Mnyama: # parent ... class Mbwa(Mnyama): # child: "Mbwa ni Mnyama" ...
Uhusiano huu unaitwa "is-a" (Mbwa ni Mnyama). Linganisha na Wiki 6, composition "has-a" (Duka lina Bidhaa). Kanuni rahisi: kama sentensi "X ni aina ya Y" ina maana, tumia inheritance; vinginevyo composition.
Method overriding: child anaandika method yenye jina lile lile kama la parent → toleo la child linatumika kwa objects za child.
super() inarudisha "parent" ili tuite methods zake. Muhimu zaidi kwenye __init__: child akiwa na __init__ yake, ya parent haiitwi yenyewe — lazima tuite super().__init__(...), vinginevyo attributes za parent hazitawekwa.
Python inapotafuta obj.method: inaangalia object → class yake → parent → parent wa parent ... → object (mzazi wa classes zote). Mpangilio huu unaitwa MRO (Method Resolution Order): Mbwa.__mro__. Taja tu; multiple inheritance (class C(A, B)) ipo lakini hatuifundishi kwa kina.
isinstance(obj, Mnyama)→ True kama obj ni Mnyama au subclass yake.issubclass(Mbwa, Mnyama)→ True.
Exceptions za Wiki 5 zilikuwa inheritance tayari! class SalioHalitoshi(Exception) — ndiyo maana except Exception inashika na SalioHalitoshi pia. Hii ni "aha moment" nzuri kwa mwanafunzi.
Sema hivi kwa mwanafunzi
- "Inheritance ni kama urithi wa familia: mtoto anarithi rangi ya macho ya mzazi (attributes) na tabia (methods), lakini anaweza kuwa na kipaji chake mwenyewe (method mpya), au kufanya jambo kwa njia yake (override)."
- "Gari, pikipiki na bajaji zote ni vyombo vya usafiri: zina namba ya usajili, zinaweza 'kuwasha'. Badala ya kuandika hivyo mara tatu, tunaandika
Chombomara moja." - "
super()ni kama kumwambia mzazi: 'Baba, fanya sehemu yako kwanza (kujaza jina na umri), kisha mimi nitaongeza yangu.'"
Andika code hii live (share screen)
Hatua 1: Tatizo — bila_urithi.py
class Mbwa: def __init__(self, jina, umri): self.jina = jina self.umri = umri def kula(self): print(f"{self.jina} anakula.") def toa_sauti(self): print("Woof!") class Paka: def __init__(self, jina, umri): self.jina = jina self.umri = umri def kula(self): print(f"{self.jina} anakula.") def toa_sauti(self): print("Meow!")
Uliza: "Unaona nini kinachojirudia?" (__init__ na kula.)
Hatua 2: Inheritance — urithi.py
class Mnyama: def __init__(self, jina, umri): self.jina = jina self.umri = umri def kula(self): print(f"{self.jina} anakula.") def toa_sauti(self): print("...") def __str__(self): return f"{type(self).__name__} {self.jina} ({self.umri} yrs)" class Mbwa(Mnyama): def toa_sauti(self): # override print("Woof!") class Paka(Mnyama): def toa_sauti(self): print("Meow!") m = Mbwa("Simba", 3) p = Paka("Pusi", 2) m.kula() # imerithiwa m.toa_sauti() # ya Mbwa p.toa_sauti() print(m, p, sep=" | ")
Output:
Simba anakula. Woof! Meow! Mbwa Simba (3 yrs) | Paka Pusi (2 yrs)
Eleza type(self).__name__ — jina la class halisi ya object.
Hatua 3: super().__init__
class Mbwa(Mnyama): def __init__(self, jina, umri, aina): super().__init__(jina, umri) # parent anaweka jina, umri self.aina = aina # child anaongeza def toa_sauti(self): print("Woof!") def leta_mpira(self): # method mpya print(f"{self.jina} ameleta mpira!") m = Mbwa("Simba", 3, "German Shepherd") print(m.jina, m.aina) m.leta_mpira()
Kisha onyesha kosa kwa makusudi: futa mstari wa super().__init__ na endesha → AttributeError: 'Mbwa' object has no attribute 'jina'. Rudisha.
Hatua 4: Override + kuita parent — wafanyakazi.py
class Mfanyakazi: def __init__(self, jina, mshahara): self.jina = jina self.mshahara = mshahara def malipo_ya_mwezi(self): return self.mshahara def maelezo(self): return f"{self.jina}: TSh {self.malipo_ya_mwezi():,}" class Meneja(Mfanyakazi): def __init__(self, jina, mshahara, posho): super().__init__(jina, mshahara) self.posho = posho def malipo_ya_mwezi(self): return super().malipo_ya_mwezi() + self.posho def maelezo(self): return super().maelezo() + " (Meneja)" wafanyakazi = [ Mfanyakazi("Asha", 800_000), Meneja("Juma", 1_500_000, 300_000), ] for w in wafanyakazi: print(w.maelezo()) print(isinstance(wafanyakazi[1], Mfanyakazi)) # True print(isinstance(wafanyakazi[0], Meneja)) # False print(issubclass(Meneja, Mfanyakazi)) # True print(Meneja.__mro__)
Output:
Asha: TSh 800,000 Juma: TSh 1,800,000 (Meneja) True False True (<class '__main__.Meneja'>, <class '__main__.Mfanyakazi'>, <class 'object'>)
Sisitiza: maelezo() ya parent inaita self.malipo_ya_mwezi() — na kwa Juma, self ni Meneja, kwa hiyo toleo la Meneja linatumika (pamoja na posho). Hili ni muhimu sana na linaanzisha polymorphism.
Hatua 5: Exceptions ni inheritance
class KosaLaBenki(Exception): pass class SalioHalitoshi(KosaLaBenki): pass class AkauntiImefungwa(KosaLaBenki): pass try: raise AkauntiImefungwa("Akaunti 1001 imefungwa.") except KosaLaBenki as e: # inashika subclasses zote print(type(e).__name__, "->", e)
Output: AkauntiImefungwa -> Akaunti 1001 imefungwa.
Maswali ya kumuuliza mwanafunzi
- Child class inapata nini kutoka parent? — Attributes na methods zote.
- Override ni nini? — Child kuandika method yenye jina lile lile ili kubadilisha tabia.
- Usipoita
super().__init__()kwenye__init__ya child, nini kinatokea? — Attributes za parent haziwekwi →AttributeErrorbaadaye. isinstance(Mbwa(...), Mnyama)ni nini? — True.- Tofauti ya "is-a" na "has-a"? — Inheritance (Mbwa ni Mnyama) vs composition (Duka lina Bidhaa).
- Kwa nini
except KosaLaBenkiinashikaSalioHalitoshi? — Kwa sababuSalioHalitoshini subclass yake.
Makosa ya kawaida
| Kosa | Ujumbe wa error | Suluhisho |
|---|---|---|
Kusahau super().__init__ |
AttributeError: 'Mbwa' object has no attribute 'jina' |
Ongeza super().__init__(jina, umri) |
Kupitisha self kwa super: super().__init__(self, jina, umri) |
TypeError: Mnyama.__init__() takes 3 positional arguments but 4 were given |
Usipitishe self |
Kusahau () kwenye super: super.__init__(...) |
TypeError: descriptor '__init__' requires a 'super' object but received a 'str' |
super().__init__(...) |
Kurithi kutoka object badala ya class: class Mbwa(Mnyama()) |
TypeError ya metaclass/arguments |
Weka jina la class bila () |
Kusahau return kwenye method iliyoovaride |
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int' |
return super().malipo_ya_mwezi() + ... |
Zoezi darasani
Zoezi 1.1: Class Chombo (namba, kasi_ya_juu) yenye maelezo(). Subclasses Gari (ina viti) na Pikipiki (ina ina_helmet: bool). Kila moja ione maelezo() yake ikiita ya parent.
Zoezi 1.2: Class Akaunti (mmiliki, salio, toa, weka). Subclass AkauntiYaAkiba isiyoruhusu salio kushuka chini ya 10,000 (override toa), na yenye ongeza_riba(asilimia).
Zoezi 1.3: Kwenye wafanyakazi.py, ongeza Mkandarasi(Mfanyakazi) analipwa kwa saa (saa, kiwango_kwa_saa). Override malipo_ya_mwezi. Chapisha jumla ya malipo ya wote.
Jibu
# 1.1 class Chombo: def __init__(self, namba, kasi_ya_juu): self.namba = namba self.kasi_ya_juu = kasi_ya_juu def maelezo(self): return f"{self.namba} (kasi ya juu {self.kasi_ya_juu} km/h)" class Gari(Chombo): def __init__(self, namba, kasi_ya_juu, viti): super().__init__(namba, kasi_ya_juu) self.viti = viti def maelezo(self): return "Gari " + super().maelezo() + f", viti {self.viti}" class Pikipiki(Chombo): def __init__(self, namba, kasi_ya_juu, ina_helmet): super().__init__(namba, kasi_ya_juu) self.ina_helmet = ina_helmet def maelezo(self): helmet = "ina helmet" if self.ina_helmet else "haina helmet" return "Pikipiki " + super().maelezo() + f", {helmet}" for c in [Gari("T 123 ABC", 180, 5), Pikipiki("MC 456 XYZ", 120, True)]: print(c.maelezo()) # 1.2 class Akaunti: def __init__(self, mmiliki, salio=0): self.mmiliki = mmiliki self.salio = salio def weka(self, kiasi): self.salio += kiasi def toa(self, kiasi): if kiasi > self.salio: raise ValueError("Salio halitoshi") self.salio -= kiasi class AkauntiYaAkiba(Akaunti): KIWANGO_CHA_CHINI = 10_000 def toa(self, kiasi): if self.salio - kiasi < self.KIWANGO_CHA_CHINI: raise ValueError(f"Lazima ubaki na angalau {self.KIWANGO_CHA_CHINI:,}") super().toa(kiasi) def ongeza_riba(self, asilimia): self.salio += self.salio * asilimia / 100 a = AkauntiYaAkiba("Neema", 50_000) a.toa(30_000) try: a.toa(15_000) except ValueError as e: print(e) # Lazima ubaki na angalau 10,000 a.ongeza_riba(5) print(a.salio) # 21000.0 # 1.3 class Mkandarasi(Mfanyakazi): def __init__(self, jina, saa, kiwango_kwa_saa): super().__init__(jina, 0) self.saa = saa self.kiwango_kwa_saa = kiwango_kwa_saa def malipo_ya_mwezi(self): return self.saa * self.kiwango_kwa_saa wote = [Mfanyakazi("Asha", 800_000), Meneja("Juma", 1_500_000, 300_000), Mkandarasi("Baraka", 120, 5_000)] jumla = 0 for w in wote: print(w.maelezo()) jumla += w.malipo_ya_mwezi() print(f"Jumla: TSh {jumla:,}") # Jumla: TSh 3,200,000
Kipindi 2 — Polymorphism, Abstract Classes na Dunder Methods
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani (maumbo ya kijiometri)
- Polymorphism + duck typing
- Abstract classes:
ABC,@abstractmethod - Mapumziko
- Dunder methods:
__len__,__eq__,__add__(+__lt__,__contains__kwa ufupi) - Zoezi darasani
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
Polymorphism ("maumbo mengi") — code moja inafanya kazi na objects za aina tofauti, kila moja ikijibu kwa njia yake. Mfano: for m in wanyama: m.toa_sauti() — hatujali ni Mbwa au Paka; kila mmoja anajua sauti yake. Tayari tuliona hili na malipo_ya_mwezi().
Duck typing: Python haihitaji objects ziwe na parent mmoja ili polymorphism ifanye kazi — inatosha ziwe na method yenye jina hilo. "Kama inatembea kama bata na inalia kama bata, basi ni bata." Hata len() inafanya kazi kwa str, list, dict — polymorphism ya built-in.
Abstract class — class ambayo haikusudiwi kuundiwa objects moja kwa moja; ni "mkataba" unaosema "kila subclass LAZIMA iwe na methods hizi."
from abc import ABC, abstractmethod class Umbo(ABC): @abstractmethod def eneo(self): ...
Umbo()→TypeError: Can't instantiate abstract class Umbo without an implementation for abstract method 'eneo'(ujumbe wa Python 3.12+).- Subclass isiyoandika
eneo→ nayo haiwezi kuundwa. Faida: kosa linaonekana mapema (wakati wa kuunda object), si baadaye ndani ya programu. - Abstract class inaweza kuwa na methods za kawaida pia, zinazotumia zile abstract (mf.
maelezo()inayoitaself.eneo()). ...(Ellipsis) aupasskwenye mwili wa abstract method — zote sawa.
Dunder methods (double underscore) ndizo zinazofanya operators na built-in functions zifanye kazi na objects zetu:
| Unaandika | Python inaita |
|-----------|---------------|
| len(x) | x.__len__() — lazima irudishe int ≥ 0 |
| x == y | x.__eq__(y) |
| x + y | x.__add__(y) |
| x < y | x.__lt__(y) (inahitajika kwa sorted()) |
| item in x | x.__contains__(item) |
| str(x), print(x) | x.__str__() |
Bila __eq__, == inalinganisha utambulisho (je ni object lile lile?), si maudhui. Pesa(100) == Pesa(100) → False mpaka tuandike __eq__.
Ndani ya __eq__/__add__, kama other si aina tunayoijua, rudisha NotImplemented (si raise) — Python itajaribu njia nyingine au itarusha TypeError yenyewe.
Kumbuka: ukiandika __eq__, Python inafanya object lisiwe "hashable" (huwezi kuliweka kwenye set au kama key ya dict) mpaka uandike __hash__. Usiingie kwa kina; taja tu kama mwanafunzi atauliza.
Sema hivi kwa mwanafunzi
- "Polymorphism ni kama mwalimu kusema 'Simameni!' darasani. Kila mwanafunzi anasimama kwa njia yake — mmoja haraka, mwingine polepole — lakini amri ni moja."
- "Abstract class ni kama tangazo la kazi: 'Mwombaji LAZIMA ajue kuendesha gari.' Tangazo lenyewe haliendeshi gari; ni sharti kwa kila atakayeajiriwa."
- "Dunder methods ni mlango wa nyuma unaoiruhusu Python kutumia alama kama
+na==kwa vitu vyetu. Unapoandikaa + b, nyuma ya pazia Python inasemaa.__add__(b)."
Andika code hii live (share screen)
Hatua 1: Polymorphism — polymorphism.py
class Mbwa: def toa_sauti(self): return "Woof!" class Paka: def toa_sauti(self): return "Meow!" class Ng'ombe: pass
Simama! Onyesha kosa: SyntaxError: unterminated string literal — apostrophe haiwezi kuwa kwenye jina. Badilisha kuwa Ngombe:
class Ngombe: def toa_sauti(self): return "Mooo!" class Redio: # si mnyama kabisa! def toa_sauti(self): return "Habari za saa hii..." for kitu in [Mbwa(), Paka(), Ngombe(), Redio()]: print(type(kitu).__name__, ":", kitu.toa_sauti())
Output:
Mbwa : Woof! Paka : Meow! Ngombe : Mooo! Redio : Habari za saa hii...
Sema: "Redio haina uhusiano na wanyama, lakini kwa sababu ina toa_sauti, inafanya kazi. Hiyo ni duck typing."
Hatua 2: Abstract class — maumbo.py
from abc import ABC, abstractmethod import math class Umbo(ABC): @abstractmethod def eneo(self): ... @abstractmethod def mzunguko(self): ... def maelezo(self): # method ya kawaida return (f"{type(self).__name__}: eneo={self.eneo():.2f}, " f"mzunguko={self.mzunguko():.2f}") class Mstatili(Umbo): def __init__(self, urefu, upana): self.urefu = urefu self.upana = upana def eneo(self): return self.urefu * self.upana def mzunguko(self): return 2 * (self.urefu + self.upana) class Duara(Umbo): def __init__(self, nusu_kipenyo): self.r = nusu_kipenyo def eneo(self): return math.pi * self.r ** 2 def mzunguko(self): return 2 * math.pi * self.r maumbo = [Mstatili(3, 4), Duara(1)] for u in maumbo: print(u.maelezo()) try: Umbo() except TypeError as e: print("Kosa:", e)
Output:
Mstatili: eneo=12.00, mzunguko=14.00 Duara: eneo=3.14, mzunguko=6.28 Kosa: Can't instantiate abstract class Umbo without an implementation for abstract methods 'eneo', 'mzunguko'
Kisha onyesha: tengeneza class Pembetatu(Umbo) yenye eneo tu (bila mzunguko) na jaribu Pembetatu() → TypeError ... abstract method 'mzunguko'.
Hatua 3: Dunders — pesa.py
class Pesa: def __init__(self, kiasi, sarafu="TZS"): self.kiasi = kiasi self.sarafu = sarafu def __repr__(self): return f"Pesa({self.kiasi!r}, {self.sarafu!r})" def __str__(self): return f"{self.sarafu} {self.kiasi:,}" def __eq__(self, other): if not isinstance(other, Pesa): return NotImplemented return self.kiasi == other.kiasi and self.sarafu == other.sarafu def __add__(self, other): if not isinstance(other, Pesa): return NotImplemented if self.sarafu != other.sarafu: raise ValueError("Huwezi kuongeza sarafu tofauti.") return Pesa(self.kiasi + other.kiasi, self.sarafu) def __lt__(self, other): if not isinstance(other, Pesa): return NotImplemented return self.kiasi < other.kiasi a = Pesa(5000) b = Pesa(5000) c = Pesa(2000) print(a == b) # True (bila __eq__ ingekuwa False) print(a is b) # False (objects tofauti) print(a + c) # TZS 7,000 print(sorted([a, c, Pesa(100)])) try: a + Pesa(10, "USD") except ValueError as e: print(e) try: a + 100 except TypeError as e: print("TypeError:", e)
Output:
True False TZS 7,000 [Pesa(100, 'TZS'), Pesa(2000, 'TZS'), Pesa(5000, 'TZS')] Huwezi kuongeza sarafu tofauti. TypeError: unsupported operand type(s) for +: 'Pesa' and 'int'
Hatua 4: __len__ na __contains__ — orodha_ya_kucheza.py
class Wimbo: def __init__(self, jina, msanii, sekunde): self.jina = jina self.msanii = msanii self.sekunde = sekunde def __repr__(self): return f"Wimbo({self.jina!r})" class Playlist: def __init__(self, jina): self.jina = jina self._nyimbo = [] def ongeza(self, wimbo): self._nyimbo.append(wimbo) def __len__(self): return len(self._nyimbo) def __contains__(self, jina_la_wimbo): for w in self._nyimbo: if w.jina.lower() == jina_la_wimbo.lower(): return True return False def __add__(self, other): mpya = Playlist(f"{self.jina} + {other.jina}") mpya._nyimbo = self._nyimbo + other._nyimbo return mpya bongo = Playlist("Bongo Flava") bongo.ongeza(Wimbo("Wimbo A", "Msanii 1", 210)) bongo.ongeza(Wimbo("Wimbo B", "Msanii 2", 185)) injili = Playlist("Injili") injili.ongeza(Wimbo("Wimbo C", "Kwaya", 300)) print(len(bongo)) # 2 print("wimbo a" in bongo) # True print("Wimbo C" in bongo) # False zote = bongo + injili print(zote.jina, len(zote)) # Bongo Flava + Injili 3 if bongo: # __len__ > 0 => True print("Playlist si tupu")
Maswali ya kumuuliza mwanafunzi
- Polymorphism ni nini kwa sentensi moja? — Kuita method moja kwa objects tofauti, kila moja ikijibu kwa njia yake.
- Kwa nini hatuwezi kuunda
Umbo()? — Ni abstract; lina abstract methods zisizo na implementation. - Faida ya abstract class? — Inalazimisha subclasses ziwe na methods fulani; kosa linaonekana mapema.
a == binaita method gani? —a.__eq__(b).- Bila
__eq__,Pesa(5) == Pesa(5)ni nini? Kwa nini? — False; inalinganisha utambulisho (is). sorted()ya objects inahitaji dunder gani? —__lt__.__len__lazima irudishe nini? — int isiyo hasi.
Makosa ya kawaida
| Kosa | Ujumbe wa error | Suluhisho |
|---|---|---|
| Kuunda abstract class | TypeError: Can't instantiate abstract class Umbo without an implementation for abstract method 'eneo' |
Unda subclass inayoandika methods zote. |
Kusahau kurithi ABC |
@abstractmethod haizuii chochote; Umbo() inafanya kazi |
class Umbo(ABC): |
Jina la method tofauti kwenye subclass (Eneo vs eneo) |
Kosa la abstract hapo juu | Majina yawe sawa kabisa. |
__len__ inarudisha float au None |
TypeError: 'float' object cannot be interpreted as an integer / 'NoneType' object cannot be interpreted as an integer |
return len(self._nyimbo) |
sorted() bila __lt__ |
TypeError: '<' not supported between instances of 'Pesa' and 'Pesa' |
Andika __lt__ (au tumia key=, Wiki 8). |
__add__ inabadilisha self badala ya kurudisha object jipya |
a + b inaharibu a |
Rudisha Pesa(...) mpya. |
__eq__ + kuweka objects kwenye set |
TypeError: unhashable type: 'Pesa' |
Ongeza __hash__ (mf. return hash((self.kiasi, self.sarafu))). |
Zoezi darasani
Zoezi 2.1: Abstract class Malipo yenye @abstractmethod lipa(kiasi). Subclasses MPesa (ada 1%), Kadi (ada 2.5%), Taslimu (bila ada); kila lipa irudishe string ya maelezo pamoja na ada. Andika function lipia_wote(njia, kiasi) inayozunguka list ya njia.
Zoezi 2.2: Class Vekta (x, y) yenye __add__, __eq__, __repr__, na method urefu() (√(x²+y²)). Swali la kufikiri: kwa nini tusitumie __len__ kwa urefu wa vekta? (Jibu: __len__ lazima irudishe int isiyo hasi; urefu wa vekta ni float, mf. 2.236.)
Zoezi 2.3: Ongeza __len__, __contains__ (kwa jina la bidhaa) kwenye Duka la Wiki 6.
Jibu
# 2.1 from abc import ABC, abstractmethod class Malipo(ABC): ADA = 0.0 @abstractmethod def lipa(self, kiasi): ... def ada(self, kiasi): return round(kiasi * self.ADA) class MPesa(Malipo): ADA = 0.01 def lipa(self, kiasi): return f"M-Pesa: {kiasi:,} + ada {self.ada(kiasi):,}" class Kadi(Malipo): ADA = 0.025 def lipa(self, kiasi): return f"Kadi: {kiasi:,} + ada {self.ada(kiasi):,}" class Taslimu(Malipo): def lipa(self, kiasi): return f"Taslimu: {kiasi:,} (hakuna ada)" def lipia_wote(njia, kiasi): for n in njia: print(n.lipa(kiasi)) lipia_wote([MPesa(), Kadi(), Taslimu()], 20_000) # M-Pesa: 20,000 + ada 200 # Kadi: 20,000 + ada 500 # Taslimu: 20,000 (hakuna ada) # 2.2 import math class Vekta: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): if not isinstance(other, Vekta): return NotImplemented return Vekta(self.x + other.x, self.y + other.y) def __eq__(self, other): if not isinstance(other, Vekta): return NotImplemented return (self.x, self.y) == (other.x, other.y) def __repr__(self): return f"Vekta({self.x}, {self.y})" def urefu(self): return math.hypot(self.x, self.y) v = Vekta(1, 2) + Vekta(2, 2) print(v, v == Vekta(3, 4), v.urefu()) # Vekta(3, 4) True 5.0 # 2.3 -- ndani ya class Duka (Wiki 6) def __len__(self): return len(self._bidhaa) def __contains__(self, jina): return jina.lower() in self._bidhaa # len(duka) -> 3 ; "sukari" in duka -> True
Kipindi 3 — Dataclasses na Type Hints
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani (Pesa / Malipo)
- Type hints: kwa nini, syntax,
list[int],dict[str, int],Optional,| @dataclass— kuandika class fupi;field(default_factory=list);order=True;frozen=True- Mapumziko
- Mini project: Mfumo wa wafanyakazi/mishahara (inheritance + abc + dataclass + hints)
- Muhtasari wa OOP (Wiki 6–7)
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
Type hints ni maelezo yanayosema aina ya data inayotarajiwa:
def salimia(jina: str, mara: int = 1) -> str: return ("Habari " + jina + "! ") * mara
jina: str— parameter inatarajiwa kuwa str.-> str— function inarudisha str.- Python HAIKAGUI wakati wa kuendesha!
salimia(5)bado itaendeshwa (na kuleta kosa la kawaida ndani). Faida ziko kwa: (1) wasomaji wa code, (2) VS Code (autocomplete, mistari ya onyo ya Pylance), (3) zana kamamypy. - Aina za makusanyo (Python 3.9+):
list[int],dict[str, float],tuple[int, str],set[str]. - Thamani inayoweza kuwa None:
str | None(Python 3.10+) — sawa kabisa naOptional[str]kutokatyping. Tumia|kwa code mpya; juaOptionalkwa sababu utaiona kwenye code za watu wengine. - Aina nyingi:
int | float. - Variables:
alama: list[int] = []. - Function isiyorudisha kitu:
-> None.
Pylance (ndani ya VS Code) inaonyesha makosa ya aina ukiwasha: Settings → tafuta "Type Checking Mode" → basic. Hiari.
@dataclass (kutoka dataclasses) — decorator inayoandika __init__, __repr__, na __eq__ yenyewe kutokana na type hints za class attributes:
from dataclasses import dataclass @dataclass class Bidhaa: jina: str bei: int idadi: int = 0
Hii ni sawa na kuandika __init__(self, jina, bei, idadi=0), __repr__ na __eq__ kwa mkono! Hapa type hints zinahitajika (ndio zinazoiambia dataclass ni fields zipi).
Sheria/mitego:
- Fields zenye default lazima zije baada ya zisizo na default (kama function parameters), vinginevyo:
TypeError: non-default argument 'bei' follows default argument 'idadi'. - Default ya list/dict: huwezi kuandika
vitu: list[str] = []→ValueError: mutable default <class 'list'> for field vitu is not allowed: use default_factory. Tumiafield(default_factory=list). @dataclass(order=True)→ inaongeza<, >, <=, >=(inalinganisha fields kwa mpangilio wake) —sorted()inafanya kazi.@dataclass(frozen=True)→ object haliwezi kubadilishwa baada ya kuundwa (FrozenInstanceError); pia linakuwa hashable.__post_init__(self)→ inaitwa baada ya__init__iliyoundwa; nzuri kwa validation.- Dataclass bado ni class ya kawaida: unaweza kuongeza methods,
@property, na kurithi. dataclasses.asdict(obj)→ dict (nzuri kwa JSON — tutatumia Wiki 8!).
Sema hivi kwa mwanafunzi
- "Type hints ni kama lebo kwenye droo: 'Visu', 'Vijiko'. Lebo hazizuii mtu kuweka kijiko kwenye droo ya visu, lakini zinasaidia kila mtu kujua nini kiende wapi."
- "
@dataclassni kama fomu iliyochapishwa tayari. Badala ya kuchora mistari ya fomu kwa mkono kila mara (__init__,__repr__), unaandika tu majina ya sehemu na printer inafanya mengine."
Andika code hii live (share screen)
Hatua 1: Type hints — hints.py
def wastani(alama: list[int]) -> float: return sum(alama) / len(alama) def tafuta_simu(jina: str, kitabu: dict[str, str]) -> str | None: return kitabu.get(jina) def salimia(jina: str, cheo: str | None = None) -> str: if cheo: return f"Habari {cheo} {jina}" return f"Habari {jina}" print(wastani([80, 70, 90])) kitabu = {"Asha": "0712000000"} print(tafuta_simu("Asha", kitabu)) print(tafuta_simu("Juma", kitabu)) print(salimia("Juma"), "|", salimia("Neema", "Dkt.")) print(wastani.__annotations__)
Output:
80.0
0712000000
None
Habari Juma | Habari Dkt. Neema
{'alama': list[int], 'return': <class 'float'>}
Kisha onyesha Python haikagui: print(salimia(123)) → Habari 123 (inafanya kazi!). Sema: "Hints ni ushauri, si sheria — lakini VS Code inakuonyesha." Elea juu ya wastani kwenye VS Code uonyeshe tooltip.
Onyesha Optional:
from typing import Optional def tafuta(jina: str) -> Optional[int]: # sawa na: int | None return None
(Hiari, kama mypy imesakinishwa kwenye venv):
python -m pip install mypy python -m mypy hints.py
Ukiongeza salimia(123), mypy itasema: error: Argument 1 to "salimia" has incompatible type "int"; expected "str" [arg-type].
Hatua 2: Class ya kawaida vs dataclass — dc_demo.py
from dataclasses import dataclass, field, asdict class BidhaaYaZamani: def __init__(self, jina: str, bei: int, idadi: int = 0): self.jina = jina self.bei = bei self.idadi = idadi def __repr__(self): return f"BidhaaYaZamani(jina={self.jina!r}, bei={self.bei!r}, idadi={self.idadi!r})" def __eq__(self, other): if not isinstance(other, BidhaaYaZamani): return NotImplemented return (self.jina, self.bei, self.idadi) == (other.jina, other.bei, other.idadi) @dataclass class Bidhaa: jina: str bei: int idadi: int = 0 @property def thamani(self) -> int: return self.bei * self.idadi b = Bidhaa("Sukari", 3000, 10) print(b) print(b == Bidhaa("Sukari", 3000, 10)) print(b.thamani) print(asdict(b))
Output:
Bidhaa(jina='Sukari', bei=3000, idadi=10)
True
30000
{'jina': 'Sukari', 'bei': 3000, 'idadi': 10}
Sema: "Mistari 14 imekuwa 4!"
Hatua 3: default_factory, order, frozen, __post_init__
from dataclasses import dataclass, field @dataclass class Mwanafunzi: jina: str alama: list[int] = field(default_factory=list) def __post_init__(self): if not self.jina.strip(): raise ValueError("Jina haliwezi kuwa tupu.") def wastani(self) -> float: return sum(self.alama) / len(self.alama) if self.alama else 0.0 a = Mwanafunzi("Asha") j = Mwanafunzi("Juma") a.alama.append(90) print(a, j) # alama za Juma bado ni tupu try: Mwanafunzi(" ") except ValueError as e: print(e) @dataclass(order=True) class Toleo: kuu: int ndogo: int matoleo = [Toleo(3, 12), Toleo(3, 9), Toleo(2, 7)] print(sorted(matoleo)) print(Toleo(3, 12) > Toleo(3, 9)) @dataclass(frozen=True) class Nukta: x: float y: float n = Nukta(1, 2) try: n.x = 5 except Exception as e: print(type(e).__name__, ":", e) print({n, Nukta(1, 2)}) # frozen => hashable, set ina moja tu
Output:
Mwanafunzi(jina='Asha', alama=[90]) Mwanafunzi(jina='Juma', alama=[])
Jina haliwezi kuwa tupu.
[Toleo(kuu=2, ndogo=7), Toleo(kuu=3, ndogo=9), Toleo(kuu=3, ndogo=12)]
True
FrozenInstanceError : cannot assign to field 'x'
{Nukta(x=1, y=2)}
Onyesha mtego: badilisha field(default_factory=list) kuwa = [] → ValueError: mutable default <class 'list'> for field alama is not allowed: use default_factory.
Hatua 4: Mini project — Mishahara — mishahara.py (kila kitu cha Wiki 6–7 pamoja)
from abc import ABC, abstractmethod from dataclasses import dataclass, field @dataclass class Mfanyakazi(ABC): jina: str idara: str @abstractmethod def mshahara(self) -> int: ... def __str__(self) -> str: return f"{self.jina:<10}{self.idara:<10}{type(self).__name__:<12}{self.mshahara():>12,}" @dataclass class WaKudumu(Mfanyakazi): mshahara_wa_mwezi: int = 0 def mshahara(self) -> int: return self.mshahara_wa_mwezi @dataclass class WaSaa(Mfanyakazi): saa: int = 0 kiwango: int = 0 def mshahara(self) -> int: return self.saa * self.kiwango @dataclass class Kampuni: jina: str wafanyakazi: list[Mfanyakazi] = field(default_factory=list) def ajiri(self, m: Mfanyakazi) -> None: self.wafanyakazi.append(m) def __len__(self) -> int: return len(self.wafanyakazi) def jumla_ya_mishahara(self) -> int: return sum(m.mshahara() for m in self.wafanyakazi) def wa_idara(self, idara: str) -> list[Mfanyakazi]: return [m for m in self.wafanyakazi if m.idara == idara] def ripoti(self) -> None: print(f"=== {self.jina} ({len(self)} wafanyakazi) ===") for m in self.wafanyakazi: print(m) print(f"{'JUMLA':<32}{self.jumla_ya_mishahara():>12,}") k = Kampuni("Python Tech Ltd") k.ajiri(WaKudumu("Asha", "IT", 1_200_000)) k.ajiri(WaKudumu("Juma", "Mauzo", 900_000)) k.ajiri(WaSaa("Baraka", "IT", saa=80, kiwango=6_000)) k.ripoti() print("IT:", [m.jina for m in k.wa_idara("IT")])
Output:
=== Python Tech Ltd (3 wafanyakazi) === Asha IT WaKudumu 1,200,000 Juma Mauzo WaKudumu 900,000 Baraka IT WaSaa 480,000 JUMLA 2,580,000 IT: ['Asha', 'Baraka']
Maelezo kwa mwalimu: Dataclass ya child inapanga fields hivi: kwanza za parent (jina, idara), kisha zake (saa, kiwango). Kama parent angekuwa na field yenye default, field yoyote ya child isiyo na default ingeleta TypeError: non-default argument 'saa' follows default argument. Ndiyo maana tumeweka = 0 kwenye fields za child — ni mazoea salama. Pia @dataclass haiongezi __str__, kwa hiyo __str__ yetu inatumika na print, wakati __repr__ ni ya dataclass. Comprehensions ([m.jina for m in ...]) ni Wiki 8; ni sawa kuzitumia kama "onyesho la mbele".
Maswali ya kumuuliza mwanafunzi
- Je, Python inazuia
salimia(123)kamajina: str? — Hapana; hints hazikaguliwi wakati wa kuendesha. str | NonenaOptional[str]— tofauti? — Hakuna; ni sawa.|ni syntax mpya (3.10+).@dataclassinatuandikia methods gani? —__init__,__repr__,__eq__.- Kwa nini
alama: list[int] = []inakataliwa? — Objects zote zingeshiriki list moja (mtego wa Wiki 6); tumiafield(default_factory=list). order=Trueinaongeza nini? — Ulinganisho<,>, n.k., hivyosorted()inafanya kazi.frozen=Trueinafanya nini? — Object haliwezi kubadilishwa baada ya kuundwa.- Ni ipi ya kutumia: class ya kawaida au dataclass? — Dataclass kama class inahifadhi data hasa; class ya kawaida kama kuna logic nyingi ya
__init__au encapsulation kali.
Makosa ya kawaida
| Kosa | Ujumbe wa error | Suluhisho |
|---|---|---|
Field isiyo na type hint kwenye dataclass: jina = "x" |
Haiwi field (haipo kwenye __init__): TypeError: Bidhaa.__init__() takes ... positional arguments |
Andika jina: str = "x" |
| Default kabla ya isiyo na default | TypeError: non-default argument 'bei' follows default argument 'idadi' |
Weka fields zenye default mwishoni. |
| List kama default | ValueError: mutable default <class 'list'> for field alama is not allowed: use default_factory |
field(default_factory=list) |
Kusahau from dataclasses import dataclass |
NameError: name 'dataclass' is not defined |
Import. |
| Kubadilisha frozen dataclass | dataclasses.FrozenInstanceError: cannot assign to field 'x' |
Unda object jipya: dataclasses.replace(n, x=5) |
list[int] kwenye Python ya zamani (<3.9) |
TypeError: 'type' object is not subscriptable |
Tumia Python 3.12 (au from typing import List). |
Zoezi darasani
Zoezi 3.1: Ongeza type hints kwenye functions 3 ulizoandika Wiki 4–5 (mf. angalia_alama, hifadhi_mawasiliano).
Zoezi 3.2: Dataclass Kitabu (isbn: str, kichwa: str, mwandishi: str, mwaka: int, kimeazimwa: bool = False). Dataclass Rafu yenye vitabu: list[Kitabu] (default_factory), methods ongeza(k), tafuta_mwandishi(jina) -> list[Kitabu], __len__. (Hii ni maandalizi ya mradi wa Wiki 8!)
Zoezi 3.3: Badilisha Pesa ya Kipindi 2 kuwa @dataclass(frozen=True, order=True) na uongeze __add__ tu. Je, bado unahitaji kuandika __eq__ na __lt__?
Jibu
# 3.1 (mifano) def angalia_alama(alama: int) -> str: ... def hifadhi_mawasiliano(jina: str, simu: str) -> None: ... def bei_ya(bidhaa: str, orodha: dict[str, int]) -> int: ... # 3.2 from dataclasses import dataclass, field @dataclass class Kitabu: isbn: str kichwa: str mwandishi: str mwaka: int kimeazimwa: bool = False @dataclass class Rafu: vitabu: list[Kitabu] = field(default_factory=list) def ongeza(self, k: Kitabu) -> None: self.vitabu.append(k) def tafuta_mwandishi(self, jina: str) -> list[Kitabu]: matokeo = [] for k in self.vitabu: if jina.lower() in k.mwandishi.lower(): matokeo.append(k) return matokeo def __len__(self) -> int: return len(self.vitabu) r = Rafu() r.ongeza(Kitabu("978-1", "Kinjeketile", "Ebrahim Hussein", 1969)) r.ongeza(Kitabu("978-2", "Rosa Mistika", "Euphrase Kezilahabi", 1971)) print(len(r)) print(r.tafuta_mwandishi("kezilahabi")) # 3.3 from dataclasses import dataclass @dataclass(frozen=True, order=True) class Pesa: kiasi: int sarafu: str = "TZS" def __add__(self, other: "Pesa") -> "Pesa": if not isinstance(other, Pesa): return NotImplemented if self.sarafu != other.sarafu: raise ValueError("Sarafu tofauti") return Pesa(self.kiasi + other.kiasi, self.sarafu) print(Pesa(100) + Pesa(50)) # Pesa(kiasi=150, sarafu='TZS') print(Pesa(100) == Pesa(100)) # True print(sorted([Pesa(5), Pesa(1)])) # [Pesa(kiasi=1, ...), Pesa(kiasi=5, ...)] # Hapana: dataclass inatengeneza __eq__ na __lt__ yenyewe. # (Tahadhari: order=True inalinganisha (kiasi, sarafu) kama tuple.)
"Pesa" kwenye quotes: ndani ya class, jina Pesa bado halijakamilika, kwa hiyo tunaliandika kama string (forward reference). Taja tu.
Kazi ya Nyumbani
(Inatakiwa: kipindi kijacho)
Kazi 1 — Maumbo (baada ya Kipindi 1): Class Umbo (bila abc bado) yenye eneo() inayorudisha 0. Subclasses Mraba(upande), Pembetatu(kitako, kimo), Duara(r) zikioveride eneo(). Tengeneza list ya maumbo 5 na uchapishe eneo la jumla.
Kazi 2 — Mfumo wa wanyama wa shamba (baada ya Kipindi 2): Abstract class Mnyama (jina, abstract toa_sauti() na chakula()), subclasses 3. Class Shamba yenye __len__, __contains__ (kwa jina), __add__ (kuunganisha mashamba mawili), na method wakati_wa_kula() inayozunguka wanyama wote (polymorphism).
Kazi 3 — Rafu ya vitabu (baada ya Kipindi 3): Endeleza Zoezi 3.2: ongeza kwenye Rafu methods azima(isbn) -> None (rusha custom exception KitabuHakipo au KimeshaAzimwa), rudisha(isbn), vinavyopatikana() -> list[Kitabu]. Type hints kila mahali. Hii itakuwa msingi wa Mradi wa Mwezi 2 — iletwe!
Jibu
# Kazi 1 import math class Umbo: def eneo(self) -> float: return 0 class Mraba(Umbo): def __init__(self, upande: float): self.upande = upande def eneo(self) -> float: return self.upande ** 2 class Pembetatu(Umbo): def __init__(self, kitako: float, kimo: float): self.kitako = kitako self.kimo = kimo def eneo(self) -> float: return 0.5 * self.kitako * self.kimo class Duara(Umbo): def __init__(self, r: float): self.r = r def eneo(self) -> float: return math.pi * self.r ** 2 maumbo = [Mraba(2), Mraba(3), Pembetatu(4, 5), Duara(1), Duara(2)] jumla = 0 for u in maumbo: jumla += u.eneo() print(f"Eneo la jumla: {jumla:.2f}") # 4+9+10+3.14+12.57 = 38.71
# Kazi 2 from abc import ABC, abstractmethod class Mnyama(ABC): def __init__(self, jina: str): self.jina = jina @abstractmethod def toa_sauti(self) -> str: ... @abstractmethod def chakula(self) -> str: ... class Ngombe(Mnyama): def toa_sauti(self): return "Mooo" def chakula(self): return "majani" class Kuku(Mnyama): def toa_sauti(self): return "Kokoriko" def chakula(self): return "mahindi" class Mbuzi(Mnyama): def toa_sauti(self): return "Meee" def chakula(self): return "majani na matawi" class Shamba: def __init__(self, jina: str): self.jina = jina self.wanyama: list[Mnyama] = [] def ongeza(self, m: Mnyama) -> None: self.wanyama.append(m) def __len__(self) -> int: return len(self.wanyama) def __contains__(self, jina: str) -> bool: return any(m.jina == jina for m in self.wanyama) def __add__(self, other: "Shamba") -> "Shamba": mpya = Shamba(f"{self.jina} & {other.jina}") mpya.wanyama = self.wanyama + other.wanyama return mpya def wakati_wa_kula(self) -> None: for m in self.wanyama: print(f"{m.jina} ({type(m).__name__}) anasema {m.toa_sauti()} na anakula {m.chakula()}") a = Shamba("Shamba la Babu") a.ongeza(Ngombe("Bahati")) a.ongeza(Kuku("Kiroboto")) b = Shamba("Shamba la Mama") b.ongeza(Mbuzi("Zawadi")) c = a + b print(len(c), "Zawadi" in c, "Simba" in c) # 3 True False c.wakati_wa_kula()
# Kazi 3 from dataclasses import dataclass, field class KosaLaMaktaba(Exception): pass class KitabuHakipo(KosaLaMaktaba): pass class KimeshaAzimwa(KosaLaMaktaba): pass @dataclass class Kitabu: isbn: str kichwa: str mwandishi: str mwaka: int kimeazimwa: bool = False @dataclass class Rafu: vitabu: list[Kitabu] = field(default_factory=list) def ongeza(self, k: Kitabu) -> None: self.vitabu.append(k) def _tafuta(self, isbn: str) -> Kitabu: for k in self.vitabu: if k.isbn == isbn: return k raise KitabuHakipo(f"Hakuna kitabu chenye ISBN {isbn}") def azima(self, isbn: str) -> None: k = self._tafuta(isbn) if k.kimeazimwa: raise KimeshaAzimwa(f"'{k.kichwa}' kimeshaazimwa.") k.kimeazimwa = True def rudisha(self, isbn: str) -> None: k = self._tafuta(isbn) k.kimeazimwa = False def vinavyopatikana(self) -> list[Kitabu]: return [k for k in self.vitabu if not k.kimeazimwa] def __len__(self) -> int: return len(self.vitabu) r = Rafu() r.ongeza(Kitabu("978-1", "Kinjeketile", "Ebrahim Hussein", 1969)) r.ongeza(Kitabu("978-2", "Rosa Mistika", "Euphrase Kezilahabi", 1971)) r.azima("978-1") try: r.azima("978-1") except KosaLaMaktaba as e: print(e) # 'Kinjeketile' kimeshaazimwa. print([k.kichwa for k in r.vinavyopatikana()]) # ['Rosa Mistika']
Ukiulizwa swali usilolijua
- Sema: "Hilo ni swali la kina — nitalichunguza na kukuletea jibu Kipindi 2/Kipindi 3." Kwenye REPL jaribu
help(dataclass),Meneja.__mro__,dir(obj). - Tafuta: "python super() explained", "python abstract class example", "python dataclass default_factory". Docs: inheritance https://docs.python.org/3/tutorial/classes.html#inheritance | abc https://docs.python.org/3/library/abc.html | dataclasses https://docs.python.org/3/library/dataclasses.html | typing https://docs.python.org/3/library/typing.html | data model (dunders) https://docs.python.org/3/reference/datamodel.html#special-method-names
- Kwa maswali ya multiple inheritance au metaclasses: sema ni mada ya juu zaidi ya kozi hii — si lazima uyajue sasa.
Checklist ya mwisho wa wiki
- Anaunda subclass na kutumia
super().__init__()kwa usahihi. - Anaoveride method na kuita toleo la parent kwa
super(). - Anatumia
isinstancena anaelewa kwa niniexcept Exceptioninashika custom exceptions. - Anaeleza polymorphism na duck typing kwa mfano.
- Anaunda abstract class kwa
ABC+@abstractmethodna anaelewa kosa la kuiunda moja kwa moja. - Anaandika
__len__,__eq__,__add__(na anajuaNotImplemented). - Anatumia
@dataclass,field(default_factory=list),order=True,frozen=True. - Anaandika type hints:
list[int],dict[str, int],str | None,-> None. - Anaelewa kuwa Python haikagui type hints wakati wa kuendesha.
- Kazi 3 (Rafu ya vitabu) imekamilika — itatumika kwenye mradi wa Wiki 8.