Ачааллж байна...
Түвшин 3 эхэлж байна. Энэ түвшинд бид OOP — Object-Oriented Programming буюу объект хандлагат программчлал руу гүн орно.
Курс 1-ийн төгсгөлд та class гэж юу болохыг үзсэн. Тэр үед бид class-ыг зураг төсөл (blueprint) гэж зүйрлэсэн: class нь загвар, объект нь тэр загвараар барьсан жинхэнэ байшин. Class нь ямар талбар (attribute) байх, ямар үйлдэл (method) хийхийг тодорхойлдог. Дараа нь та тэр загвараар олон объект үүсгэдэг.
Тэр мэдлэгээ товч сануулъя:
python
class Nom:
def __init__(self, garchig: str, zokhiolch: str) -> None:
self.garchig = garchig
self.zokhiolch = zokhiolch
def танилцуулах(self) -> str:
return f"{self.garchig} — {self.zokhiolch}"
nom = Nom("Монголын нууц товчоо", "Тодорхойгүй")
print(nom.танилцуулах())Монголын нууц товчоо — Тодорхойгүй__init__ нь объект үүсэх үед автоматаар ажилладаг тусгай method. self нь "энэ объект өөрөө" гэсэн утгатай. Эдгээр нь Курс 1-ээс танил.
Одоо бид дараагийн алхам руу орно: inheritance буюу өвлөлт. Энэ нь class-ууд бие биенээсээ шинж чанар, зан төлөвөө өвлөж авдаг механизм юм.
Асуудлыг эхнээс нь харъя. Номын сангийн систем бичиж байна гэж бодъё (тэр нь Бүлэг 9-ийн guided build болно). Танд гурван төрлийн зүйл байна: ном, сэтгүүл, DVD.
Тэдгээрийг тусад нь бичье:
python
class Nom:
def __init__(self, garchig: str, on: int, zokhiolch: str) -> None:
self.garchig = garchig
self.on = on
self.zokhiolch = zokhiolch
self.zeeldsen = False
def zeeldekh(self) -> bool:
if self.zeeldsen:
return False
self.zeeldsen = True
return True
def butsaakh(self) -> None:
self.zeeldsen = False
class Setguul:
def __init__(self, garchig: str, on: int, dugaar: int) -> None:
self.garchig = garchig
self.on = on
self.dugaar = dugaar
self.zeeldsen = False
def zeeldekh(self) -> bool:
if self.zeeldsen:
return False
self.zeeldsen = True
return True
def butsaakh(self) -> None:
self.zeeldsen = False
class DVD:
def __init__(self, garchig: str, on: int, khugatsaa: int) -> None:
self.garchig = garchig
self.on = on
self.khugatsaa = khugatsaa
self.zeeldsen = False
def zeeldekh(self) -> bool:
if self.zeeldsen:
return False
self.zeeldsen = True
return True
def butsaakh(self) -> None:
self.zeeldsen = FalseЭнэ код АЖИЛЛАНА. Гэвч түүнийг харахад тааламжгүй мэдрэмж төрөх ёстой.
zeeldekh ба butsaakh нь ГУРВАН УДАА, ЯГ ИЖИЛХЭН давтагдсан. garchig, on, zeeldsen талбарууд ч бас гурван удаа.
Яагаад энэ муу вэ?
Шалтгаан 1: засварлахад аймшигтай. Хэрэв та зээлэх логикт өөрчлөлт оруулах хэрэгтэй болбол (жишээ нь "зээлсэн огноо хадгалах"), ГУРВАН газарт засах хэрэгтэй. Нэгийг мартвал — чимээгүй алдаа.
Шалтгаан 2: шинэ төрөл нэмэхэд. Дөрөв дэх төрөл (аудио ном) нэмэхэд ижил кодыг ДАХИН хуулах хэрэгтэй.
Шалтгаан 3: үнэнийг нуудаг. Энэ гурван зүйл нь үнэндээ БИЕ БИЕТЭЙГЭЭ ХОЛБООТОЙ — тэдгээр нь бүгд "номын санд зээлж болох зүйл" юм. Код нь тэр холбоог илэрхийлэхгүй байна.
Inheritance нь яг энэ гурван асуудлыг шийднэ.
Inheritance-ийг ойлгох хамгийн байгалийн зүйрлэл нь гэр бүл юм.
Хүүхэд эцэг эхээсээ маш олон зүйл өвлөж авдаг. Нүдний өнгө, өндөр, зарим зан төлөв. Хүүхэд тэдгээрийг өөрөө "олж авах" шаардлагагүй — тэдгээр нь автоматаар ирдэг.
Гэхдээ хүүхэд бол эцэг эхийнхээ ХУУЛБАР биш. Хүүхдэд өөрийн гэсэн зүйлс байдаг — эцэг эхэд нь байхгүй авъяас, сонирхол, чадвар.
Мөн заримдаа хүүхэд эцэг эхийнхээ зүйлийг ӨӨРӨӨР хийдэг. Эцэг нь удаан алхдаг байхад хүүхэд нь гүйдэг. Ижил үйлдэл (алхах), өөр гүйцэтгэл.
Программын ертөнцөд:
Эцэг class (parent class, эсвэл base class) — нийтлэг зүйлсийг агуулна
Хүү class (child class, эсвэл subclass) — эцгээсээ БҮГДИЙГ өвлөж авна, дараа нь өөрийн зүйлсийг НЭМНЭ, эсвэл өвлөсөн зүйлээ ӨӨРЧИЛНӨ
Хамгийн чухал дүрэм: хүү class нь эцэг class-ын БҮХ зүйлийг АВТОМАТААР авна. Та юу ч хуулах шаардлагагүй.
Эхлээд хамгийн энгийн хэлбэрээр харъя.
python
class Amitan:
def __init__(self, ner: str) -> None:
self.ner = ner
def unshikh(self) -> str:
return f"{self.ner} чимээ гаргаж байна"
class Nokhoi(Amitan):
pass
nokhoi = Nokhoi("Бор")
print(nokhoi.ner)
print(nokhoi.unshikh())Бор
Бор чимээ гаргаж байнаХоёр мөрийг анхааралтай хараарай.
class Nokhoi(Amitan): — энэ бол inheritance-ийн бичлэг. Class-ын нэрийн ард дугуй хаалт, дотор нь ЭЦЭГ class-ын нэр. Уншихдаа: "Nokhoi бол Amitan -аас өвлөнө."
pass — Курс 1-ээс танил түлхүүр үг: "энд юу ч байхгүй, гэхдээ Python-д хоосон блок хэрэгтэй." Өөрөөр хэлбэл Nokhoi class нь ЮУ Ч НЭМЭЭГҮЙ.
Гэсэн ч гэсэн nokhoi.ner ажиллаж байна. nokhoi.unshikh() ч ажиллаж байна. Хаанаас ирэв?
Amitan -аас. Nokhoi нь __init__ бичээгүй, unshikh бичээгүй — гэхдээ хоёуланг нь ӨВЛӨЖ АВСАН. Тэдгээр нь автоматаар түүний хөрөнгө болсон.
Энэ бол inheritance-ийн бүх мөн чанар: хүү class нь эцгийн бүх зүйлийг үнэгүй авна.
Одоо Nokhoi -д өөрийн гэсэн зүйл нэмье.
python
class Amitan:
def __init__(self, ner: str) -> None:
self.ner = ner
def unshikh(self) -> str:
return f"{self.ner} чимээ гаргаж байна"
class Nokhoi(Amitan):
def hutsakh(self) -> str:
return f"{self.ner}: Хав хав!"
nokhoi = Nokhoi("Бор")
print(nokhoi.unshikh()) # ӨВЛӨСӨН
print(nokhoi.hutsakh()) # ӨӨРИЙНХБор чимээ гаргаж байна
Бор: Хав хав!Nokhoi нь одоо хоёр method-той: unshikh (өвлөсөн) ба hutsakh (өөрийнх).
hutsakh дотор self.ner ашиглаж байгааг анзаараарай. Тэр талбар нь Amitan -ы __init__ -д үүссэн — гэхдээ Nokhoi түүнийг чөлөөтэй ашиглаж чадаж байна. Өвлөсөн зүйл нь ЖИНХЭНЭ, өөрийн зүйл шиг ажиллана.
Эцэг class нь хүүгийн талаар юу ч МЭДЭХГҮЙ. Amitan нь hutsakh method байдгийг мэдэхгүй. Харагдац нь НЭГ ЧИГЛЭЛТЭЙ: хүү нь эцгээ хардаг, эцэг нь хүүгээ хардаггүй.
(Бүлэг 3.4-ийн scope-ын дүрэмтэй ижил санаа: функц гадагшаа харна, гадна тал функц рүү харахгүй.)
Одоо эхний асуудал руу буцаж, түүнийг inheritance-ээр шийдье.
python
class Zuil:
def __init__(self, garchig: str, on: int) -> None:
self.garchig = garchig
self.on = on
self.zeeldsen = False
def zeeldekh(self) -> bool:
if self.zeeldsen:
return False
self.zeeldsen = True
return True
def butsaakh(self) -> None:
self.zeeldsen = False
def baidal(self) -> str:
if self.zeeldsen:
return "зээлсэн"
return "боломжтой"
class Nom(Zuil):
pass
class Setguul(Zuil):
pass
class DVD(Zuil):
passБүх давхардал АРИЛЛАА. zeeldekh, butsaakh, baidal — гурвуулаа НЭГ л газарт бичигдсэн. Гурван хүү class нь тэднийг өвлөж авна.
Туршиж үзье:
python
nom = Nom("Монголын нууц товчоо", 1240)
setguul = Setguul("Шинжлэх ухаан", 2026)
print(nom.baidal())
print(nom.zeeldekh())
print(nom.baidal())
print(nom.zeeldekh()) # хоёр дахь удаа — зээлж болохгүй
print(setguul.baidal()) # сэтгүүл хараахан зээлэгдээгүйболомжтой
True
зээлсэн
False
боломжтойГурван зүйлийг анзаараарай.
Нэгдүгээрт, nom ба setguul нь ТУСДАА объектууд. Номыг зээлсэн ч сэтгүүл хөндөгдөөгүй. Тэдгээр нь ижил class-аас өвлөсөн ч өөр өөр объект — өөр өөр self.zeeldsen утгатай.
Хоёрдугаарт, хоёр дахь zeeldekh() нь False буцаасан. Логик нь ажиллаж байна: аль хэдийн зээлсэн зүйлийг дахин зээлж болохгүй.
Гуравдугаарт, хэрэв та зээлэх логикийг өөрчлөх хэрэгтэй болбол — ГАНЦ газарт засна. Гурван class бүгд автоматаар шинэчлэгдэнэ. Энэ бол inheritance-ийн хамгийн бодит ашиг тус.
isinstance ба төрлийн шалгалтInheritance нь нэг сонирхолтой шинж чанар өгдөг: хүү объект нь ЭЦГИЙН төрөл ч мөн болно.
python
nom = Nom("Монголын нууц товчоо", 1240)
print(isinstance(nom, Nom))
print(isinstance(nom, Zuil))
print(isinstance(nom, Setguul))True
True
Falseisinstance(objekt, Class) нь "энэ объект тэр class-ын жишээ мөн үү" гэж шалгадаг built-in функц.
nom нь Nom мөн — мэдээж.
nom нь мөн Zuil ч БАЙНА. Учир нь Nom нь Zuil -ээс өвлөсөн тул ном бол "нэг төрлийн зүйл" юм. Гэр бүлийн зүйрлэлээр: хүүхэд бол мөн тэр гэр бүлийн гишүүн.
nom нь Setguul БИШ — тэдгээр нь ах дүү, эцэг хүү биш.
Энэ шинж чанар нь маш ХЭРЭГТЭЙ. Тэр нь танд өөр өөр төрлийн объектуудыг НЭГ жагсаалтад хийж, ижил байдлаар боловсруулах боломж өгдөг.
python
nomiin_san: list[Zuil] = [
Nom("Монголын нууц товчоо", 1240),
Setguul("Шинжлэх ухаан", 2026),
DVD("Аав", 2019),
]
for zuil in nomiin_san:
print(f"{zuil.garchig} ({zuil.on}) — {zuil.baidal()}")Монголын нууц товчоо (1240) — боломжтой
Шинжлэх ухаан (2026) — боломжтой
Аав (2019) — боломжтойHint нь list[Zuil] гэдгийг анзаараарай. Гэвч жагсаалтад гурван ӨӨР class-ын объект байна! Яагаад hint нь зөв бэ?
Учир нь гурвуулаа Zuil -ийн ҮР САД юм. Тэдгээр нь бүгд Zuil -ийн бүх зүйлтэй тул Zuil шиг ашиглагдаж чадна.
Энэ бол inheritance-ийн ХАМГИЙН ХҮЧИРХЭГ санаа. Loop нь zuil -ийн жинхэнэ төрлийг мэдэх шаардлагагүй. Тэр зөвхөн garchig, on, baidal() байгааг мэдэж байхад л хангалттай — тэдгээр нь Zuil -д байгаа тул БҮХ үр садад байх БАТАЛГААТАЙ.
Энэ зарчмыг polymorphism (олон хэлбэрт байдал) гэж нэрлэдэг. Нэр томьёог цээжлэх шаардлагагүй, гэхдээ санааг нь ойлгох нь чухал: нэг л код, олон төрлийн объект дээр ажиллана.
Функцийн параметрт эцэг class-ыг hint болгож бичвэл — БҮХ үр садыг хүлээж авна.
python
def zeeldekh_oroldokh(zuil: Zuil) -> None:
if zuil.zeeldekh():
print(f"'{zuil.garchig}' -ыг зээллээ.")
else:
print(f"'{zuil.garchig}' аль хэдийн зээлэгдсэн байна.")
nom = Nom("Монголын нууц товчоо", 1240)
dvd = DVD("Аав", 2019)
zeeldekh_oroldokh(nom)
zeeldekh_oroldokh(dvd)
zeeldekh_oroldokh(nom)'Монголын нууц товчоо' -ыг зээллээ.
'Аав' -ыг зээллээ.
'Монголын нууц товчоо' аль хэдийн зээлэгдсэн байна.zeeldekh_oroldokh нь zuil: Zuil гэсэн hint-тэй. Гэвч бид Nom ба DVD дамжуулж байна — хоёулаа ажиллаж байна.
Дүрэм: эцэг class-ыг hint болгож бичвэл, дуудагч ямар ч үр садыг дамжуулж болно.
Энэ бол маш чухал дизайны хэрэгсэл. Та функцээ хамгийн ерөнхий төрлөөр бичвэл — ирээдүйд нэмэгдэх шинэ class-ууд ч автоматаар ажиллана. Функцийг ЗАСАХ шаардлагагүй.
Хэрэв ирээдүйд AudioNom(Zuil) гэсэн шинэ class нэмвэл, zeeldekh_oroldokh нь ямар ч өөрчлөлтгүйгээр түүнийг хүлээж авна.
Нэг сонирхолтой баримт: Python-д БҮХ class нь object гэдэг class-аас өвлөдөг, та тэгж бичээгүй ч гэсэн.
python
class Zuil:
pass
print(isinstance(Zuil(), object))
print(Zuil.__bases__)True
(<class 'object'>,)__bases__ нь class-ын эцэг class-уудыг харуулна. Zuil нь ямар ч эцэг зарлаагүй ч object нь автоматаар түүний эцэг болсон.
Хуучин Python код дээр та ийм бичлэг харах магадлалтай:
python
class Zuil(object): # хуучин бичлэг
passЭнэ нь Python 2-ын үлдэгдэл. Python 3-д (object) гэж бичих нь шаардлагагүй — тэр автоматаар хийгддэг. Бичихгүй байх нь зөв.
Яагаад энэ чухал вэ? Учир нь object -оос ирдэг зарим method байдаг — жишээ нь print(objekt) дуудахад ажилладаг method. Тэр нь одоохондоо муухай ажиллаж байгаа:
python
nom = Nom("Монголын нууц товчоо", 1240)
print(nom)<__main__.Nom object at 0x000001A2B3C4D5E0>Ойлгомжгүй, ашиггүй. Тэр асуудлыг Бүлэг 8-д __str__ ашиглан шийднэ. Одоохондоо мэдэж байгаад өнгөрөөрэй.
Одоо нэг асуудал үлдлээ. Эхний жишээнд Nom -д zokhiolch (зохиолч), Setguul -д dugaar (дугаар) байсан. Тэдгээр нь ЗӨВХӨН тэр class-д хамааралтай.
Одоохондоо бид тэдгээрийг АЛДСАН — бүх хүү class нь pass л бичсэн.
Тэднийг буцааж оруулах гэж оролдъё:
python
class Nom(Zuil):
def __init__(self, garchig: str, on: int, zokhiolch: str) -> None:
self.zokhiolch = zokhiolch
nom = Nom("Монголын нууц товчоо", 1240, "Тодорхойгүй")
print(nom.zokhiolch)
print(nom.garchig)Тодорхойгүй
Traceback (most recent call last):
File "main.py", line 8, in <module>
print(nom.garchig)
AttributeError: 'Nom' object has no attribute 'garchig'AttributeError: 'Nom' object has no attribute 'garchig' гэдэг нь "Nom объектод garchig гэсэн талбар байхгүй байна" гэсэн үг.
Юу болов? Nom нь ӨӨРИЙН __init__ бичсэн. Тэр нь эцгийн __init__ -ийг ДАРЖ БИЧСЭН. Одоо Zuil.__init__ нь ОГТ АЖИЛЛАХГҮЙ — тиймээс self.garchig, self.on, self.zeeldsen нь хэзээ ч үүсээгүй.
Гэр бүлийн зүйрлэлээр: хүү нь эцгээсээ өвлөх ёстой байсан бүх зүйлээ авахаас ТАТГАЛЗАЖ, зөвхөн өөрийн зүйлээ авсан.
Энэ бол дараагийн хичээлийн сэдэв. Шийдэл нь super() гэдэг функц бөгөөд тэр нь "эцгийн __init__ -ийг эхлээд ажиллуул, дараа нь миний зүйлийг нэм" гэж хэлдэг.
Одоохондоо энэ асуудлыг мэдэж байгаад өнгөрөөрэй. Дараагийн хичээлд бид түүнийг бүрэн шийднэ.
Хэрэв сонирхвол: өөрийн сонирхлын салбараас эцэг-хүү class бичиж үзээрэй. Жишээ нь:
python
class Tyeever:
def __init__(self, ner: str, khurd: int) -> None:
self.ner = ner
self.khurd = khurd
def khodolokh(self) -> str:
return f"{self.ner} {self.khurd} км/ц хурдтай явж байна"
class Mashin(Tyeever):
pass
class Dugui(Tyeever):
passДараа нь list[Tyeever] жагсаалт үүсгээд, loop-оор бүгдийг нь khodolokh() дуудаад үзээрэй. Нэг л код, олон төрөл — polymorphism-ийг өөрийн нүдээр харна.
Мөн хүсвэл: isinstance -ээр туршиж, хүү объект нь эцгийн төрөл ч мөн болохыг батлаарай.
1. __init__ -ыг дарж бичээд super() мартах
AttributeError: 'Nom' object has no attribute 'garchig'Хүү class-д __init__ бичвэл, эцгийнх нь ОГТ ажиллахгүй. Шийдэл нь super() — дараагийн хичээл.
2. Хаалт мартах
python
class Nom: # inheritance БАЙХГҮЙ
...Хэрэв та class Nom(Zuil): гэж бичихээ мартвал — Nom нь Zuil -ээс юу ч өвлөхгүй. Дараа нь:
AttributeError: 'Nom' object has no attribute 'zeeldekh'3. Эцэг рүү хүү class-ыг ашиглах гэж оролдох
python
class Zuil:
def medeelel(self) -> str:
return f"{self.garchig} — {self.zokhiolch}" # zokhiolch нь ЗӨВХӨН Nom-д байнаAttributeError: 'Setguul' object has no attribute 'zokhiolch'Эцэг class нь хүүгийн талбарыг МЭДЭХГҮЙ. Харагдац нь нэг чиглэлтэй. Эцэг class-д зөвхөн БҮХ хүүд байгаа зүйлсийг л ашигла.
4. Инстанц үүсгэхээ мартах
python
print(isinstance(Zuil, object)) # Zuil бол CLASS, объект биш
print(isinstance(Zuil(), object)) # Zuil() бол ОБЪЕКТЭхнийх нь ч бас True буцаана (учир нь class ч бас объект), гэхдээ та өөр зүйл шалгаж байна. Объект үүсгэхийн тулд ЗААВАЛ хаалт: Zuil().
Inheritance нь class-ууд бие биенээсээ шинж чанар, method өвлөж авах механизм.
Бичлэг: class Nokhoi(Amitan): — дугуй хаалт дотор эцэг class-ын нэр.
Хүү class нь эцгийн БҮХ зүйлийг АВТОМАТААР авна. Юу ч хуулах шаардлагагүй.
Хүү class нь өөрийн method, талбар НЭМЖ болно.
Харагдац НЭГ ЧИГЛЭЛТЭЙ: хүү нь эцгээ хардаг, эцэг нь хүүгээ ХАРАХГҮЙ.
Гол ашиг тус: давхардал арилна. Нийтлэг логикийг НЭГ газарт бич, олон class ашиглана. Засварлахад ганц газар засна.
isinstance(objekt, Class) — хүү объект нь ЭЦГИЙН төрөл ч мөн байна. isinstance(nom, Zuil) → True.
Тиймээс өөр өөр төрлийн объектуудыг НЭГ жагсаалтад хийж, ижил байдлаар боловсруулж болно: list[Zuil].
Функцийн hint-д ЭЦЭГ class-ыг бич — дуудагч ямар ч үр садыг дамжуулж болно. Ирээдүйн шинэ class-ууд автоматаар ажиллана.
Энэ зарчмыг polymorphism гэнэ: нэг код, олон төрөл.
Python-д бүх class нь object -оос өвлөдөг. class Zuil(object): гэж бичих нь хуучирсан — зүгээр л class Zuil: бич.
Одоохондоо шийдэгдээгүй асуудал: хүү class-д __init__ бичвэл эцгийнх нь ажиллахаа болино → AttributeError.
Дараагийн хичээлд бид тэр асуудлыг шийднэ. super() гэдэг функц нь хүү class-д "эцгийн __init__ -ийг эхлээд ажиллуул" гэж хэлэх боломж өгдөг. Ингэснээр хүү class нь эцгийн БҮХ талбарыг авч, ДЭЭР НЬ өөрийн талбаруудыг нэмж чадна. Тэр нь inheritance-ийг жинхэнэ ашигтай болгодог түлхүүр юм.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.