# Madarasa na OOP katika Python: __init__, self, mbinu, urithi na dataclass

URL: https://skillaria.top/sw/guides/python/klassy-python

OOP katika Python inategemea madarasa: darasa linaeleza sifa na mbinu, na vitu (mifano) huundwa kwa kuliita darasa; __init__ hujaza sifa, na self inaonyesha kitu cha sasa. Urithi huruhusu kuchukua tabia ya mzazi na kubadilisha baadhi ya mbinu, na decorator @dataclass hutengeneza __init__, __repr__ na __eq__ kwa madarasa yanayoshikilia data zaidi.

Darasa katika Python ni mchoro: linaeleza data ambayo kitu kinahifadhi na kile kinachoweza kufanyiwa. Kitu (mfano, au instance) ni bidhaa halisi iliyotengenezwa kwa mchoro huo. OOP katika Python inasimama juu ya mambo matatu: madarasa yenye mbinu, urithi, na mbinu maalum kama `__init__` na `__repr__`. Hapa tutaziangalia zote kwa mpangilio, kwa mfano mmoja: ufuatiliaji wa matumizi.

## Kwa nini madarasa, wakati kuna kamusi

Data inaweza kuhifadhiwa kwenye kamusi: `{"title": "Kahawa", "amount": 250}`. Tatizo ni kwamba kamusi haijui ni funguo zipi ni sahihi. Hitilafu ya kuandika `expense.get("amout", 0)` haitatoa kosa — itarudisha sifuri, na ripoti itaanza kudanganya kimyakimya.

Kitu cha darasa kina sehemu zilizotangazwa mapema. Kufikia sifa isiyokuwepo kunasababisha `AttributeError` mara moja, na Python ya kisasa hata inapendekeza jina sahihi. Zaidi ya hayo, darasa linakusanya data na vitendo vyake mahali pamoja: matumizi yenyewe yanaweza kuhesabu kodi, na ripoti yenyewe inahesabu jumla.

## Jinsi ya kuunda darasa katika Python

Darasa linatangazwa kwa neno `class`, na jina kwa mujibu wa PEP 8 linaandikwa kwa mtindo wa CamelCase. Mfano rahisi kabisa:

```
class Expense:
    def __init__(self, title, amount):
        self.title = title
        self.amount = amount

coffee = Expense("Kahawa", 250)
taxi = Expense("Teksi", 300)
print(coffee.title, coffee.amount)   # Kahawa 250
```

Wito `Expense("Kahawa", 250)` huunda mfano mpya wa darasa. `coffee` na `taxi` ni vitu viwili tofauti vya aina moja, kila kimoja na thamani zake. Kuangalia kama kitu ni cha darasa fulani kunafanywa kwa `isinstance(coffee, Expense)` — itarudisha `True`.

### Mjenzi __init__: kuanzisha darasa

`__init__` mara nyingi huitwa konstrukta ya darasa, ingawa kwa usahihi ni kianzishi (initializer). Kitu chenyewe huundwa na mbinu `__new__`, kisha `__init__` huitwa mara baada ya hapo na kuweka hoja zilizopitishwa kwenye sifa. Haihitaji kurudisha kitu chochote (kwa usahihi, lazima irudishe `None`).

Katika `__init__` ni rahisi kukagua data ya ingizo, ili programu isiwe na vitu visivyo sahihi kwa makusudi:

```
class Expense:
    def __init__(self, title, amount):
        if amount < 0:
            raise ValueError("Kiasi hakiwezi kuwa hasi")
        self.title = title
        self.amount = amount
```

### self katika Python ni nini

`self` ni marejeo ya kitu hasa ambacho mbinu inafanya kazi nacho kwa sasa. Unapoandika `coffee.with_tax()`, Python kwa kweli huita `Expense.with_tax(coffee)` na kupitisha kitu kama hoja ya kwanza. Ndiyo maana `self` huandikwa kwanza kwenye tangazo la mbinu, lakini wakati wa wito haiandikwi.

`self` si neno la hifadhi, bali ni makubaliano: kitaalamu parameta inaweza kuitwa chochote, lakini hakuna anayefanya hivyo. Kosa la kawaida la wanaoanza ni kusahau `self` kwenye tangazo la mbinu. Kisha wito unaanguka na `TypeError: Expense.with_tax() takes 0 positional arguments but 1 was given`.

## Mbinu za darasa na mbinu maalum

Mbinu ni kazi iliyotangazwa ndani ya darasa. Inaona sifa za kitu kupitia `self`:

