Misingi

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

Imesasishwa: · Skillaria.Top

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

KosaDaliliSuluhisho
Kusahau self kwenye mbinuTypeError: ... takes 0 positional arguments but 1 was givenParameta 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 darasaData inasafiri kati ya vituUnda orodha ndani ya __init__ au kwa default_factory
Hakuna super().__init__()AttributeError kwenye sehemu za mzaziItisha kianzishi cha mzazi
Kuita property kwa mabanoTypeError: 'int' object is not callableSoma 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.

Maswali ya mara kwa mara

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.

Jifunze mada hii kwa video

Mada hii inafundishwa kwa kina pamoja na mazoezi katika kozi “Python 3.15: от новичка до профи”.

Nenda kwenye kozi