Ачааллж байна...
Өмнөх хоёр хичээлд бид inheritance-ийн ХОЁР зүйлийг үзсэн: хүү class нь эцгийн бүх зүйлийг АВНА (Хичээл 7.1), мөн өөрийн зүйлийг НЭМНЭ (Хичээл 7.2).
Одоо гурав дахь, хамгийн хүчирхэг зүйл рүү орно: хүү class нь эцгийн method-ыг ӨӨРӨӨР ХЭРЭГЖҮҮЛЖ чадна. Түүнийг method override гэж нэрлэдэг — англиар override гэдэг нь "дарж бичих", "хүчингүй болгож солих" гэсэн утгатай.
Үнэндээ та override-ыг АЛЬ ХЭДИЙН харсан. Хичээл 7.2-т хүү class нь __init__ -ийг өөрийнхөөрөө бичихэд — тэр нь override байсан. Эцгийн __init__ -ийг дарж бичсэн тул super() -ээр буцааж дуудах хэрэгтэй болсон.
Одоо бид тэр санааг БҮХ method-д тэлнэ. Мөн override-ын жинхэнэ хүч — polymorphism -ыг бүрэн харна: нэг л дуудалт (zuil.medeelel()) нь объектын ЖИНХЭНЭ ТӨРЛӨӨС хамааран өөр өөр ажиллана.
Гэр бүлийн зүйрлэлийг үргэлжлүүлье.
Эцэг нь тодорхой аргаар мэндчилдэг: гар барих. Хүү нь тэр уламжлалыг өвлөж авсан — тэр ч бас мэндчилдэг. Гэвч тэр өөрийнхөөрөө мэндчилдэг: тэврэх.
Ижил ҮЙЛДЭЛ (мэндчилэх), өөр ГҮЙЦЭТГЭЛ.
Одоо хамгийн чухал зүйл: хэрэв та "тэр хүнтэй мэндчил" гэж хэлбэл — эцэг нь гар барина, хүү нь тэврэнэ. Та тэдэнд юу хийхийг ЗААХГҮЙ. Тэд өөрсдөө мэднэ.
Энэ бол override-ын жинхэнэ хүч. Та zuil.medeelel() гэж НЭГ л удаа бичнэ, гэхдээ ном өөр хариулт, DVD өөр хариулт өгнө. Код нь ямар төрөл болохыг шалгах шаардлагагүй.
python
class Amitan:
def __init__(self, ner: str) -> None:
self.ner = ner
def duu_gargakh(self) -> str:
return f"{self.ner} чимээ гаргаж байна"
class Nokhoi(Amitan):
def duu_gargakh(self) -> str:
return f"{self.ner}: Хав хав!"
class Muur(Amitan):
def duu_gargakh(self) -> str:
return f"{self.ner}: Мяу!"
amitan = Amitan("Амьтан")
nokhoi = Nokhoi("Бор")
muur = Muur("Мийс")
print(amitan.duu_gargakh())
print(nokhoi.duu_gargakh())
print(muur.duu_gargakh())Амьтан чимээ гаргаж байна
Бор: Хав хав!
Мийс: Мяу!Гурван объект, ижил method нэр, гурван өөр үр дүн.
Nokhoi ба Muur нь duu_gargakh -ыг ӨӨРСДИЙНХӨӨРӨӨ бичсэн. Тэдгээр нь эцгийн хувилбарыг дарж бичсэн.
Override хийхэд ямар нэг тусгай түлхүүр үг бичих ШААРДЛАГАГҮЙ. Зөвхөн эцэгтэй ИЖИЛ НЭРТЭЙ method бичихэд л хангалттай. Python нь автоматаар "аан, энэ нь эцгийнхийг сольж байна" гэж ойлгоно.
Одоо ард нь юу болж байгааг ойлгоцгооё, учир нь энэ нь бүх зүйлийг тодруулна.
nokhoi.duu_gargakh() гэж дуудахад Python нь ТОДОРХОЙ ДАРААЛЛААР хайдаг:
Эхлээд объектын ӨӨРИЙН class-аас хайна — Nokhoi -д duu_gargakh байна уу? Байна → түүнийг ашиглана, зогсоно.
Хэрэв байхгүй бол — ЭЦЭГ class-аас хайна (Amitan).
Хэрэв тэнд ч байхгүй бол — эцгийн эцгээс хайна.
Хаанаас ч олдохгүй бол → AttributeError.
Тэгэхээр Nokhoi -д duu_gargakh байгаа тул Python Amitan -ыг ХАРАХ Ч ҮГҮЙ.
Энэ дараалал нь Бүлэг 3.4-ийн scope -ын дүрэмтэй ижил бүтэцтэй: эхлээд ойрын газраас хай, олдохгүй бол гадагш хар.
Одоо Amitan -д БАЙГАА, гэхдээ Nokhoi -д БАЙХГҮЙ method-ыг харъя:
python
class Amitan:
def __init__(self, ner: str) -> None:
self.ner = ner
def duu_gargakh(self) -> str:
return f"{self.ner} чимээ гаргаж байна"
def unt(self) -> str:
return f"{self.ner} унтаж байна"
class Nokhoi(Amitan):
def duu_gargakh(self) -> str:
return f"{self.ner}: Хав хав!"
nokhoi = Nokhoi("Бор")
print(nokhoi.duu_gargakh()) # Nokhoi-гийнх — OVERRIDE
print(nokhoi.unt()) # Amitan-ынх — ӨВЛӨСӨНБор: Хав хав!
Бор унтаж байнаduu_gargakh нь override хийгдсэн тул Nokhoi -гийнх ажиллав. unt нь override хийгдээгүй тул Amitan -ынх ажиллав.
Override нь БҮХ method-д хамаарахгүй — зөвхөн та дахин бичсэн method-д л. Үлдсэн нь өвлөгдсөн хэвээрээ.
Заримдаа та эцгийн method-ыг бүрэн солихыг хүсэхгүй — зөвхөн түүн дээр НЭМЭХИЙГ хүсдэг.
Тэр үед super() дахин ажиллана.
python
class Zuil:
def __init__(self, garchig: str, on: int) -> None:
self.garchig = garchig
self.on = on
self.zeeldsen = False
def medeelel(self) -> str:
return f"{self.garchig} ({self.on})"
class Nom(Zuil):
def __init__(self, garchig: str, on: int, zokhiolch: str) -> None:
super().__init__(garchig, on)
self.zokhiolch = zokhiolch
def medeelel(self) -> str:
undsen = super().medeelel()
return f"{undsen} — {self.zokhiolch}"
class DVD(Zuil):
def __init__(self, garchig: str, on: int, minut: int) -> None:
super().__init__(garchig, on)
self.minut = minut
def medeelel(self) -> str:
undsen = super().medeelel()
return f"{undsen} — {self.minut} минут"
nom = Nom("Монголын нууц товчоо", 1240, "Тодорхойгүй")
dvd = DVD("Аав", 2019, 95)
print(nom.medeelel())
print(dvd.medeelel())Монголын нууц товчоо (1240) — Тодорхойгүй
Аав (2019) — 95 минутsuper().medeelel() гэсэн мөрийг анхааралтай хараарай.
Тэр нь ЭЦГИЙН medeelel -ийг дуудаж байна. Тэр нь "Монголын нууц товчоо (1240)" гэсэн string буцаана. Дараа нь хүү class нь түүн дээр өөрийн хэсгийг нэмж байна.
Энэ бол override-ын хамгийн ухаалаг хэлбэр: солих биш, ӨРГӨТГӨХ.
Ашиг тус нь:
Гарчиг, оныг форматлах логик НЭГ л газарт (Zuil.medeelel)
Хэрэв тэр форматыг өөрчлөх хэрэгтэй болбол — ганц газар засна, бүх хүү class автоматаар шинэчлэгдэнэ
Хүү class бүр зөвхөн ӨӨРИЙН нэмэлтийг л мэднэ
Хэрэв super() ашиглаагүй бол:
python
class Nom(Zuil):
def medeelel(self) -> str:
return f"{self.garchig} ({self.on}) — {self.zokhiolch}" # ДАВХАРДАЛЭнэ ч бас ажиллана. Гэхдээ {self.garchig} ({self.on}) гэсэн формат ДАХИН бичигдсэн. Хэрэв эцэг class-ын формат өөрчлөгдвөл — энэ мөр хоцорно.
Дүрэм: эцгийн зан төлөвийг СОЛИХ гэж байгаа бол super() бүү дууд. ӨРГӨТГӨХ гэж байгаа бол super() дууд.
Одоо энэ хичээлийн ГОЛ санаа руу орно.
python
nomiin_san: list[Zuil] = [
Nom("Монголын нууц товчоо", 1240, "Тодорхойгүй"),
DVD("Аав", 2019, 95),
Nom("Цагаан хэрэм", 1971, "Б. Ринчен"),
DVD("Хөх торгон дээл", 2020, 110),
]
for zuil in nomiin_san:
print(zuil.medeelel())Монголын нууц товчоо (1240) — Тодорхойгүй
Аав (2019) — 95 минут
Цагаан хэрэм (1971) — Б. Ринчен
Хөх торгон дээл (2020) — 110 минутLoop-д ямар ч if байхгүй байгааг анзаараарай.
zuil.medeelel() гэсэн НЭГ л дуудалт байна. Гэвч ном бол зохиолч, DVD бол минут харуулж байна. Python нь объект бүрийн ЖИНХЭНЭ төрлийг мэдэж, зөв method-ыг олж дуудаж байна.
Одоо энэ кодыг override-гүйгээр бичих гэж оролдъё:
python
for zuil in nomiin_san:
if isinstance(zuil, Nom):
print(f"{zuil.garchig} ({zuil.on}) — {zuil.zokhiolch}")
elif isinstance(zuil, DVD):
print(f"{zuil.garchig} ({zuil.on}) — {zuil.minut} минут")
else:
print(f"{zuil.garchig} ({zuil.on})")Ижил үр дүн. Гэвч энэ код нь ХАМААГҮЙ МУУ. Яагаад?
Шалтгаан 1: шинэ төрөл нэмэхэд. Хэрэв Setguul class нэмвэл — энэ loop-д ШИНЭ elif нэмэх хэрэгтэй. Мөн энэ мэт if/elif гинж кодод хэдэн арван газар байж болно. Бүгдийг олж, бүгдийг засах хэрэгтэй. Нэгийг мартвал — чимээгүй алдаа.
Override-той бол? Юу ч засах шаардлагагүй. Шинэ class нь өөрийн medeelel() -ийг бичээд, loop нь автоматаар зөв ажиллана.
Шалтгаан 2: мэдлэг тархсан байна. if/elif хувилбарт "ном яаж харагдах вэ" гэсэн мэдлэг нь Nom class-д БИШ, харин loop-д байна. Хэрэв тэр мэдлэг арван газарт хэрэгтэй бол — арван газарт хуулагдана.
Override-той бол? Мэдлэг нь өөрийн class-д амьдарна. "Ном яаж харагдахыг" зөвхөн Nom мэднэ. Тэр л зөв.
Шалтгаан 3: уншихад хүнд. for zuil in nomiin_san: print(zuil.medeelel()) — хоёр мөр, тодорхой. if/elif гинж — арван мөр, шалгалттай.
Энэ бол OOP-ийн ХАМГИЙН чухал сургамжуудын нэг:
Хэрэв та
isinstance-ээр төрөл шалгаж, дараа нь өөр өөр зүйл хийж байвал — тэр нь ихэвчлэн override ашиглах ёстой байсны шинж юм.
Одоохондоо бид зөвхөн МЭДЭЭЛЭЛ харуулах method-ыг override хийсэн. Гэхдээ override нь ЖИНХЭНЭ ЛОГИКИЙГ ч өөрчилж чадна.
Номын санд дүрэм байна: DVD-г зөвхөн 3 хоног, ном 14 хоног зээлж болно. Мөн лавлах ном (reference book) -ыг ОГТ зээлж болохгүй.
python
class Zuil:
def __init__(self, garchig: str, on: int) -> None:
self.garchig = garchig
self.on = on
self.zeeldsen = False
def zeeleh_khugatsaa(self) -> int:
return 14
def zeeldekh_bolokh_uu(self) -> bool:
return not self.zeeldsen
def zeeldekh(self) -> bool:
if not self.zeeldekh_bolokh_uu():
return False
self.zeeldsen = True
return True
class Nom(Zuil):
pass # ердийн дүрэм — override хэрэггүй
class DVD(Zuil):
def zeeleh_khugatsaa(self) -> int:
return 3 # DVD нь зөвхөн 3 хоног
class LavlakhNom(Zuil):
def zeeldekh_bolokh_uu(self) -> bool:
return False # ХЭЗЭЭ Ч зээлж болохгүй
def zeeleh_khugatsaa(self) -> int:
return 0Туршиж үзье:
python
nom = Nom("Монголын нууц товчоо", 1240)
dvd = DVD("Аав", 2019)
lavlakh = LavlakhNom("Монгол хэлний тайлбар толь", 2018)
zuiluud: list[Zuil] = [nom, dvd, lavlakh]
for zuil in zuiluud:
amjilttai = zuil.zeeldekh()
if amjilttai:
print(f"{zuil.garchig}: зээллээ, {zuil.zeeleh_khugatsaa()} хоног")
else:
print(f"{zuil.garchig}: зээлж болохгүй")Монголын нууц товчоо: зээллээ, 14 хоног
Аав: зээллээ, 3 хоног
Монгол хэлний тайлбар толь: зээлж болохгүйГурван объект, гурван өөр зан төлөв. Гэвч loop нь ЯМАР Ч ялгааг мэдэхгүй.
Хамгийн сонирхолтой хэсэг: Zuil.zeeldekh() дотор self.zeeldekh_bolokh_uu() гэж дуудаж байна. Тэр method нь LavlakhNom -д override хийгдсэн.
Тэгвэл lavlakh.zeeldekh() дуудахад юу болох вэ?
zeeldekh нь LavlakhNom -д БАЙХГҮЙ → эцгийнх (Zuil.zeeldekh) ажиллана
Тэр дотор self.zeeldekh_bolokh_uu() дуудагдана
Python нь self -ийн ЖИНХЭНЭ төрлөөс (LavlakhNom) хайна
LavlakhNom.zeeldekh_bolokh_uu олдоно → False буцаана
Тиймээс zeeldekh нь False буцаана
Эцгийн код нь ХҮҮГИЙН override-ыг дуудаж байна. Хэдийгээр Zuil нь LavlakhNom -ийн талаар юу ч мэдэхгүй ч гэсэн.
Энэ бол маш хүчирхэг загвар. Эцэг class нь ЯМАР алхмууд байхыг тодорхойлно (zeeldekh -ийн бүтэц), хүү class нь АЛХАМ БҮРИЙГ ЯАЖ хийхийг тодорхойлно (zeeldekh_bolokh_uu). Тэдгээр нь хамтдаа ажиллана.
Override хийхэд нэг ЧУХАЛ дүрэм бий: хүү class-ын method нь эцгийнхтэй ИЖИЛ ГАРЫН ҮСЭГТЭЙ (signature) байх ёстой.
Гарын үсэг гэдэг нь: параметрүүд ба буцаах төрөл.
python
class Zuil:
def medeelel(self) -> str:
return f"{self.garchig} ({self.on})"
class Nom(Zuil):
def medeelel(self, delgerengui: bool) -> str: # ӨӨР гарын үсэг!
if delgerengui:
return f"{self.garchig} ({self.on}) — {self.zokhiolch}"
return self.garchigОдоо polymorphism ЭВДЭРНЭ:
python
zuiluud: list[Zuil] = [Nom("Ном", 2020, "Зохиолч"), DVD("Кино", 2019, 95)]
for zuil in zuiluud:
print(zuil.medeelel())Traceback (most recent call last):
File "main.py", line 4, in <module>
print(zuil.medeelel())
TypeError: Nom.medeelel() missing 1 required positional argument: 'delgerengui'missing 1 required positional argument: 'delgerengui' — Nom.medeelel нь нэмэлт аргумент шаардаж байна, гэвч loop нь түүнийг мэдэхгүй.
Polymorphism-ийн БҮХ утга алдагдлаа. Loop нь одоо объект бүрийн төрлийг мэдэх ёстой болно — яг тэр зүйлээс бид зугтахыг хүсэж байсан.
Дүрэм: override хийхдээ ИЖИЛ гарын үсэг хадгал. Ижил параметр, ижил буцаах төрөл.
Хэрэв нэмэлт сонголт хэрэгтэй бол — default утга ашигла (Бүлэг 3.1):
python
class Zuil:
def medeelel(self, delgerengui: bool = False) -> str:
return f"{self.garchig} ({self.on})"
class Nom(Zuil):
def medeelel(self, delgerengui: bool = False) -> str:
undsen = super().medeelel()
if delgerengui:
return f"{undsen} — {self.zokhiolch}"
return undsenОдоо zuil.medeelel() нь БҮХ төрөлд ажиллана, мөн nom.medeelel(delgerengui=True) ч ажиллана. Хоёр ертөнц зэрэг.
Хэрэв сонирхвол: дээрх номын сангийн class-уудад Setguul гэсэн шинэ class нэмээд, medeelel() -ыг override хийгээд, түүнийг nomiin_san жагсаалтад оруулаад ажиллуулаарай.
Loop-ыг ОГТ засах шаардлагагүй байх ёстой. Тэр нь автоматаар зөв ажиллана. Тэр мөч — шинэ класс нэмэхэд хуучин код өөрчлөгдөхгүй байх тэр мөч — бол OOP-ийн жинхэнэ шагнал юм.
Мөн хүсвэл: zeeleh_khugatsaa() -ыг Setguul -д override хийж, 7 хоног болгож үзээрэй.
1. Method-ын нэрийг буруу бичих
python
class Nokhoi(Amitan):
def duu_gargax(self) -> str: # "gargakh" биш "gargax"!
return f"{self.ner}: Хав хав!"Алдаа гарахгүй. Программ ажиллана. Гэхдээ override НЭГ Ч БОЛООГҮЙ.
nokhoi.duu_gargakh() дуудахад ЭЦГИЙНХ ажиллана (учир нь duu_gargakh нь Nokhoi -д байхгүй). Таны бичсэн duu_gargax нь зүгээр л шинэ, огт дуудагдахгүй method болж үлдэнэ.
Энэ бол хамгийн аюултай алдаа, учир нь ямар ч сэрэмжлүүлэг байхгүй. Мэргэжлийн Python-д үүнээс сэргийлэх арга бий:
python
from typing import override
class Nokhoi(Amitan):
@override
def duu_gargax(self) -> str:
...@override нь (Python 3.12-өөс) "энэ нь эцгийн method-ыг дарж бичиж байна" гэж зарладаг. Хэрэв эцэгт тийм нэртэй method байхгүй бол — type checker (mypy) нь АЛДАА заана.
Энэ курсэд бид @override -ыг ЗААВАЛ ашиглахгүй (тэр нь @ тэмдэгтэй, харин decorator-ыг Бүлэг 10-д үзнэ), гэхдээ мэдэж байх нь зүйтэй. Одоохондоо хамгийн энгийн хамгаалалт: нэрийг эцгээс ХУУЛЖ ав, гараар бүү бич.
2. super() -ыг мартаж, давхардал үүсгэх
python
class Nom(Zuil):
def medeelel(self) -> str:
return f"{self.garchig} ({self.on}) — {self.zokhiolch}"Ажиллана, гэхдээ эцгийн формат ДАВХАРДСАН. Эцэг өөрчлөгдвөл энэ мөр хоцорно. Засвар: super().medeelel() дууд, дараа нь нэм.
3. Гарын үсгийг өөрчлөх
TypeError: Nom.medeelel() missing 1 required positional argumentPolymorphism эвдэрнэ. Ижил гарын үсэг хадгал, эсвэл default утга ашигла.
4. self -ийг мартах
python
class Nokhoi(Amitan):
def duu_gargakh() -> str: # self байхгүй!
return "Хав хав!"
nokhoi = Nokhoi("Бор")
print(nokhoi.duu_gargakh())TypeError: Nokhoi.duu_gargakh() takes 0 positional arguments but 1 was giventakes 0 positional arguments but 1 was given — Python нь self -ийг АВТОМАТААР дамжуулдаг. Method бүрийн эхний параметр нь ЗААВАЛ self байх ёстой. Курс 1-ээс танил дүрэм.
5. isinstance -ээр төрөл шалгаад өөр өөр зүйл хийх
python
for zuil in nomiin_san:
if isinstance(zuil, Nom):
...
elif isinstance(zuil, DVD):
...Алдаа биш, гэхдээ дизайны асуудлын шинж. Тэр логикийг class-уудад override болгож зөөвөл — код нь богино, аюулгүй, өргөтгөх боломжтой болно.
Method override нь хүү class нь эцгийн method-ыг ӨӨРӨӨР хэрэгжүүлэх явдал. Тусгай түлхүүр үг ХЭРЭГГҮЙ — зөвхөн ижил нэртэй method бич.
Python-ы хайх дараалал: объектын өөрийн class → эцэг → эцгийн эцэг → AttributeError. Хамгийн ойрыг олмогц зогсоно.
Override нь ЗӨВХӨН дахин бичсэн method-д хамаарна. Үлдсэн нь өвлөгдсөн хэвээрээ.
super().method() нь эцгийн хувилбарыг дуудна. Ингэснээр та эцгийн зан төлөвийг СОЛИХ биш, ӨРГӨТГӨЖ чадна.
Солих гэж байвал super() бүү дууд.
Өргөтгөх гэж байвал super() дууд, дараа нь нэм.
Polymorphism: zuil.medeelel() гэсэн НЭГ дуудалт нь объектын жинхэнэ төрлөөс хамааран өөр өөр ажиллана. Loop-д if хэрэггүй.
Шинэ class нэмэхэд хуучин loop-ыг ЗАСАХ ШААРДЛАГАГҮЙ. Энэ бол OOP-ийн жинхэнэ шагнал.
Эцгийн method нь ХҮҮГИЙН override-ыг дуудаж чадна (self.zeeldekh_bolokh_uu()) — эцэг нь бүтцийг, хүү нь дэлгэрэнгүйг тодорхойлно.
ИЖИЛ ГАРЫН ҮСЭГ хадгал. Параметр нэмбэл polymorphism эвдэрнэ. Нэмэлт хэрэгтэй бол default утга ашигла.
Нэрийг буруу бичих нь хамгийн аюултай алдаа — override болохгүй, гэхдээ ямар ч сэрэмжлүүлэг байхгүй. Эцгээс ХУУЛЖ ав.
isinstance -ээр төрөл шалгаад өөр өөр зүйл хийж байвал — override ашиглах ёстой байсны шинж.
Дараагийн хичээлээр Бүлэг 7 дуусна. Тэнд шинэ бичлэг байхгүй — зөвхөн шударга зөвлөгөө. Inheritance нь хүчирхэг, гэхдээ түүнийг ХЭТРҮҮЛЭН ашиглах нь программистуудын хамгийн түгээмэл алдаануудын нэг юм. Хэзээ inheritance ашиглах вэ, хэзээ БОЛИХ вэ? Тэр асуултын хариулт нь энгийн дүрэмтэй бөгөөд түүнийг мэдэх нь мэдэхгүй байхаас хамаагүй чухал.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.