```
class Expense:
    def __init__(self, title, amount):
        self.title = title
        self.amount = amount

    def with_tax(self, rate=0.2):
        return self.amount * (1 + rate)

    def __repr__(self):
        return f"Expense({self.title!r}, {self.amount})"

coffee = Expense("Kahawa", 250)
print(coffee.with_tax())   # 300.0
print(coffee)              # Expense('Kahawa', 250)
```

Bila `__repr__`, matokeo yangeonekana kama `<__main__.Expense object at 0x7f...>` — hayana msaada kwa utatuzi. Mbinu zenye mistari miwili ya chini pande zote mbili huitwa maalum (dunder): Python huziita yenyewe. `__str__` inahusika na uchapishaji "mzuri" kwa mtumiaji, `__eq__` na ulinganisho kwa `==`, `__len__` na `len(obj)`.

### @classmethod na @staticmethod

Mbinu ya kawaida hufanya kazi na mfano. Mbinu ya darasa yenye decorator `@classmethod` inapokea darasa lenyewe (`cls`) kama hoja ya kwanza — mara nyingi hutumika kama konstrukta mbadala. Mbinu tuli `@staticmethod` haipokei kitu wala darasa: ni kazi ya kawaida inayohusiana kimantiki na darasa.

```
class Expense:
    def __init__(self, title, amount):
        self.title = title
        self.amount = amount

    @classmethod
    def from_csv(cls, line):
        title, amount = line.split(";")
        return cls(title, float(amount))

    @staticmethod
    def is_valid_amount(value):
        return value >= 0

e = Expense.from_csv("Chakula cha mchana;520")
print(e.amount)                    # 520.0
print(Expense.is_valid_amount(-1)) # False
```

### Mharibu wa darasa: __del__

Python ina mbinu `__del__` inayoitwa kabla kitu hakijaondolewa na mkusanyaji taka (garbage collector). Usitegemee kwa kuachilia rasilimali: wakati wa wito haujahakikishwa. Kwa faili na miunganisho tumia meneja wa muktadha `with` au wito wa wazi `close()`.

## Sifa za darasa na sifa za mfano

Sehemu zilizowekwa kupitia `self.jina = ...` ni za kitu fulani. Vigezo vilivyotangazwa moja kwa moja ndani ya mwili wa darasa ni sifa za darasa: ni za pamoja kwa mifano yote. Hapo ndipo mara nyingi huhifadhiwa thamani za kudumu za darasa.

```
class Expense:
    CURRENCY = "TZS"          # sifa ya darasa, thamani ya kudumu

    def __init__(self, title, amount):
        self.title = title    # sifa za mfano
        self.amount = amount

a = Expense("Kahawa", 250)
b = Expense("Chai", 150)
print(a.CURRENCY, Expense.CURRENCY)   # TZS TZS
```

Mtego hatari ni sifa ya darasa inayoweza kubadilika. Ukitangaza `rows = []` ndani ya darasa na kuongeza kupitia `self.rows.append(...)`, orodha hiyo itakuwa ya pamoja kwa vitu vyote. Sehemu zinazoweza kubadilika zinapaswa kila mara kuundwa ndani ya `__init__`.

Unaweza kuona sehemu za kitu kupitia `vars(obj)` au `obj.__dict__`, na kusoma sifa kwa jina lililo kwenye mfuatano kwa `getattr(obj, "amount")`.

## Sifa za darasa: @property

Property ni mbinu inayosomwa kutoka nje kama sehemu ya kawaida, bila mabano. Ni rahisi kwa thamani zinazohesabiwa: jumla ya ripoti haihitaji kuhifadhiwa tofauti, na huna hatari ya kusahau kuisasisha.

```
class Report:
    def __init__(self):
        self.rows = []

    def add(self, expense):
        self.rows.append(expense)

    @property
    def total(self):
        return sum(e.amount for e in self.rows)

r = Report()
r.add(Expense("Kahawa", 250))
r.add(Expense("Teksi", 300))
print(r.total)   # 550
```

Ikiwa unahitaji kudhibiti uandikaji, ongeza setter kwa `@total.setter`. Bila setter, mgawo `r.total = 0` utasababisha `AttributeError`.

Kuhusu encapsulation: sehemu za faragha kwa maana kali hazipo katika Python. Mstari mmoja wa chini (`_cache`) ni makubaliano ya "usiguse kutoka nje", na mistari miwili (`__secret`) huwasha name mangling, ili sehemu isifunikwe kwa bahati mbaya na mrithi.

## Urithi wa madarasa katika Python

Urithi unaruhusu kuunda darasa la mtoto kutoka kwa darasa la mzazi: mtoto anapata sehemu na mbinu zote, na zile zinazohitajika anazibadilisha. Darasa la mzazi linaandikwa kwenye mabano.

