Wiki 6: OOP Sehemu ya 1 — Classes na Objects
Mwezi 2 | Lengo kuu: Mwanafunzi aelewe na atumie classes, objects,
__init__,self, methods, class vs instance attributes, encapsulation (_private,@property) na__str__/__repr__, na kujenga mini project ya BankAccount na Duka.
Maandalizi ya Mwalimu (soma kabla ya wiki kuanza)
- Soma: Python Tutorial — "Classes", sehemu 9.1–9.4 tu: https://docs.python.org/3/tutorial/classes.html . Usijali sehemu za inheritance/iterators — ni Wiki 7–8.
- Zoezi binafsi: Andika class ya
MbwanaAkauntiYaBenkimwenyewe mara mbili bila kuangalia, mpaka ueleweselfni nini. Endesha kila mfano wa "Andika code hii live". - Wazo kuu la kukumbuka: Class ni ramani (blueprint); object ni kitu halisi kilichojengwa kwa ramani hiyo. Ukielewa hili, mengine yote yanafuata.
- Andaa folder:
python_kozi\wiki06\. Hakuna package mpya ya kusakinisha. - Tumia Python Tutor (hiari): https://pythontutor.com — inaonyesha objects kwa michoro; nzuri kuonyesha
selfkwa mwanafunzi.
Malengo ya Wiki (mwisho wa wiki mwanafunzi ataweza...)
- Kueleza class, object (instance), attribute, na method kwa maneno yake mwenyewe.
- Kuandika class yenye
__init__na kutengeneza objects kadhaa. - Kueleza
selfni nini na kwa nini ni parameter ya kwanza ya kila method. - Kutofautisha class attributes na instance attributes.
- Kulinda data kwa
_jina(convention) na@property/ setter yenye validation. - Kuandika
__str__na__repr__ili objects zichapishwe vizuri. - Kujenga mfumo mdogo (BankAccount, Duka) wenye classes mbili zinazoshirikiana.
Kipindi 1 — Classes, Objects, __init__ na self
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani ya Wiki 5 (word counter, daftari la alama)
- Kwa nini OOP? Tatizo la dicts nyingi + functions zilizotawanyika
- Class ya kwanza, objects, attributes
__init__- Mapumziko
- Methods na
self - Zoezi darasani
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
OOP (Object-Oriented Programming) ni mtindo wa kupanga code ambapo tunaunganisha data (attributes) na tabia (methods) zinazohusiana ndani ya kitu kimoja — object.
Kumbuka Wiki 5: tulikuwa na dict ya mwanafunzi {"jina": "Asha", "alama": 88} na functions tofauti kama ongeza_alama(mwanafunzi, n). Hiyo inafanya kazi, lakini programu ikikua: dict zinaweza kuwa na keys zilizokosewa ("jna"), hakuna mahali pamoja panaposema "mwanafunzi ana nini na anaweza kufanya nini". Class inatatua hilo.
Maneno muhimu:
- class — ramani/kiolezo. Kwa kawaida jina linaanza na herufi kubwa:
Mwanafunzi,AkauntiYaBenki(PascalCase). - object / instance — kitu halisi kilichoundwa kutoka class:
asha = Mwanafunzi("Asha"). - attribute — data ya object:
asha.jina. - method — function iliyo ndani ya class:
asha.salimia(). __init__— method maalum inayoitwa yenyewe object linapoundwa. Kazi yake: kuweka attributes za mwanzo. (Si "constructor" kamili kwa maana ya kiufundi, lakini ni sawa kuiita hivyo darasani.)self— object lenyewe. Ukiandikaasha.salimia(), Python inafanyaMwanafunzi.salimia(asha)— kwa hiyoselfndani ya method niasha. Ndiyo maana kila method inaselfkama parameter ya kwanza, lakini hatuipitishi sisi tunapoita.
Jina self si keyword — ni convention tu, lakini tumia self daima.
Sema hivi kwa mwanafunzi
- "Class ni ramani ya nyumba ambayo fundi anayo. Ramani moja inaweza kujenga nyumba 100. Kila nyumba (object) ina rangi yake, mmiliki wake, lakini zote zimejengwa kwa ramani moja."
- "Fikiria fomu ya kujiunga na shule. Fomu tupu ni class. Kila fomu iliyojazwa — ya Asha, ya Juma — ni object. Sehemu za fomu (jina, umri) ni attributes."
- "
selfni neno 'mimi'. Asha akisema 'jina langu ni...' anazungumza kuhusu yeye. Juma akisema maneno yale yale, anazungumza kuhusu Juma. Method moja, lakiniselfinabadilika kulingana na nani anaiita." - "
__init__ni kama siku ya kuzaliwa: mtoto anapewa jina na tarehe ya kuzaliwa papo hapo."
Andika code hii live (share screen)
Hatua 1: Tatizo — kwa_nini_oop.py
asha = {"jina": "Asha", "alama": 88} juma = {"jna": "Juma", "alama": 70} # kosa la kuandika! Python haitusaidii def salimia(mwanafunzi): print("Habari, mimi ni", mwanafunzi["jina"]) salimia(asha) salimia(juma) # KeyError: 'jina'
Sema: "Dict haijui mwanafunzi anatakiwa awe na nini. Tutatengeneza 'ramani'."
Hatua 2: Class tupu na attributes — class_ya_kwanza.py
class Mwanafunzi: pass a = Mwanafunzi() b = Mwanafunzi() a.jina = "Asha" b.jina = "Juma" print(a.jina, b.jina) print(type(a)) print(a is b)
Output:
Asha Juma <class '__main__.Mwanafunzi'> False
Sema: "Objects mbili tofauti kutoka ramani moja."
Hatua 3: __init__
class Mwanafunzi: def __init__(self, jina, alama): self.jina = jina self.alama = alama asha = Mwanafunzi("Asha", 88) juma = Mwanafunzi("Juma", 70) print(asha.jina, asha.alama) print(juma.jina, juma.alama)
Eleza kwa kuchora (au kwa maneno): Mwanafunzi("Asha", 88) → Python inaunda object tupu → inaita __init__(object_huo, "Asha", 88) → self.jina = "Asha" inaweka attribute kwenye object huo → object linarudishwa na kuwekwa kwenye asha.
Onyesha kosa: Mwanafunzi("Asha") → TypeError: Mwanafunzi.__init__() missing 1 required positional argument: 'alama'. "Sasa Python inatulinda!"
Hatua 4: Methods
class Mwanafunzi: def __init__(self, jina, alama=0): self.jina = jina self.alama = alama def salimia(self): print(f"Habari, mimi ni {self.jina}.") def ongeza_alama(self, kiasi): self.alama += kiasi def amefaulu(self): return self.alama >= 50 asha = Mwanafunzi("Asha", 45) asha.salimia() print(asha.amefaulu()) asha.ongeza_alama(10) print(asha.alama, asha.amefaulu()) juma = Mwanafunzi("Juma") juma.salimia() print(juma.alama)
Output:
Habari, mimi ni Asha. False 55 True Habari, mimi ni Juma. 0
Onyesha Mwanafunzi.salimia(asha) inatoa matokeo yale yale kama asha.salimia() — "hii ndiyo siri ya self".
Hatua 5: Objects kwenye list
darasa = [Mwanafunzi("Asha", 88), Mwanafunzi("Juma", 42), Mwanafunzi("Neema", 67)] for m in darasa: hali = "amefaulu" if m.amefaulu() else "hajafaulu" print(f"{m.jina}: {m.alama} - {hali}")
Maswali ya kumuuliza mwanafunzi
- Tofauti ya class na object? — Class ni ramani; object ni kitu halisi kilichoundwa nayo.
__init__inaitwa lini? — Yenyewe, kila object jipya linapoundwa.- Kwenye
asha.salimia(),selfni nani? —asha. - Kwa nini
self.jina = jinana sijina = jinatu? —jina = jinani local variable inayopotea function ikiisha;self.jinainahifadhiwa kwenye object. - Method ni nini? — Function iliyo ndani ya class inayofanya kazi na data ya object.
- Kwa nini
asha.alamanajuma.alamahazichanganyiki? — Kila object lina attributes zake.
Makosa ya kawaida
| Kosa | Ujumbe wa error | Suluhisho |
|---|---|---|
Kusahau self kwenye method |
TypeError: Mwanafunzi.salimia() takes 0 positional arguments but 1 was given |
def salimia(self): |
Kusahau self. ndani ya method |
NameError: name 'jina' is not defined |
self.jina |
Kuandika __init au _init_ (underscore moja) |
TypeError: Mwanafunzi() takes no arguments |
Underscore mbili kila upande: __init__ |
Kuita method bila () |
Inachapisha <bound method Mwanafunzi.salimia of ...> |
asha.salimia() |
Kupitisha self mwenyewe: asha.salimia(asha) |
TypeError: ... takes 1 positional argument but 2 were given |
Usipitishe self; Python inafanya. |
| Indentation: method nje ya class | NameError au method haipo: AttributeError: 'Mwanafunzi' object has no attribute 'salimia' |
Methods ziwe ndani (indented) ya class. |
Zoezi darasani
Zoezi 1.1: Tengeneza class Gari yenye attributes aina, rangi, kasi (default 0). Methods: ongeza_kasi(kiasi), punguza_kasi(kiasi) (kasi isishuke chini ya 0), taarifa() inayochapisha hali ya gari.
Zoezi 1.2: Tengeneza class Mstatili (rectangle) yenye urefu na upana, na methods eneo() na mzunguko(). Tengeneza mistatili 3 kwenye list, chapisha eneo la kila mmoja na lile kubwa zaidi.
Zoezi 1.3: Class Kikapu (shopping basket): __init__ inaanza na list tupu self.vitu. Methods: ongeza(jina, bei), jumla(), onyesha().
Jibu
# 1.1 class Gari: def __init__(self, aina, rangi, kasi=0): self.aina = aina self.rangi = rangi self.kasi = kasi def ongeza_kasi(self, kiasi): self.kasi += kiasi def punguza_kasi(self, kiasi): self.kasi = max(0, self.kasi - kiasi) def taarifa(self): print(f"{self.aina} ya rangi {self.rangi} inaenda {self.kasi} km/h") g = Gari("Toyota IST", "nyeupe") g.ongeza_kasi(60) g.punguza_kasi(100) g.taarifa() # Toyota IST ya rangi nyeupe inaenda 0 km/h # 1.2 class Mstatili: 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) mistatili = [Mstatili(2, 3), Mstatili(5, 4), Mstatili(1, 10)] kubwa = mistatili[0] for m in mistatili: print(f"{m.urefu}x{m.upana}: eneo={m.eneo()}, mzunguko={m.mzunguko()}") if m.eneo() > kubwa.eneo(): kubwa = m print("Kubwa zaidi:", kubwa.urefu, "x", kubwa.upana) # 5 x 4 # 1.3 class Kikapu: def __init__(self): self.vitu = [] def ongeza(self, jina, bei): self.vitu.append((jina, bei)) def jumla(self): total = 0 for _, bei in self.vitu: total += bei return total def onyesha(self): for jina, bei in self.vitu: print(f"{jina:<10}{bei:>8,}") print(f"{'JUMLA':<10}{self.jumla():>8,}") k = Kikapu() k.ongeza("Sukari", 3000) k.ongeza("Mafuta", 7500) k.onyesha()
Kipindi 2 — Class vs Instance Attributes, Encapsulation na @property
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani (class
Simu) - Class attributes vs instance attributes
- Encapsulation:
_salioconvention, kwa nini tulinde data - Mapumziko
@propertyna setter yenye validation- Zoezi darasani
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
Instance attribute — inawekwa kwa self.x = ... (kawaida ndani ya __init__), na ni ya object mmoja tu.
Class attribute — inawekwa moja kwa moja ndani ya class (nje ya method) na inashirikiwa na objects zote:
class Mwanafunzi: shule = "Python School" # class attribute idadi = 0 # class attribute def __init__(self, jina): self.jina = jina # instance attribute Mwanafunzi.idadi += 1
Python inapotafuta obj.x: inaangalia kwanza object, kisha class. Mtego: asha.shule = "Nyingine" HAIBADILISHI class attribute — inaunda instance attribute mpya kwa asha tu inayoficha ya class. Ili kubadilisha kwa wote: Mwanafunzi.shule = "...". Kwa sababu hiyo, kwenye __init__ tunaandika Mwanafunzi.idadi += 1, si self.idadi += 1.
Mtego mkubwa zaidi: usiweke list/dict kama class attribute kama unataka kila object awe na yake — wote watashiriki list moja!
Encapsulation = kuficha maelezo ya ndani na kudhibiti jinsi data inavyobadilishwa. Mfano: salio la benki lisiwe hasi; mtu asiandike akaunti.salio = -1000000.
Python haina private kamili kama Java. Ina conventions:
_salio(underscore moja) — "hii ni ya ndani, usiiguse kutoka nje". Bado inawezekana, lakini ni makubaliano kati ya waandishi wa code.__salio(underscore mbili, bila mbili mwishoni) — "name mangling": Python inaibadilisha jina kuwa_AkauntiYaBenki__salio, kwa hiyoobj.__salioinaletaAttributeError. Hutumiwa mara chache; tumia_moja kwa kozi hii. Taja tu.
@property inaturuhusu kuandika method lakini kuitumia kama attribute (bila ()):
@property def salio(self): # getter: akaunti.salio return self._salio
Na setter inaturuhusu kuweka validation pale mtu anapoandika obj.x = thamani:
@salio.setter def salio(self, thamani): if thamani < 0: raise ValueError(...) self._salio = thamani
Bila setter, property ni read-only: obj.salio = 5 → AttributeError: property 'salio' of 'Akaunti' object has no setter.
Property pia nzuri kwa thamani zinazokokotolewa (computed), mf. eneo ya mstatili: m.eneo badala ya m.eneo().
@property ni "decorator" — tutajifunza decorators kikamilifu Wiki 8. Kwa sasa: "ni alama inayobadilisha tabia ya function."
Sema hivi kwa mwanafunzi
- "Class attribute ni kama jina la shule kwenye sare — wanafunzi wote wanalo. Instance attribute ni jina la mwanafunzi — kila mmoja lake."
- "Encapsulation ni kama ATM. Huwezi kufungua mashine na kuchukua pesa mwenyewe; unatumia vitufe (methods) ambavyo vinaangalia PIN na salio kwanza."
- "
_salioni kama bango 'Wafanyakazi tu' mlangoni. Mlango haujafungwa kwa kufuli, lakini watu wema wanaheshimu." - "
@propertyni kama mhudumu wa benki: unauliza 'salio langu ni ngapi?' naye anaangalia daftari la ndani na kukujibu. Ukitaka kubadilisha, lazima apitie sheria."
Andika code hii live (share screen)
Hatua 1: Class attributes — class_attr.py
class Mwanafunzi: shule = "Python School" idadi = 0 def __init__(self, jina): self.jina = jina Mwanafunzi.idadi += 1 a = Mwanafunzi("Asha") b = Mwanafunzi("Juma") print(a.shule, b.shule, Mwanafunzi.shule) print("Idadi:", Mwanafunzi.idadi) Mwanafunzi.shule = "Python Academy" print(a.shule, b.shule) a.shule = "Shule Binafsi" # mtego! print(a.shule, b.shule, Mwanafunzi.shule) print(a.__dict__) print(b.__dict__)
Output:
Python School Python School Python School
Idadi: 2
Python Academy Python Academy
Shule Binafsi Python Academy Python Academy
{'jina': 'Asha', 'shule': 'Shule Binafsi'}
{'jina': 'Juma'}
Eleza __dict__ — "inaonyesha attributes zilizo ndani ya object mwenyewe."
Hatua 2: Mtego wa list kama class attribute
class Kikapu: vitu = [] # MBAYA: list moja kwa wote def ongeza(self, kitu): self.vitu.append(kitu) k1 = Kikapu() k2 = Kikapu() k1.ongeza("embe") print(k2.vitu) # ['embe'] -- k2 hakununua chochote!
Rekebisha kwa kuweka self.vitu = [] ndani ya __init__.
Hatua 3: Tatizo bila encapsulation — akaunti.py
class Akaunti: def __init__(self, mmiliki, salio=0): self.mmiliki = mmiliki self.salio = salio a = Akaunti("Asha", 5000) a.salio = -1_000_000 # hakuna anayezuia! print(a.salio)
Hatua 4: _salio + methods + @property
class Akaunti: def __init__(self, mmiliki, salio=0): self.mmiliki = mmiliki self._salio = 0 self.weka(salio) # tumia validation hata mwanzoni @property def salio(self): return self._salio def weka(self, kiasi): if kiasi < 0: raise ValueError("Kiasi cha kuweka hakiwezi kuwa hasi.") self._salio += kiasi def toa(self, kiasi): if kiasi <= 0: raise ValueError("Kiasi lazima kiwe zaidi ya sifuri.") if kiasi > self._salio: raise ValueError(f"Salio halitoshi (salio: {self._salio}).") self._salio -= kiasi a = Akaunti("Asha", 5000) a.weka(2000) a.toa(1500) print(a.salio) # 5500 -- bila () try: a.toa(100_000) except ValueError as e: print("Kosa:", e) try: a.salio = 999_999 except AttributeError as e: print("Kosa:", e)
Output:
5500 Kosa: Salio halitoshi (salio: 5500). Kosa: property 'salio' of 'Akaunti' object has no setter
Hatua 5: Setter yenye validation — mtu.py
class Mtu: def __init__(self, jina, umri): self.jina = jina self.umri = umri # hii inaita setter hapa chini! @property def umri(self): return self._umri @umri.setter def umri(self, thamani): if not isinstance(thamani, int) or not 0 <= thamani <= 130: raise ValueError(f"Umri si sahihi: {thamani!r}") self._umri = thamani @property def ni_mtu_mzima(self): # computed, read-only return self.umri >= 18 m = Mtu("Juma", 17) print(m.umri, m.ni_mtu_mzima) m.umri = 18 print(m.umri, m.ni_mtu_mzima) try: m.umri = -3 except ValueError as e: print(e) try: Mtu("Neema", "kumi") except ValueError as e: print(e)
Output:
17 False 18 True Umri si sahihi: -3 Umri si sahihi: 'kumi'
Eleza !r — inaonyesha thamani kwa repr, ndiyo maana 'kumi' ina quotes.
Maswali ya kumuuliza mwanafunzi
- Class attribute inaandikwa wapi? — Ndani ya class, nje ya methods.
- Ukiandika
a.shule = "X", jeb.shuleinabadilika? — Hapana; inaundwa instance attribute yaatu. - Kwa nini
Mwanafunzi.idadi += 1na siself.idadi += 1? —self.idadi += 1ingeunda instance attribute kwa kila object, na hesabu ya class isingeongezeka. _salioinamaanisha nini? — Ni ya ndani; usiiguse moja kwa moja kutoka nje (convention).- Faida ya
@property? — Tunasoma kama attribute lakini tunadhibiti (read-only, au validation kwenye setter). - Kwa nini setter isiandike
self.umri = thamani? — Ingeita setter tena na tena →RecursionError. Tumiaself._umri.
Makosa ya kawaida
| Kosa | Ujumbe wa error | Suluhisho |
|---|---|---|
Setter inajiandikia yenyewe self.umri = thamani |
RecursionError: maximum recursion depth exceeded |
self._umri = thamani |
Kuita property kama method a.salio() |
TypeError: 'int' object is not callable |
a.salio bila () |
| Kuweka thamani kwenye property isiyo na setter | AttributeError: property 'salio' of 'Akaunti' object has no setter |
Tumia method (weka) au ongeza setter. |
| Jina la setter tofauti na la property | AttributeError au property inapotea |
Wote wawe na jina moja: @umri.setter def umri(...) |
| List kama class attribute | Objects zote zinashiriki data | self.vitu = [] ndani ya __init__ |
Kutumia __salio na kujaribu kusoma nje |
AttributeError: 'Akaunti' object has no attribute '__salio' |
Tumia _salio moja + property. |
Zoezi darasani
Zoezi 2.1: Class Bidhaa yenye class attribute VAT = 0.18 na instance attributes jina, bei. Property bei_na_vat (read-only). Kisha badilisha Bidhaa.VAT = 0.16 uone bidhaa zote zinabadilika.
Zoezi 2.2: Class Mstatili yenye properties urefu na upana (setter zisiruhusu ≤ 0 → ValueError), na property ya computed eneo.
Zoezi 2.3: Class Mwanafunzi inayohesabu ni wanafunzi wangapi wameundwa (idadi) na kuwa na property alama (0–100 tu).
Jibu
# 2.1 class Bidhaa: VAT = 0.18 def __init__(self, jina, bei): self.jina = jina self.bei = bei @property def bei_na_vat(self): return round(self.bei * (1 + Bidhaa.VAT)) s = Bidhaa("Sukari", 3000) m = Bidhaa("Mchele", 2500) print(s.bei_na_vat, m.bei_na_vat) # 3540 2950 Bidhaa.VAT = 0.16 print(s.bei_na_vat, m.bei_na_vat) # 3480 2900 # 2.2 class Mstatili: def __init__(self, urefu, upana): self.urefu = urefu self.upana = upana @property def urefu(self): return self._urefu @urefu.setter def urefu(self, v): if v <= 0: raise ValueError("Urefu lazima uwe > 0") self._urefu = v @property def upana(self): return self._upana @upana.setter def upana(self, v): if v <= 0: raise ValueError("Upana lazima uwe > 0") self._upana = v @property def eneo(self): return self.urefu * self.upana r = Mstatili(3, 4) print(r.eneo) # 12 r.upana = 10 print(r.eneo) # 30 try: r.urefu = 0 except ValueError as e: print(e) # Urefu lazima uwe > 0 # 2.3 class Mwanafunzi: idadi = 0 def __init__(self, jina, alama=0): self.jina = jina self.alama = alama Mwanafunzi.idadi += 1 @property def alama(self): return self._alama @alama.setter def alama(self, v): if not 0 <= v <= 100: raise ValueError("Alama ni 0 hadi 100") self._alama = v Mwanafunzi("Asha", 80) Mwanafunzi("Juma") print(Mwanafunzi.idadi) # 2
Kipindi 3 — __str__, __repr__ na Mini Project: BankAccount na Duka
Mtiririko wa kipindi
- Marudio ya kazi ya nyumbani (class
Joto) __str__na__repr__- Mini project A:
BankAccountkamili (historia ya miamala) — live - Mapumziko
- Mini project B:
Duka(inventory) — classes mbili zinazoshirikiana, mwanafunzi anaandika, wewe unaongoza - Muhtasari wa wiki — maneno 7 ya OOP
- Q&A + kazi ya nyumbani
Elezo kwa mwalimu (soma kwanza wewe)
Ukichapisha object bila maandalizi: print(a) → <__main__.Akaunti object at 0x0000021F...> — haina maana kwa mtumiaji. Tunaweza kuifundisha Python namna ya kuonyesha object kwa dunder methods ("double underscore", mf. __init__). Wiki 7 tutaona dunders zaidi.
__str__(self)— maandishi "mazuri" kwa mtumiaji. Yanatumiwa naprint(obj),str(obj), f-stringf"{obj}".__repr__(self)— maandishi ya "kiufundi" kwa developer, ikiwezekana yaonekane kama code inayoweza kuunda object hilo tena:Akaunti('Asha', 5000). Yanatumiwa na REPL, debugger, na ndani ya list:print([a, b])inatumia__repr__ya kila kitu!- Kama una
__repr__tu,printitaitumia pia. Kwa hiyo kama utaandika moja tu, andika__repr__. - Zote mbili lazima zirudishe (return) string, si
print.
Mini projects: lengo ni kuunganisha kila kitu cha wiki — __init__, methods, _private, @property, exceptions (Wiki 5), __str__, na objects ndani ya objects (Duka lina Bidhaa nyingi kwenye dict). Kumbuka: Duka ina bidhaa — hii inaitwa composition ("has-a"). Wiki ijayo tutaona inheritance ("is-a").
Sema hivi kwa mwanafunzi
- "
__str__ni kadi ya biashara unayompa mteja — nzuri na fupi.__repr__ni kitambulisho cha taifa (NIDA) — taarifa kamili kwa ofisi." - "Duka lina bidhaa nyingi — kama kabati lenye mitungi. Kabati ni object moja (
Duka), kila mtungi ni object (Bidhaa)."
Andika code hii live (share screen)
Hatua 1: Bila na kwa __str__/__repr__ — onyesha.py
class Bidhaa: def __init__(self, jina, bei): self.jina = jina self.bei = bei b = Bidhaa("Sukari", 3000) print(b) # <__main__.Bidhaa object at 0x...>
Ongeza:
class Bidhaa: def __init__(self, jina, bei): self.jina = jina self.bei = bei def __str__(self): return f"{self.jina} - TSh {self.bei:,}" def __repr__(self): return f"Bidhaa({self.jina!r}, {self.bei!r})" b = Bidhaa("Sukari", 3000) print(b) print(str(b)) print(repr(b)) print(f"Nimenunua {b}") print([b, Bidhaa("Chumvi", 800)])
Output:
Sukari - TSh 3,000
Sukari - TSh 3,000
Bidhaa('Sukari', 3000)
Nimenunua Sukari - TSh 3,000
[Bidhaa('Sukari', 3000), Bidhaa('Chumvi', 800)]
Sisitiza mstari wa mwisho: list inatumia __repr__.
Hatua 2: Mini project A — BankAccount — bank_account.py
from datetime import datetime class SalioHalitoshi(Exception): pass class BankAccount: benki = "Benki ya Python" _namba_inayofuata = 1001 def __init__(self, mmiliki, salio_la_mwanzo=0): self.mmiliki = mmiliki self.namba = BankAccount._namba_inayofuata BankAccount._namba_inayofuata += 1 self._salio = 0 self._miamala = [] if salio_la_mwanzo > 0: self.weka(salio_la_mwanzo) @property def salio(self): return self._salio def _rekodi(self, aina, kiasi): wakati = datetime.now().strftime("%Y-%m-%d %H:%M") self._miamala.append((wakati, aina, kiasi, self._salio)) def weka(self, kiasi): if kiasi <= 0: raise ValueError("Kiasi lazima kiwe zaidi ya sifuri.") self._salio += kiasi self._rekodi("WEKA", kiasi) def toa(self, kiasi): if kiasi <= 0: raise ValueError("Kiasi lazima kiwe zaidi ya sifuri.") if kiasi > self._salio: raise SalioHalitoshi( f"Unataka {kiasi:,} lakini salio ni {self._salio:,}." ) self._salio -= kiasi self._rekodi("TOA", kiasi) def hamisha(self, akaunti_nyingine, kiasi): self.toa(kiasi) # kama inashindwa, haiendelei akaunti_nyingine.weka(kiasi) def taarifa(self): print(f"--- Taarifa ya {self.mmiliki} ({self.namba}) ---") for wakati, aina, kiasi, salio in self._miamala: print(f"{wakati} {aina:<5}{kiasi:>10,} salio: {salio:>10,}") def __str__(self): return f"{self.mmiliki} ({self.namba}): TSh {self._salio:,}" def __repr__(self): return f"BankAccount({self.mmiliki!r}, {self._salio!r})" if __name__ == "__main__": asha = BankAccount("Asha", 50_000) juma = BankAccount("Juma") asha.toa(10_000) asha.hamisha(juma, 15_000) print(asha) print(juma) try: juma.toa(1_000_000) except SalioHalitoshi as e: print("Imeshindikana:", e) asha.taarifa()
Output (tarehe itatofautiana):
Asha (1001): TSh 25,000 Juma (1002): TSh 15,000 Imeshindikana: Unataka 1,000,000 lakini salio ni 15,000. --- Taarifa ya Asha (1001) --- 2026-12-11 10:15 WEKA 50,000 salio: 50,000 2026-12-11 10:15 TOA 10,000 salio: 40,000 2026-12-11 10:15 TOA 15,000 salio: 25,000
Eleza if __name__ == "__main__": (marudio ya Wiki 4 — modules): code hii inaendeshwa tu tukiendesha file hili moja kwa moja, si tukilifanya import.
Hatua 3: Mini project B — Duka. Mwambie mwanafunzi aandike, wewe unaelekeza hatua kwa hatua. Mpe muundo kwanza (andika kama comments):
# Bidhaa: jina, bei, idadi; property thamani (bei*idadi); __str__ # Duka: jina, dict ya bidhaa (jina -> Bidhaa) # ongeza_bidhaa(bidhaa), uza(jina, idadi) -> inarudisha bei ya mauzo # ongeza_stock(jina, idadi), bidhaa_zinazoisha(kikomo=5), thamani_ya_stock() # mapato (property)
Suluhisho (duka.py):
class BidhaaHaipo(Exception): pass class StockHaitoshi(Exception): pass class Bidhaa: def __init__(self, jina, bei, idadi=0): if bei <= 0: raise ValueError("Bei lazima iwe zaidi ya sifuri.") self.jina = jina self.bei = bei self.idadi = idadi @property def thamani(self): return self.bei * self.idadi def __str__(self): return f"{self.jina:<10} bei {self.bei:>7,} idadi {self.idadi:>4}" def __repr__(self): return f"Bidhaa({self.jina!r}, {self.bei!r}, {self.idadi!r})" class Duka: def __init__(self, jina): self.jina = jina self._bidhaa = {} self._mapato = 0 @property def mapato(self): return self._mapato def ongeza_bidhaa(self, bidhaa): self._bidhaa[bidhaa.jina.lower()] = bidhaa def _tafuta(self, jina): try: return self._bidhaa[jina.lower()] except KeyError: raise BidhaaHaipo(f"'{jina}' haipo dukani.") from None def ongeza_stock(self, jina, idadi): self._tafuta(jina).idadi += idadi def uza(self, jina, idadi): b = self._tafuta(jina) if idadi > b.idadi: raise StockHaitoshi(f"Zimebaki {b.idadi} tu za {b.jina}.") b.idadi -= idadi jumla = b.bei * idadi self._mapato += jumla return jumla def bidhaa_zinazoisha(self, kikomo=5): return [b for b in self._bidhaa.values() if b.idadi <= kikomo] def thamani_ya_stock(self): return sum(b.thamani for b in self._bidhaa.values()) def __str__(self): mistari = [f"=== {self.jina} ==="] for b in self._bidhaa.values(): mistari.append(str(b)) mistari.append(f"Thamani ya stock: TSh {self.thamani_ya_stock():,}") mistari.append(f"Mapato: TSh {self._mapato:,}") return "\n".join(mistari) if __name__ == "__main__": duka = Duka("Duka la Mama Neema") duka.ongeza_bidhaa(Bidhaa("Sukari", 3000, 20)) duka.ongeza_bidhaa(Bidhaa("Mchele", 2500, 6)) duka.ongeza_bidhaa(Bidhaa("Sabuni", 1200, 3)) print("Mauzo:", duka.uza("sukari", 5)) for jina, n in [("Mchele", 10), ("Unga", 1)]: try: duka.uza(jina, n) except (StockHaitoshi, BidhaaHaipo) as e: print("Kosa:", e) print(duka) print("Zinaisha:", duka.bidhaa_zinazoisha())
Output:
Mauzo: 15000
Kosa: Zimebaki 6 tu za Mchele.
Kosa: 'Unga' haipo dukani.
=== Duka la Mama Neema ===
Sukari bei 3,000 idadi 15
Mchele bei 2,500 idadi 6
Sabuni bei 1,200 idadi 3
Thamani ya stock: TSh 63,600
Mapato: TSh 15,000
Zinaisha: [Bidhaa('Sabuni', 1200, 3)]
Maelezo kwa mwalimu: list comprehension ([b for b in ...]) na sum(... for ...) ni za Wiki 8. Kama mwanafunzi hajazizoea, mwonyeshe toleo la loop:
def bidhaa_zinazoisha(self, kikomo=5): matokeo = [] for b in self._bidhaa.values(): if b.idadi <= kikomo: matokeo.append(b) return matokeo
raise ... from None inaficha KeyError ya ndani ili traceback iwe safi — sema tu "ni usafi".
Kwa nini "Mchele" (6) haiko kwenye "Zinaisha"? Kwa sababu kikomo ni 5 na 6 <= 5 ni False — uliza hili kama swali la kufikiri.
Maswali ya kumuuliza mwanafunzi
print(obj)inatumia nini? —__str__(au__repr__kama__str__haipo).print([obj1, obj2])inatumia nini? —__repr__ya kila object.__str__iwe naprintndani aureturn? —returnstring.- Kwenye
Duka, kwa nini_bidhaani dict na si list? — Kutafuta kwa jina ni haraka na rahisi. - Kwa nini
hamishainaitaself.toakwanza? — Kama salio halitoshi, exception inarushwa nawekahaiitwi; pesa haziundwi kutoka hewani. - Duka "lina" bidhaa — hii inaitwaje? — Composition (has-a).
Makosa ya kawaida
| Kosa | Ujumbe wa error | Suluhisho |
|---|---|---|
__str__ inafanya print badala ya return |
TypeError: __str__ returned non-string (type NoneType) |
return f"..." |
__str__ inarudisha namba |
TypeError: __str__ returned non-string (type int) |
Rudisha string. |
Kusahau kurudisha thamani kwenye uza |
print(duka.uza(...)) inaonyesha None |
return jumla |
Kuongeza object badala ya kutafuta kwa jina: duka._bidhaa[bidhaa] |
KeyError baadaye |
Tumia bidhaa.jina kama key. |
Kusahau self._mapato = 0 kwenye __init__ |
AttributeError: 'Duka' object has no attribute '_mapato' |
Anzisha attributes zote kwenye __init__. |
Zoezi darasani
Zoezi 3.1: Ongeza __str__ na __repr__ kwenye class Gari ya Kipindi 1.
Zoezi 3.2: Ongeza kwenye Duka method ripoti_ya_mauzo() — duka liweke rekodi ya kila mauzo (jina, idadi, jumla) kwenye list na method ichapishe zote.
Zoezi 3.3: Ongeza kwenye BankAccount kikomo cha kutoa kwa siku (class attribute KIKOMO_CHA_SIKU = 200_000). Mtu akijaribu kutoa jumla zaidi ya kikomo, rusha ValueError. (Kwa urahisi, hesabu jumla ya "TOA" zote kwenye _miamala za leo.)
Jibu
# 3.1 class Gari: def __init__(self, aina, rangi, kasi=0): self.aina = aina self.rangi = rangi self.kasi = kasi def __str__(self): return f"{self.aina} ({self.rangi}) - {self.kasi} km/h" def __repr__(self): return f"Gari({self.aina!r}, {self.rangi!r}, {self.kasi!r})" # 3.2 -- mabadiliko kwenye Duka # __init__: self._mauzo = [] # uza(): baada ya kuhesabu jumla: # self._mauzo.append((b.jina, idadi, jumla)) def ripoti_ya_mauzo(self): print("--- Mauzo ---") for jina, idadi, jumla in self._mauzo: print(f"{jina:<10}{idadi:>4} x = {jumla:>8,}") print(f"Jumla ya mapato: {self._mapato:,}") # 3.3 -- mabadiliko kwenye BankAccount from datetime import datetime class BankAccount: KIKOMO_CHA_SIKU = 200_000 # ... (sehemu nyingine kama zilivyo) def _iliyotolewa_leo(self): leo = datetime.now().strftime("%Y-%m-%d") jumla = 0 for wakati, aina, kiasi, _ in self._miamala: if aina == "TOA" and wakati.startswith(leo): jumla += kiasi return jumla def toa(self, kiasi): if kiasi <= 0: raise ValueError("Kiasi lazima kiwe zaidi ya sifuri.") if self._iliyotolewa_leo() + kiasi > BankAccount.KIKOMO_CHA_SIKU: raise ValueError("Umezidi kikomo cha kutoa kwa siku.") if kiasi > self._salio: raise SalioHalitoshi(f"Salio ni {self._salio:,}.") self._salio -= kiasi self._rekodi("TOA", kiasi)
Kazi ya Nyumbani
(Inatakiwa: kipindi kijacho)
Kazi 1 (baada ya Kipindi 1): Class Simu yenye chapa, modeli, chaji (default 100). Methods: piga_simu(dakika) — kila dakika inapunguza chaji 2% (chaji isishuke chini ya 0; kama haitoshi, chapisha "Chaji haitoshi"), chaji_simu(asilimia) (isizidi 100), hali().
Kazi 2 (baada ya Kipindi 2): Class Joto (temperature) inayohifadhi _celsius. Property celsius yenye setter (isikubali chini ya -273.15), na property fahrenheit yenye getter na setter (F = C × 9/5 + 32).
Kazi 3 (baada ya Kipindi 3): Hifadhi Duka kwenye JSON! Ongeza method hifadhi(path) kwenye Duka, na function ya kawaida (nje ya class) pakia_duka(path) inayorudisha Duka jipya kutoka JSON. Ongeza __repr__ kwenye Duka. Programu iwe na menu ndogo: ongeza bidhaa, uza, onyesha, toka (ikihifadhi kabla ya kutoka).
Jibu
# Kazi 1 class Simu: def __init__(self, chapa, modeli, chaji=100): self.chapa = chapa self.modeli = modeli self.chaji = chaji def piga_simu(self, dakika): inahitajika = dakika * 2 if inahitajika > self.chaji: print("Chaji haitoshi") return self.chaji -= inahitajika print(f"Umeongea dakika {dakika}.") def chaji_simu(self, asilimia): self.chaji = min(100, self.chaji + asilimia) def hali(self): print(f"{self.chapa} {self.modeli}: chaji {self.chaji}%") s = Simu("Tecno", "Spark 20") s.piga_simu(30) # chaji 40 s.piga_simu(25) # Chaji haitoshi s.chaji_simu(80) # 100 s.hali()
# Kazi 2 class Joto: def __init__(self, celsius=0): self.celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, v): if v < -273.15: raise ValueError("Chini ya absolute zero haiwezekani.") self._celsius = v @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 @fahrenheit.setter def fahrenheit(self, f): self.celsius = (f - 32) * 5 / 9 # inapita validation ya celsius j = Joto(25) print(j.fahrenheit) # 77.0 j.fahrenheit = 212 print(j.celsius) # 100.0
# Kazi 3 -- weka chini ya classes za duka.py (Bidhaa, Duka, exceptions) import json from pathlib import Path # Ndani ya class Duka ongeza: # def __repr__(self): # return f"Duka({self.jina!r}, bidhaa={len(self._bidhaa)})" # # def hifadhi(self, path): # data = { # "jina": self.jina, # "mapato": self._mapato, # "bidhaa": [ # {"jina": b.jina, "bei": b.bei, "idadi": b.idadi} # for b in self._bidhaa.values() # ], # } # with open(path, "w", encoding="utf-8") as f: # json.dump(data, f, indent=2, ensure_ascii=False) def pakia_duka(path): with open(path, encoding="utf-8") as f: data = json.load(f) duka = Duka(data["jina"]) duka._mapato = data.get("mapato", 0) for b in data["bidhaa"]: duka.ongeza_bidhaa(Bidhaa(b["jina"], b["bei"], b["idadi"])) return duka FAILI = Path(__file__).parent / "duka.json" try: duka = pakia_duka(FAILI) except FileNotFoundError: duka = Duka("Duka Langu") while True: chaguo = input("\n1.Ongeza 2.Uza 3.Onyesha 4.Toka : ") try: if chaguo == "1": jina = input("Jina: ") bei = int(input("Bei: ")) idadi = int(input("Idadi: ")) duka.ongeza_bidhaa(Bidhaa(jina, bei, idadi)) elif chaguo == "2": jina = input("Jina: ") idadi = int(input("Idadi: ")) print("Jumla:", duka.uza(jina, idadi)) elif chaguo == "3": print(duka) elif chaguo == "4": duka.hifadhi(FAILI) print("Imehifadhiwa. Kwaheri!") break except (ValueError, BidhaaHaipo, StockHaitoshi) as e: print("Kosa:", e)
Kumbuka: @classmethod (njia "rasmi" ya kuunda object kutoka JSON) itatajwa Wiki 7/8 kwenye mradi wa maktaba; kwa sasa function ya kawaida inatosha.
Ukiulizwa swali usilolijua
- Ni sawa kusema "Sina uhakika, tujaribu." Fungua REPL (
python) na jaribu:dir(obj)inaonyesha attributes/methods zote,vars(obj)auobj.__dict__inaonyesha attributes za object,help(ClassName)inaonyesha maelezo. - Tafuta: "python class property setter example", "python str vs repr". Docs: https://docs.python.org/3/tutorial/classes.html |
property: https://docs.python.org/3/library/functions.html#property | data model (dunders): https://docs.python.org/3/reference/datamodel.html#basic-customization - Python Tutor (https://pythontutor.com) ni msaada mzuri kuonyesha
selfna objects kwa picha.
Checklist ya mwisho wa wiki
- Anaeleza class vs object kwa mfano wake mwenyewe.
- Anaandika class yenye
__init__na methods bila kusahauself. - Anaeleza
selfni nani kwenyeobj.method(). - Anatofautisha class attribute na instance attribute (na mtego wa
obj.x = ...). - Anajua kwa nini list isiwe class attribute.
- Anatumia
_jinana@property(getter + setter yenye validation). - Anaandika
__str__na__repr__zinazorudisha string. - Ameunda
BankAccountinayorusha exception kwa salio lisilotosha. - Ameunda
Dukalenye objects zaBidhaa(composition). - Kazi ya nyumbani ya Wiki 6 imekabidhiwa.