Ачааллж байна...
Курс 1-ээс эхлэн та len() функцийг хэдэн зуун удаа ашигласан. Тэр нь бүх зүйл дээр ажилладаг:
python
print(len("Монгол"))
print(len([1, 2, 3]))
print(len({"ner": "Болд", "nas": 25}))
print(len((10, 20)))
print(len({"а", "б", "в"}))6
3
2
2
3Таван огт өөр төрөл. Нэг л функц.
String, list, dictionary, tuple, set — эдгээр нь дотоод бүтцээрээ тэс өөр. String нь тэмдэгтүүдийн дараалал, dictionary нь hash хүснэгт, set нь өөр бүтэц. Тэдгээр нь ямар ч ерөнхий эцэг class-гүй (object -ээс бусад).
Тэгвэл len() яаж бүгд дээр ажиллаж байна вэ?
Хариулт нь эхэндээ энгийн мэт санагдана: тэд бүгд __len__ гэдэг method-той. len(x) дуудахад Python нь дотроо x.__len__() дуудна.
Гэвч тэр хариултын ард ГҮНЗГИЙ санаа нуугдаж байна — тэр нь протокол гэдэг ойлголт бөгөөд Python-ы бүх бүтцийг тайлбарладаг. Энэ хичээлийн жинхэнэ зорилго бол тэр санааг ойлгуулах явдал юм.
Эхлээд практик талаас нь эхэлье.
python
class NomiinSan:
def __init__(self, ner: str) -> None:
self.ner = ner
self.nomnuud: list[str] = []
def nom_nemekh(self, garchig: str) -> None:
self.nomnuud.append(garchig)
san = NomiinSan("Улаанбаатарын номын сан")
san.nom_nemekh("Монголын нууц товчоо")
san.nom_nemekh("Цагаан хэрэм")
print(len(san))Traceback (most recent call last):
File "main.py", line 12, in <module>
print(len(san))
TypeError: object of type 'NomiinSan' has no len()TypeError: object of type 'NomiinSan' has no len() гэдэг нь "NomiinSan төрлийн объектод урт байхгүй" гэсэн үг.
Логиктой — Python нь номын сангийн "урт" нь юу болохыг мэдэхгүй. Номын тоо юу? Гишүүдийн тоо юу? Нэрний тэмдэгтийн тоо юу?
Шийдэл: түүнд ХЭЛЖ ӨГӨХ.
python
class NomiinSan:
def __init__(self, ner: str) -> None:
self.ner = ner
self.nomnuud: list[str] = []
def nom_nemekh(self, garchig: str) -> None:
self.nomnuud.append(garchig)
def __len__(self) -> int:
return len(self.nomnuud)
san = NomiinSan("Улаанбаатарын номын сан")
san.nom_nemekh("Монголын нууц товчоо")
san.nom_nemekh("Цагаан хэрэм")
print(len(san))2Ажиллалаа.
__len__ -ийг задалъя.
Параметр: зөвхөн self.
Буцаах төрөл: -> int. ЗААВАЛ бүхэл тоо, мөн ЗААВАЛ сөрөг биш.
Биед нь: return len(self.nomnuud) — бид дотоод жагсаалтын уртыг буцааж байна. Тэр жагсаалт нь өөрөө __len__ -тэй тул len() нь түүн дээр ажиллана.
Та __len__ -ыг ШУУД дуудаагүй. len(san) гэж бичсэн. Python нь холбоосыг өөрөө хийсэн.
Одоо нэг гоё зүйл болов:
python
khooson_san = NomiinSan("Хоосон")
duurgesen_san = NomiinSan("Дүүрэн")
duurgesen_san.nom_nemekh("Ном")
if khooson_san:
print("Хоосон сан: ном байна")
else:
print("Хоосон сан: ном алга")
if duurgesen_san:
print("Дүүрэн сан: ном байна")
else:
print("Дүүрэн сан: ном алга")Хоосон сан: ном алга
Дүүрэн сан: ном байнаif objekt: ажиллаж байна!
Бид __bool__ гэж юу ч бичээгүй. Гэвч Python нь if дотор объектыг үнэн/худал болгож үнэлж чадаж байна.
Учир нь: __bool__ байхгүй бол Python нь __len__ -ыг ашигладаг. Дүрэм нь энгийн — урт нь 0 бол False, 0 -ээс их бол True.
Энэ бол Курс 1-ээс танил зан төлөв:
python
if not neres: # хоосон жагсаалт бол True
if not tekst: # хоосон string бол True
if not devter: # хоосон dictionary бол TrueОдоо та тэдгээр яагаад ажилладгийг мэдэж байна. Тэдгээр бүгд __len__ -тэй, if нь түүнийг ашиглаж байна.
НЭГ method бичсэн, ХОЁР чадвар авлаа. Энэ бол special method-ын гоо сайхан.
Одоо гүнзгий санаа руу орно.
Протокол гэдэг нь: "хэрэв та ЭДГЭЭР method-уудтай бол, та ЭНЭ ЗҮЙЛИЙГ хийж чадна."
Тэр бол ГЭРЭЭ. Албан ёсны, бичигдсэн гэрээ биш — харин Python-той тохиролцсон дүрэм.
Хэдэн жишээ:
Протокол | Шаардлагатай method | Танд юу боломжтой болох вэ |
|---|---|---|
Урттай байх |
|
|
Хэвлэгдэх |
|
|
Харьцуулагдах |
|
|
Давтагдах |
|
|
Индексжүүлэгдэх |
|
|
Set-д орох |
|
|
Хамгийн чухал зүйл: Python нь таны class-ын НЭРИЙГ, ЭЦГИЙГ, ТӨРЛИЙГ шалгахгүй. Тэр зөвхөн "чи тэр method-той юу?" гэж асууна.
Тэгэхээр len() нь string, list, dictionary дээр яагаад ажилладаг вэ? Учир нь тэд бүгд __len__ -тэй. Өөр ямар ч холбоо байхгүй. Тэд эцэг class хуваалцахгүй, ямар ч гэрээнд гарын үсэг зураагүй. Зөвхөн ижил нэртэй method-той.
Энэ хандлагыг программын ертөнцөд "duck typing" гэж нэрлэдэг. Нэр нь англи хэлний хэлц үгнээс гаралтай: "Хэрэв тэр нугас шиг алхаж, нугас шиг ганганаж байвал — тэр нь нугас юм."
Өөрөөр хэлбэл: Python нь чиний ХЭН болохыг асуухгүй. Тэр зөвхөн чиний ЮУ ХИЙЖ ЧАДАХЫГ асууна.
Хоёр өөр ертөнц төсөөлье.
Ертөнц 1 (хатуу төрөлт): машин жолоодохын тулд танд жолооны үнэмлэх ЗААВАЛ хэрэгтэй. Цагдаа таныг зогсоож, үнэмлэхийг шалгана. Хэрэв танд үнэмлэх байхгүй бол — та жолоодож чадах ч гэсэн зөвшөөрөгдөхгүй.
Ертөнц 2 (Python, duck typing): машин жолоодохын тулд танд зөвхөн ЖОЛООДОХ ЧАДВАР хэрэгтэй. Хэн ч үнэмлэх асуухгүй. Хэрэв та түлхүүрийг эргүүлж, хурдасгуур дарж, жолоо эргүүлж чадаж байвал — та жолоодож байна.
Python нь хоёр дахь ертөнц юм. Түүнд "энэ class нь Sized гэсэн interface -ийг хэрэгжүүлдэг" гэсэн албан ёсны зарлал хэрэггүй. Хэрэв танд __len__ байвал — len() ажиллана. Тэр л болоо.
Энэ нь маш чөлөөтэй, маш хүчирхэг. Та Python-ы бичигдсэн кодыг ямар ч байдлаар өөрчлөхгүйгээр өөрийн class-ыг Python-ы бүх хэрэгсэлтэй ажиллуулж чадна.
Одоо өөр нэг протоколыг үзье.
python
class NomiinSan:
def __init__(self, ner: str) -> None:
self.ner = ner
self.nomnuud: list[str] = []
def nom_nemekh(self, garchig: str) -> None:
self.nomnuud.append(garchig)
def __len__(self) -> int:
return len(self.nomnuud)
def __getitem__(self, index: int) -> str:
return self.nomnuud[index]
san = NomiinSan("Улаанбаатарын номын сан")
san.nom_nemekh("Монголын нууц товчоо")
san.nom_nemekh("Цагаан хэрэм")
san.nom_nemekh("Хөх толбо")
print(san[0])
print(san[-1])
print(san[0:2])Монголын нууц товчоо
Хөх толбо
['Монголын нууц товчоо', 'Цагаан хэрэм']Index ажиллаж байна. Сөрөг index ч ажиллаж байна. SLICING ч ажиллаж байна!
Бид зөвхөн __getitem__ бичсэн. Гэвч Бүлэг 2.4-ийн БҮХ slicing чадвар автоматаар ирлээ.
Яагаад? Учир нь бид түүнийг дотоод жагсаалт руу ДАМЖУУЛЖ байна: return self.nomnuud[index]. Жагсаалт нь өөрөө сөрөг index, slicing-ыг мэддэг тул тэдгээр нь дамжуулагдаж байна.
Гэхдээ бүр гайхалтай зүйл болно:
python
for nom in san:
print(nom)Монголын нууц товчоо
Цагаан хэрэм
Хөх толбоfor loop АЖИЛЛАЖ БАЙНА!
Бид __iter__ гэж юу ч бичээгүй. Тэгвэл яаж?
Учир нь Python нь ХУУЧИН, УХААЛАГ дүрэмтэй: __iter__ байхгүй бол, __getitem__ -ыг ашиглаж, 0, 1, 2, ... гэж дуудна, IndexError гарах хүртэл.
Тэгэхээр for нь дотроо:
san[0] → "Монголын нууц товчоо"
san[1] → "Цагаан хэрэм"
san[2] → "Хөх толбо"
san[3] → IndexError → зогсоно
НЭГ method бичсэн, ГУРВАН чадвар авлаа: index, slicing, for loop.
Мөн in ч ажиллана:
python
print("Цагаан хэрэм" in san)
print("Байхгүй ном" in san)True
Falsein нь __contains__ -ыг хайдаг, олохгүй бол for loop-оор гүйж шалгадаг. for нь ажиллаж байгаа тул in ч ажиллана.
Дөрөв дэх чадвар. Нэг method-оос.
Одоо энэ бүхнийг бодит class дээр нэгтгэе.
python
class Zarlaga:
def __init__(self, ner: str, dun: int, angilal: str) -> None:
self.ner = ner
self.dun = dun
self.angilal = angilal
def __repr__(self) -> str:
return f"Zarlaga(ner={self.ner!r}, dun={self.dun}, angilal={self.angilal!r})"
def __str__(self) -> str:
return f"{self.ner}: {self.dun:,} төгрөг"
class ZarlagiinBurtgel:
def __init__(self) -> None:
self.zarlaguud: list[Zarlaga] = []
def nemekh(self, zarlaga: Zarlaga) -> None:
self.zarlaguud.append(zarlaga)
def niit_dun(self) -> int:
return sum(z.dun for z in self.zarlaguud)
def __len__(self) -> int:
return len(self.zarlaguud)
def __getitem__(self, index: int) -> Zarlaga:
return self.zarlaguud[index]
def __str__(self) -> str:
return f"Бүртгэл: {len(self)} зарлага, нийт {self.niit_dun():,} төгрөг"
burtgel = ZarlagiinBurtgel()
burtgel.nemekh(Zarlaga("Ном", 15000, "боловсрол"))
burtgel.nemekh(Zarlaga("Автобус", 1000, "тээвэр"))
burtgel.nemekh(Zarlaga("Хоол", 8500, "хоол"))Одоо энэ объект нь Python-ы БҮХ хэрэгсэлтэй ажиллана:
python
# 1. len()
print(f"Зарлагын тоо: {len(burtgel)}")
# 2. if
if burtgel:
print("Бүртгэлд зарлага байна")
# 3. index
print(f"Эхний: {burtgel[0]}")
print(f"Сүүлчийн: {burtgel[-1]}")
# 4. slicing
print(f"Эхний хоёр: {burtgel[:2]}")
# 5. for loop
print("\nБүх зарлага:")
for z in burtgel:
print(f" {z}")
# 6. comprehension!
print("\nТээврийн зарлага:")
tseever = [z for z in burtgel if z.angilal == "тээвэр"]
print(tseever)
# 7. sorted!
print("\nДүнгээр эрэмбэлсэн:")
for z in sorted(burtgel, key=lambda z: z.dun, reverse=True):
print(f" {z}")
# 8. sum!
print(f"\nНийт: {sum(z.dun for z in burtgel):,} төгрөг")
# 9. __str__
print(f"\n{burtgel}")Зарлагын тоо: 3
Бүртгэлд зарлага байна
Эхний: Ном: 15,000 төгрөг
Сүүлчийн: Хоол: 8,500 төгрөг
Эхний хоёр: [Zarlaga(ner='Ном', dun=15000, angilal='боловсрол'), Zarlaga(ner='Автобус', dun=1000, angilal='тээвэр')]
Бүх зарлага:
Ном: 15,000 төгрөг
Автобус: 1,000 төгрөг
Хоол: 8,500 төгрөг
Тээврийн зарлага:
[Zarlaga(ner='Автобус', dun=1000, angilal='тээвэр')]
Дүнгээр эрэмбэлсэн:
Ном: 15,000 төгрөг
Хоол: 8,500 төгрөг
Автобус: 1,000 төгрөг
Нийт: 24,500 төгрөг
Бүртгэл: 3 зарлага, нийт 24,500 төгрөгЕС ЧАДВАР. ХОЁР method-оос (__len__ ба __getitem__).
comprehension, sorted, sum — эдгээр нь бүгд for loop дээр баригдсан. for нь __getitem__ дээр ажиллаж байгаа тул тэдгээр бүгд автоматаар ирсэн.
Энэ бол протоколын жинхэнэ хүч. Та Python-ы гэрээг биелүүлмэгц, Python-ы БҮХ хэрэгсэл таны объект дээр ажиллаж эхэлнэ. Та тэдгээрийг тусад нь бичих ШААРДЛАГАГҮЙ.
Одоо та Курс 1-ээс өнөөг хүртэлх ОЛОН зүйлийг шинэ нүдээр харах болно.
Яагаад len() нь бүх зүйл дээр ажилладаг вэ? → Тэд бүгд __len__ -тэй.
Яагаад for нь string, list, dictionary дээр ажилладаг вэ? → Тэд бүгд давтагдах протоколыг биелүүлдэг.
Яагаад if not jagsaalt: ажилладаг вэ? → __len__ нь 0 буцаавал False.
Яагаад nom in nomnuud ажилладаг вэ? → __eq__ (Хичээл 8.2) + давтагдах чадвар.
Яагаад sorted(jagsaalt, key=...) ажилладаг вэ? → sorted нь давтагдах ямар ч зүйлийг хүлээж авдаг.
Яагаад print(x) бүх зүйл дээр ажилладаг вэ? → __str__ эсвэл __repr__.
Яагаад list-ийг set-д хийж болохгүй вэ? (Бүлэг 5) → List-д __hash__ байхгүй, учир нь тэр өөрчлөгддөг.
Python бол протоколуудын систем. Түүний бүх "илбэ" нь энэ л зарчим дээр баригдсан. Та тэдгээр протоколыг мэдмэгц, Python-ы бүх зан төлөв ойлгомжтой болно.
Шударгаар хэлье: __getitem__ -ээр for loop авах нь ХУУЧИН арга бөгөөд хязгаарлагдмал.
Хэрэв таны объект нь жинхэнэ дараалал (index-тэй, эрэмбэтэй) БИШ бол — энэ арга ажиллахгүй. Жишээ нь та dictionary шиг зүйл, эсвэл файлын мөрүүд шиг зүйл бүтээж байвал.
Тэр тохиолдолд __iter__ гэдэг method хэрэгтэй болно. Гэвч түүнийг ойлгохын тулд generator ба iterator -ыг мэдэх ёстой — тэдгээр нь Бүлэг 13-ын сэдэв.
Тиймээс би __iter__ -ыг ЭНД зааж БАЙХГҮЙ. Одоохондоо __getitem__ нь бидний хэрэгцээнд бүрэн хангалттай, учир нь бидний class-ууд (номын сан, зарлагын бүртгэл) нь бүгд жагсаалт дээр баригдсан жинхэнэ дараалал юм.
Бүлэг 13-т бид энэ асуудал руу буцаж, for loop-ийн ард ЮУ болж байгааг бүрэн тайлбарлана. Тэр үед энэ хичээлийг эргэж хараарай — бүх зүйл нэгдэх болно.
Хэрэв сонирхвол: __len__ -гүй class үүсгээд, len(objekt) дуудаж, TypeError: object of type 'X' has no len() -ыг өөрийн нүдээр хараарай. Дараа нь __len__ нэмээд, len() БА if objekt: хоёулаа гэнэт ажиллаж эхэлснийг мэдрээрэй.
Дараа нь __getitem__ нэмээд, objekt[0], objekt[1:3], for x in objekt, [x for x in objekt], sorted(objekt) — бүгдийг туршаад үзээрэй. Тэдгээр бүгд НЭГ method-оос ирсэн гэдгийг мэдрэх нь энэ хичээлийн хамгийн үнэ цэнэтэй мөч юм.
1. __len__ -аас тоо биш зүйл буцаах
python
def __len__(self) -> int:
return self.nomnuud # жагсаалт буцаалаа!TypeError: 'list' object cannot be interpreted as an integer__len__ нь ЗААВАЛ бүхэл тоо буцаах ёстой. Засвар: return len(self.nomnuud).
2. Сөрөг тоо буцаах
python
def __len__(self) -> int:
return -1ValueError: __len__() should return >= 0__len__() should return >= 0 — урт нь сөрөг байж болохгүй. Логиктой.
3. __getitem__ -д IndexError гаргахгүй байх
python
def __getitem__(self, index: int) -> str:
if index >= len(self.nomnuud):
return "" # хоосон string буцаалаа — БУРУУ!
return self.nomnuud[index]Одоо for loop нь ХЭЗЭЭ Ч ЗОГСОХГҮЙ. Тэр нь IndexError -ыг хүлээж байгаа боловч түүнийг хэзээ ч авахгүй тул мөнхөд гүйнэ.
Дүрэм: __getitem__ нь хязгаараас гарахад ЗААВАЛ IndexError гаргах ёстой. Хамгийн энгийн арга: дотоод жагсаалт руу дамжуул (return self.nomnuud[index]) — тэр нь өөрөө зөв алдаа гаргана.
4. len() -ыг ШУУД дуудах
python
print(san.__len__())Ажиллана, гэхдээ буруу зуршил. len(san) гэж бич.
5. __len__ -ыг эцэг class-аас өвлөхгүй байх
python
class Zuil:
def __len__(self) -> int:
return len(self.nomnuud)
class Nom(Zuil):
passХэрэв Nom -д nomnuud талбар байхгүй бол — AttributeError. Эцэг class-д зөвхөн БҮХ хүүд байгаа зүйлсийг л ашигла (Бүлэг 7.4-ийн сургамж).
__len__(self) -> int нь len(x) -ийн зан төлөвийг тодорхойлно. ЗААВАЛ сөрөг биш бүхэл тоо буцаана.
Гэнэтийн шагнал: __len__ бичихэд if objekt: ч бас автоматаар ажиллана (урт 0 бол False).
__getitem__(self, index) нь x[0] -ийг ажиллуулна. Дотоод жагсаалт руу дамжуулбал — сөрөг index, SLICING ч автоматаар ирнэ.
Мөн for loop, in, comprehension, sorted, sum — бүгд автоматаар ажиллаж эхэлнэ. ХОЁР method → ЕС чадвар.
ПРОТОКОЛ гэдэг нь: "хэрэв та ЭДГЭЭР method-уудтай бол, та ЭНЭ ЗҮЙЛИЙГ хийж чадна." Албан ёсны зарлал ХЭРЭГГҮЙ.
Python нь чиний ХЭН болохыг асуухгүй — зөвхөн ЮУ ХИЙЖ ЧАДАХЫГ асууна. Энэ хандлагыг duck typing гэнэ.
Тиймээс len() нь string, list, dict, tuple, set дээр ажилладаг — тэдгээр нь ямар ч холбоогүй, зөвхөн бүгд __len__ -тэй.
Python бол протоколуудын систем. Түүний бүх "илбэ" нь энэ зарчим дээр баригдсан.
__getitem__ нь хязгаараас гарахад ЗААВАЛ IndexError гаргах ёстой — эс тэгвээс for loop мөнхөд гүйнэ.
__getitem__ -ээр for авах нь хуучин, хязгаарлагдмал арга. Жинхэнэ шийдэл нь __iter__ бөгөөд түүнийг Бүлэг 13-т үзнэ.
Дараагийн хичээлээр Бүлэг 8 дуусна. Тэнд бид property -ыг үзнэ. Асуулт нь: method-ыг ТАЛБАР шиг харагдуулж болох уу? nom.khuudas_too() гэж хаалттай бичихийн оронд nom.khuudas_too гэж хаалтгүй бичиж болох уу? Хариулт нь тийм — мөн тэр нь маш чухал загвар юм. Мөн тэнд та @ тэмдгийг АНХ УДАА харна. Тэр нь decorator бөгөөд Бүлэг 10-д бүрэн тайлагдана.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.