```
class MonthlyReport(Report):
    def __init__(self, month):
        super().__init__()        # itisha __init__ ya mzazi
        self.month = month

    def add(self, expense):       # kubadilisha mbinu
        print(f"[{self.month}] + {expense}")
        super().add(expense)      # kuhifadhi tabia ya mzazi

m = MonthlyReport("Oktoba")
m.add(Expense("Kahawa", 250))   # [Oktoba] + Expense('Kahawa', 250)
print(m.total)                  # 250 — property imerithiwa
```

`super()` hurudisha kitu cha kati cha kuita mbinu za mzazi. Kusahau `super().__init__()` kwenye `__init__` ya mtoto ni kosa la kawaida: sehemu `rows` haijaundwa, na `add` ya kwanza inaanguka na `AttributeError`.

Uhusiano wa urithi unakaguliwa kwa `issubclass(MonthlyReport, Report)`. Madarasa yote katika Python 3 yanarithi kimyakimya kutoka `object` — ndiyo chanzo cha `__repr__`, `__eq__` na mbinu nyingine za msingi. Python inaruhusu pia urithi wa aina nyingi (`class C(A, B)`); mpangilio wa kutafuta mbinu unaonyeshwa na `C.__mro__`.

### Darasa dhahania katika Python

Darasa dhahania (abstract class) linaelezea kiolesura: ni mbinu zipi lazima madarasa ya mrithi yazitekeleze. Huwezi kuunda mfano wake. Kwa hili kuna moduli `abc`:

```
from abc import ABC, abstractmethod

class Exporter(ABC):
    @abstractmethod
    def export(self, report): ...

class CsvExporter(Exporter):
    def export(self, report):
        return "\n".join(f"{e.title};{e.amount}" for e in report.rows)

Exporter()   # TypeError: Can't instantiate abstract class Exporter ...
```

Darasa la mrithi lisilotekeleza mbinu zote dhahania nalo pia linabaki dhahania, na wakati wa kuunda kitu litatoa kosa lilelile.

## Muundo (composition) badala ya miti mirefu ya urithi

Baada ya kujifunza urithi, inavutia kujenga ngazi: tumizi la msingi, tumizi la fedha, tumizi la shilingi — na kadhalika hadi ngazi tano. Katika Python hilo hufanyika mara chache. Urithi unaeleza uhusiano wa "ni", na muundo unaeleza "ina": ripoti si orodha ya matumizi, bali ina orodha hiyo kwenye sehemu `rows`.

Kanuni ya vitendo: ikiwa unahitaji tu uwezo wa kitu kingine, weka kitu hicho kwenye sehemu, badala ya kurithi. Kwa data mara nyingi dataclass inatosha, na mantiki inaweza kuwekwa kwenye kazi za kawaida.

## Dataclasses katika Python: darasa bila msimbo wa kurudiarudia

Kwa madarasa yanayoshikilia data zaidi, kuandika `__init__`, `__repr__` na `__eq__` kwa mkono ni kazi ya kuchosha. Decorator `@dataclass` kutoka moduli `dataclasses` huzitengeneza kutoka kwa maelezo ya aina za sehemu:

```
from dataclasses import dataclass, field

@dataclass
class Expense:
    title: str
    amount: float
    tags: list[str] = field(default_factory=list)

    def __post_init__(self):
        if self.amount < 0:
            raise ValueError("Kiasi hakiwezi kuwa hasi")

e = Expense("Kahawa", 250)
print(e)                           # Expense(title='Kahawa', amount=250, tags=[])
print(e == Expense("Kahawa", 250))   # True
```

Ukaguzi wa thamani uliokuwa ukiandikwa kwenye `__init__` unahamishiwa kwenye `__post_init__` — inaitwa mara baada ya kianzishi kilichotengenezwa. Thamani chaguo-msingi zinazoweza kubadilika zinatolewa tu kwa `field(default_factory=list)`: andiko la `tags: list = []` dataclass inalikataa na kutoa `ValueError`.

### frozen na slots

Parameta `frozen=True` hufanya kitu kisibadilike, na `slots=True` (tangu Python 3.10) huunda `__slots__` na kuokoa kumbukumbu. Jaribio la kubadilisha sehemu ya kitu kilichogandishwa linasababisha `FrozenInstanceError`. Ili kupata kitu chenye thamani mpya, unaunda nakala:

```
from dataclasses import dataclass, replace

@dataclass(frozen=True, slots=True)
class Expense:
    title: str
    amount: float

e = Expense("Kahawa", 250)
# e.amount = 300   -> dataclasses.FrozenInstanceError
e2 = replace(e, amount=300)
print(e, e2)   # Expense(title='Kahawa', amount=250) Expense(title='Kahawa', amount=300)
```

