Ачааллж байна...
Өмнөх хичээлд бид iterable ба iterator хоёрыг салгаж, for loop далдуур iter() дуудаж түгээгч авдаг, дараа нь түүнээс next()-ээр элемент авдаг гэдгийг хагас нээсэн. Тэнд iter() ба next() хоёр зөвхөн туслах дүр байсан. Энэ хичээлд тэд гол дүр болно. Бид for loop-ийг огт ашиглахгүйгээр давталтыг бүхэлд нь гараараа удирдаж, алхам бүрт юу болж байгааг нүдээрээ харна. Энэ хичээлийн төгсгөлд for loop таны хувьд ид шид байхаа больж, бидний өөрсдөө бичиж чадах энгийн хэдэн мөрийн товчлол болж хувирна.
Энэ хичээлд бас нэгэн шинэ танил гарч ирнэ: StopIteration. Өмнөх хичээлийн trace-д түгээгч хоосроход "дууслаа" гэсэн дохио өгдөг гэж бид хэлсэн. Тэр дохио бол яг энэ exception юм. Өдөр тутмын кодонд та түүнийг хэзээ ч хараагүй байх магадлалтай, учир нь for loop түүнийг таны өмнөөс чимээгүй барьдаг. Өнөөдөр бид түүнийг чөлөөтэй гаргаж, traceback-ийг нь Монгол хэлээр уншиж, Python яагаад "дууслаа" гэдгийг алдааны хэлбэрээр илэрхийлдгийг ойлгоно.
Iter() ба next()-ийн утгыг товчхон томьёолбол: iter(x) нь “x-ээс шинэ түгээгч авчирч өг” гэсэн хүсэлт, next(it) нь "түгээгч минь, дараагийнхаа элементийг өгөөч" гэсэн хүсэлт юм. Энэ хоёр функц бол Python-ий давталтын бүх систем зогсож буй хоёр тулгуур бөгөөд энэ түвшний үлдсэн бүх хичээл тэдгээрийн дээр тулгуурлана.
Дэвтэр тараадаг түгээгчийн зүйрлэл рүүгээ буцъя. Өмнө нь ангийн бүх сурагч эгнээнд зогсоод, түгээгч эгнээний дагуу автоматаар явж, хүн бүрт нэг нэгээр өгч байсан гэж төсөөлөөд үзээрэй. Энэ бол for loop. Түгээгч хэзээ эхлэх, хэзээ зогсохыг эгнээ өөрөө зохицуулдаг, багш зөвхөн "тараа" гэж хэлэхэд л хангалттай.
Одоо багш эгнээг тараагаад, сурагчдыг нэг нэгээр нь өөрөө дуудаж байна гэж бодъё. "Бат, ирээд дэвтрээ ав." Түгээгч нэгийг өгнө. "Сараа, ир." Түгээгч дахиад нэгийг өгнө. Багш хэзээ дуудахаа, хэдэн хүн дуудахаа, хооронд нь өөр юу хийхээ бүрэн өөрөө шийднэ. Энэ бол гараар next() дуудах. Гэхдээ энэ эрх чөлөө нэг үүрэгтэй ирдэг: дэвтэр дууссаны дараа багш дахин нэг хүн дуудвал түгээгч хоосон гараа өргөөд "дууслаа!" гэж чангаар хэлнэ. Эгнээ байхад энэ дуудлагыг эгнээ өөрөө барьж, тайван зогсдог байсан бол одоо багш өөрөө сонсож, хариу үйлдэл хийх ёстой.
Энэ "дууслаа!" гэсэн хашгираан бол StopIteration юм. Энэ хичээлийн туршид бид эгнээгүй ажиллаж, тэр хашгирааныг эхлээд сонсох, дараа нь зөв барих аргыг сурна.
Гурван тоотой list-ээс түгээгч авч, дөрвөн удаа next() дуудъя. Гурван элемент байгаа тул дөрөв дэх дуудлага юу болохыг харахыг хүсэж байна.
python
numbers = [10, 20, 30]
it = iter(numbers)
print(next(it))
print(next(it))
print(next(it))
print(next(it))Ажиллуулахад эхний гурван мөр хэвлэгдээд, дараа нь traceback гарна:
10
20
30
Traceback (most recent call last):
File "C:\python-gunzgii\manual.py", line 7, in <module>
print(next(it))
^^^^^^^^
StopIterationЭнэ кодыг санах ойн түвшинд мөрдөж үзье.
Алхам 1 (numbers = [10, 20, 30]). Санах ойд гурван элементтэй list үүснэ. Ямар ч түгээгч байхгүй.
Алхам 2 (it = iter(numbers)). iter() нь numbers-ээс шинэ list_iterator авчирна. Санах ойд: list хэвээрээ, мөн it гэдэг нэртэй түгээгч, түүний байрлал 0. Өмнөх хичээлийн for loop-оос ялгаатай нь энэ удаа түгээгч нэртэй, тиймээс бид түүнийг өөрсдөө барьж, хүссэн үедээ хандаж чадна.
Алхам 3 (эхний next(it)). Түгээгч 0-р байрлалын элемент 10-ыг өгч, байрлалаа 1 болгоно. 10 хэвлэгдэнэ. List өөрөө огт өөрчлөгдөөгүй.
Алхам 4 (хоёр дахь next(it)). 20 өгөгдөж, байрлал 2 болно.
Алхам 5 (гурав дахь next(it)). 30 өгөгдөж, байрлал 3 болно. Энэ нь list-ийн уртаас хэтэрсэн байрлал, гэхдээ түгээгч одоохондоо үүнийг "мэдсэнгүй", учир нь хэн ч дараагийнхыг асуугаагүй байна.
Алхам 6 (дөрөв дэх next(it)). Түгээгч 3-р байрлалд элемент байхгүйг олж мэдэв. Тэр утга буцаахын оронд StopIteration exception шидэв. Бидний кодонд энэ exception-ийг барих try/except байхгүй тул програм энэ мөрөнд зогсож, traceback хэвлэгдэв.
Одоо traceback-ийг Монгол хэлээр уншъя. “Traceback (most recent call last)” гэдэг нь "хамгийн сүүлийн дуудлага хамгийн доор байрлах дарааллаар алдааны мөр" гэсэн утгатай, Course 4-т бид үүнийг олон удаа уншсан. Дараагийн мөр нь алдаа manual.py файлын 7-р мөрөнд, модулийн үндсэн түвшинд (<module>) гарсныг хэлж байна. ^^^^^^^^ тэмдгүүд нь тэр мөрийн аль хэсэг, тухайлбал next(it) дуудлага, алдаа шидсэнийг заан харуулж байна. Хамгийн сүүлийн мөр нь exception-ийн нэр: StopIteration. Анзаарсан бол түүний ард тайлбар мессеж огт алга. Ихэнх exception "юу буруу болсныг" тайлбарладаг бол StopIteration-д тайлбар хэрэггүй, учир нь түүний нэр бүх зүйлийг хэлж байна: "давталт зогслоо."
Энд нэг чухал санаа бий. StopIteration бол "алдаа" гэхээсээ илүү дохио юм. Python-д exception-ийг зөвхөн буруу зүйл болсныг мэдэгдэх биш, мөн кодын хэвийн урсгалын тухай мэдээлэл дамжуулахад ашигладаг. Түгээгч "бүх зүйл хэвийн, зүгээр л үлдсэн юм алга" гэж хэлэх ёстой бөгөөд Python тэр мэдээг exception-ээр дамжуулахаар сонгосон. Тиймээс traceback харагдсан нь бид буруу зүйл хийснийг биш, харин дохиог сонсох хүнгүй орхисныг хэлж байна.
Жишээ 1: for loop-ийг гараараа бичих
Одоо бид for loop-ийг iter(), next(), StopIteration гурваар бүрэн дахин бичих мэдлэгтэй боллоо. Доорх хоёр кодыг зэрэгцүүлж харъя. Эхнийх нь бидний мэддэг хэлбэр:
python
names = ["Бат", "Сараа", "Тэмүүлэн"]
for name in names:
print(name)Хоёр дахь нь for loop-ийн ард юу болдгийг шууд бичсэн хэлбэр:
python
names = ["Бат", "Сараа", "Тэмүүлэн"]
it = iter(names)
while True:
try:
name = next(it)
except StopIteration:
break
print(name)Хоёулаа яг ижил гаралт өгнө:
Бат
Сараа
ТэмүүлэнХоёр дахь кодыг мөр мөрөөр нь задалъя. it = iter(names) нь loop-ийн эхэнд нэг удаа түгээгч авчирна. Энэ нь for loop-ийн in names хэсгийн далд ажил юм. while True: нь "хязгааргүй давт" гэсэн утгатай, зогсох шийдвэрийг дотор нь гаргана. name = next(it) нь дараагийн элементийг аваад хувьсагчид онооно, энэ бол for loop-ийн for name хэсгийн далд ажил. Хэрэв next() StopIteration шидвэл except StopIteration: break түүнийг барьж, loop-оос гарна. Энэ нь for loop хоосорсон түгээгчтэй тулгарахдаа чимээгүй зогсдог тэр зан чанар юм. Эцэст нь print(name) бол loop-ийн бие, for loop-д бидний бичдэг хэсэг.
Шударга байхад, Python for loop-ийг яг энэ Python кодоор биш, C хэл дээр илүү хурдан хэлбэрээр гүйцэтгэдэг. Гэвч логикийн хувьд хоёр нь ижил бөгөөд энэ бол for loop-ийн бүрэн тайлбар юм. Одооноос эхлэн for loop бичих бүрдээ та түүний ард iter() нэг удаа, next() олон удаа дуудагдаж, эцэст нь StopIteration чимээгүй барьдгийг мэдэж байна. Хоёр хувилбарын аль нь уншихад хялбар байгаа нь мэдээж ойлгомжтой, тиймээс энэ while хувилбарыг бодит кодонд бичих шаардлага бараг гардаггүй. Түүний үнэ цэнэ нь ойлголтод байна.
Жишээ 2: next()-ийн хоёр дахь argument
StopIteration-ийг try/except-ээр барих нь заримдаа хэтэрхий урт санагдана. Ялангуяа "нэг элемент авъя, байхгүй бол ямар нэг анхдагч утга хэрэглэе" гэсэн тохиолдолд. Ийм үед next() хоёр дахь argument хүлээн авдаг: түгээгч хоосон байвал exception шидэхийн оронд энэ утгыг буцаана.
python
queue = ["Бат"]
it = iter(queue)
print(next(it, "дараалал хоосон"))
print(next(it, "дараалал хоосон"))
print(next(it, "дараалал хоосон"))Бат
дараалал хоосон
дараалал хоосонЭхний дуудлагад түгээгчид элемент байсан тул "Бат" буцав, байрлал 1 болов. Хоёр дахь дуудлагад элемент үлдээгүй ч traceback гарсангүй, оронд нь бидний өгсөн "дараалал хоосон" гэсэн string буцав. Гурав дахь дуудлага ч мөн адил. Хоёр дахь argument-ийг өгсөн тохиолдолд next() огт exception шидэхгүй, тиймээс try/except шаардлагагүй. Анхдагч утга ямар ч төрөлтэй байж болно, ихэвчлэн None ашигладаг. Гэхдээ анхаар: хэрэв таны түгээгч өөрөө None элементтэй байж болох бол буцаж ирсэн None нь "хоосон" гэсэн утгатай юу, эсвэл жинхэнэ элемент үү гэдгийг ялгах боломжгүй болно. Ийм үед try/except хэлбэр илүү найдвартай.
Жишээ 3: эхний тохирох элементийг олох
Гараар next() дуудах хамгийн бодит, өдөр тутмын хэрэглээний нэг бол "энэ жагсаалтаас нөхцөлд тохирох эхний элементийг олох" явдал. Өмнөх хичээлд бид filter() iterator буцаадаг гэдгийг харсан. Түүнийг next()-тэй хослуулбал:
python
scores = [72, 65, 94, 88, 97]
first_excellent = next(filter(lambda s: s >= 90, scores), None)
print(first_excellent)
first_perfect = next(filter(lambda s: s == 100, scores), None)
print(first_perfect)94
NoneЭхний мөрийг дотроос нь гадагш уншъя. filter(...) нь 90-ээс дээш онооны түгээгч үүсгэнэ. Өмнөх хичээлд ярьсан "асуухад нь л бэлтгэдэг" чанараас болж энэ мөчид ямар ч шүүлт хараахан хийгдээгүй. Дараа нь next() түгээгчээс нэг элемент асууна. Түгээгч 72-ыг шалгаад алгасна, 65-ыг шалгаад алгасна, 94-ийг шалгаад тохирсон тул буцаана. 88 ба 97-г хэн ч асуугаагүй тул тэд огт шалгагдсангүй. Хоёр дахь тохиолдолд 100 оноо байхгүй тул түгээгч бүх элементийг шалгаад хоосорно, next() анхдагч утга None-ыг буцаана.
Энэ аргын давуу тал нь хариултаа олмогц зогсдогт оршино. Хэрэв scores сая элементтэй, тохирох элемент гурав дахь байрлалд байсан бол зөвхөн гурван элемент шалгагдана. Үүнийг list-ийн хувилбартай харьцуулбал илүү тод харагдана: [s for s in scores if s >= 90][0] нь бүх сая элементийг шалгаж, бүх тохирохыг шинэ list-д хийсний дараа л эхнийхийг авна. Мөн тохирох элемент байхгүй бол IndexError шиднэ. Дараагийн бүлэгт generator expression-ийг сурсны дараа энэ загвар бүр ч цэвэрхэн харагдах болно.
Жишээ 4: next() ба for-ийг хослуулах
Гараар next() дуудах нь for loop-ийг орлох биш, түүнтэй хамтран ажиллаж чаддаг. Хамгийн түгээмэл жишээ бол гарчгийн мөртэй CSV файл. scores.csv файлд гурван мөр байна гэж бодъё: “name,score”, "Бат,90", "Сараа,85".
python
with open("scores.csv", encoding="utf-8") as f:
header = next(f)
print("Гарчиг:", header.strip())
for line in f:
print("Өгөгдөл:", line.strip())Гарчиг: name,score
Өгөгдөл: Бат,90
Өгөгдөл: Сараа,85Үүнийг trace хийе. Файл нээгдэхэд f нь өөрөө түгээгч бөгөөд унших байрлал нь файлын эхэнд байна. next(f) эхний мөрийг уншиж буцаана, байрлал хоёр дахь мөрний эхэнд шилжинэ. Дараа нь for loop iter(f) дуудна. Өмнөх хичээлээс бид файл бол iterator гэдгийг мэднэ, тиймээс iter(f) өөрийгөө буцаана, шинэ түгээгч үүсэхгүй. Тиймээс for loop эхнээс биш, next() орхисон газраас, хоёр дахь мөрөөс эхэлнэ. Гарчгийн мөр давхар уншигдахгүй.
Энэ жишээ хоёр хичээлийн мэдлэгийг нэгтгэж байна. Хэрэв f нь iterator биш, list шиг iterable байсан бол for loop шинэ түгээгч авч, эхнээс нь эхэлж, гарчгийг дахин хэвлэх байсан. Файл iterator учраас next() ба for нэг түгээгчийг хуваалцаж, бие биенийхээ ажлыг үргэлжлүүлдэг. Iterable ба iterator-ийн ялгаа энд бодит, ашигтай үр дүн өгч байна.
Хүсвэл Жишээ 1-ийн while хувилбарыг өөрөө бичиж, list-ийн оронд string ("сайн") ашиглаж үзээрэй. Үсэг бүр тусдаа хэвлэгдэх үү? Өөр нэг санаа: Жишээ 4-т next(f)-ийг хоёр удаа дуудвал for loop хэдэн мөр хэвлэх вэ гэдгийг эхлээд таамаглаад, дараа нь шалгаж болно. Мөн next()-ийн хоёр дахь argument-д 0, хоосон list зэрэг өөр утгууд өгч туршихыг сонирхож болно. Эдгээр бүгд санал бөгөөд дараагийн хичээл тэдгээрийг хийсэн гэж үзэхгүй.
StopIteration (тайлбаргүй traceback). Энэ нь "давталт зогслоо" гэсэн утгатай. Хоосорсон түгээгчээс next() дуудаад, түүнийг барих try/except байхгүй үед гарна. Шийдэл нь гурван замын аль нэг: next()-д хоёр дахь argument өгөх (next(it, None)), try/except StopIteration-ээр барих, эсвэл гараар давтах шаардлагагүй бол for loop ашиглах. Traceback-ийн ^^^^ тэмдэг аль next() дуудлага хоосон түгээгчтэй тулгарсныг яг заадаг тул тэр мөрөөс эхэлж хараарай.
TypeError: ‘list’ object is not an iterator. Өмнөх хичээлийн танил. “list объект бол iterator биш” гэсэн утгатай. next()-ийг iter()-гүйгээр list, dict, string, range дээр шууд дуудсан. Шийдэл нь эхлээд түгээгч авах: it = iter(numbers), дараа нь next(it).
Үргэлж эхний элемент буцах. Энэ алдаа мессежгүй тул анзаарахад хэцүү. Доорх кодыг хараарай:
python
names = ["Бат", "Сараа"]
print(next(iter(names)))
print(next(iter(names)))Бат
БатХоёр мөр хоёулаа "Бат" хэвлэв. Шалтгаан нь iter(names) бүр шинэ түгээгч авчирдаг, шинэ түгээгч бүр 0 байрлалаас эхэлдэг. Хоёр дахь мөр өмнөх түгээгчийг үргэлжлүүлсэнгүй, бүр шинээр эхэлсэн. Хэрэв энэ загвар while loop дотор байвал loop хэзээ ч дуусахгүй, үргэлж нэг ижил элемент авсаар байна. Шийдэл нь түгээгчийг нэг удаа үүсгээд хувьсагчид хадгалах: it = iter(names), дараа нь next(it)-ийг хэдэн ч удаа дуудах.
Гарчгийн мөр өгөгдөлд холилдох эсвэл алга болох. Жишээ 4-ийн загварыг хэрэглэхдээ файлын оронд файлын агуулгыг list болгосон бол (lines = f.readlines()) next(iter(lines)) гарчгийг авах ч for loop дахин эхнээс нь эхэлж гарчгийг давхар уншина. Эсрэгээр, next(f)-ийг санамсаргүй хоёр удаа дуудвал эхний өгөгдлийн мөр чимээгүй алга болно. Аль түгээгчийг ашиглаж байгаагаа, тэр нь iterator мөн эсэхийг үргэлж бодолцоорой.
iter(x) нь x-ээс шинэ түгээгч авчирдаг, next(it) нь түгээгчээс дараагийн элементийг авдаг. Түгээгч хоосорвол next() StopIteration exception шиднэ. Энэ нь алдаа гэхээсээ илүү "давталт зогслоо" гэсэн хэвийн дохио юм. for loop бол iter()-ийг нэг удаа, next()-ийг олон удаа дуудаж, StopIteration-ийг чимээгүй барьж зогсдог while loop-ийн товчлол бөгөөд бид түүнийг өөрсдөө бүрэн бичиж үзлээ. next()-ийн хоёр дахь argument нь exception-ийн оронд анхдагч утга буцаадаг бөгөөд next(filter(...), None) загвар нь эхний тохирох элементийг хамгийн хэмнэлттэйгээр олдог. Iterator-ийг next() ба for хоёр хуваалцаж, бие биенийхээ ажлыг үргэлжлүүлж чаддаг.
Одоо бид түгээгчийн хэрэглэгч талыг бүрэн ойлголоо: түүнээс хэрхэн асуух, тэр хэрхэн хариулах, хэрхэн "дууслаа" гэж хэлэхийг. Дараагийн хичээлд бид эсрэг тал руу шилжинэ. Бусдын бүтээсэн түгээгчийг ашиглахын оронд өөрсдийн түгээгчийг бүтээнэ. __iter__ ба __next__ method-уудтай Countdown class бичиж, Course 2-т сурсан тусгай method-уудын мэдлэг энд хэрхэн газардахыг харна. Өнөөдөр бидний гадаас дуудсан next() ба StopIteration-ийг маргааш бид class-ийн дотроос өөрсдөө бичих болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.