Kuanzia Python 3.13 kazi ya jumla `copy.replace()` inafanya kitu hicho hicho. Vitu vya dataclass vilivyogandishwa vinaweza kuwekwa kwenye seti na kutumika kama funguo za kamusi — vina `__hash__`.

## Mambo mapya katika madarasa: Self, @override, __slots__

Kwa uandishi wa aina za madarasa kuna zana mbili muhimu. `typing.Self` (Python 3.11, PEP 673) inamaanisha "itarudi kitu cha darasa hilohilo" — hii inafanya kazi pia kwenye mbinu zilizorithiwa. Decorator `@override` (Python 3.12, PEP 698) inaweka alama kwenye mbinu ambayo lazima ibadilishe ile ya mzazi: ukikosea jina, mypy au pyright zitakuonya kwamba mbinu ya msingi yenye jina hilo haipo.

```
from typing import Self, override

class Report:
    def __init__(self):
        self.rows = []

    def add(self, e) -> Self:
        self.rows.append(e)
        return self              # inaweza kuitwa mfululizo

class MonthlyReport(Report):
    @override
    def add(self, e) -> Self:
        return super().add(e)
```

Katika Python 3.15 kizuizi kidogo kimeondolewa: madarasa ya mrithi wa `tuple` yanaruhusiwa `__slots__` zisizo tupu, na `__dict__` na `__weakref__` vinaweza kutajwa kwenye `__slots__` ya darasa lolote. Katika 3.14 tamko kama hilo kwa mrithi wa tuple lilianguka na `TypeError: nonempty __slots__ not supported for subtype of 'tuple'`.

## Makosa ya kawaida katika madarasa

| Kosa | Dalili | Suluhisho |
| --- | --- | --- |

| Kusahau `self` kwenye mbinu | `TypeError: ... takes 0 positional arguments but 1 was given` | Parameta ya kwanza ya mbinu daima iwe `self` |

| Sehemu bila `self.` kwenye `__init__` | Thamani inapotea baada ya `__init__` | Andika `self.amount = amount` |

| Orodha kama sifa ya darasa | Data inasafiri kati ya vitu | Unda orodha ndani ya `__init__` au kwa `default_factory` |

| Hakuna `super().__init__()` | `AttributeError` kwenye sehemu za mzazi | Itisha kianzishi cha mzazi |

| Kuita property kwa mabano | `TypeError: 'int' object is not callable` | Soma property bila mabano: `r.total` |

## Hitimisho

OOP katika Python inaanza kwa jambo rahisi: darasa linaeleza sehemu na mbinu, `__init__` inajaza sehemu, na `self` inaonyesha kitu cha sasa. Urithi na `super()` huruhusu kupanua tabia, madarasa dhahania yanaeleza kiolesura, na `@property` huunda sehemu zinazohesabiwa. Kwa madarasa ya data chukua `@dataclass`, na kwa miti mirefu ya urithi pendelea muundo. Katika kozi ya Python madarasa na dataclasses vinafundishwa kwa vitendo kwa mfano wa kifuatiliaji cha matumizi.

## FAQ

**Darasa linatofautianaje na kitu katika Python?**

Darasa ni maelezo ya aina: lina sehemu na mbinu gani. Kitu, au mfano, huundwa kwa kuliita darasa, kwa mfano Expense("Kahawa", 250), na kina thamani halisi za sehemu.

**Je, ni lazima kuandika __init__ katika darasa?**

Hapana. Kama __init__ haijatangazwa, inatumika ile ya object, ambayo haipokei hoja zozote. Kwenye dataclass inatengenezwa na decorator.

**__str__ inatofautianaje na __repr__?**

__repr__ ni uwakilishi usio na utata kwa msanidi na debugger; __str__ ni uchapishaji unaoeleweka kwa mtumiaji katika print na f-strings. Ikiwa __str__ haipo, Python hutumia __repr__.

**Lini nichague dataclass na lini darasa la kawaida?**

Dataclass inafaa darasa linaloshikilia data zaidi na linalohitaji __init__, __repr__ na ulinganisho tayari. Darasa la kawaida ni bora pale mantiki tata ya kuanzisha au tabia ni muhimu kuliko data.

**Je, Python ina sehemu za faragha?**

Faragha kali haipo. Mstari mmoja wa chini mwanzoni mwa jina ni makubaliano ya "si kwa matumizi ya nje", na mistari miwili huwasha name mangling inayolinda sehemu dhidi ya kufunikwa kwa bahati mbaya na mrithi.


---
Source: Skillaria.Top (https://skillaria.top)
