Ачааллж байна...
Түвшин 1-ийн сүүлийн бүлэгт тавтай морил. Өмнөх хоёр бүлэгт бид iterator ба generator-ийг дотроос нь ойлгосон. Энэ бүлэгт тэдгээрийг өдөр тутмын кодонд илүү цэвэрхэн хэрэглэх functional хэрэгслүүдийг судална. Functional гэдэг үг програмчлалын нэг хандлагаас гаралтай бөгөөд "функцийг утга мэт дамжуулж, бусад функцэд өгч ажиллах" арга барилыг хэлдэг. Course 2-т та функцийг хувьсагчид оноож, өөр функцэд argument болгон өгч болдгийг мэдсэн. Энэ бүлэг тэр санааг бүрэн ашиглана.
Эхний хичээл маань хоёр сонгодог функцэд зориулагдсан: map ба filter. map нь өгсөн функцийг iterable-ийн элемент бүрт хэрэглэж, үр дүнгүүдийг нь нэг нэгээр гаргадаг. filter нь өгсөн функцээр элемент бүрийг шалгаж, True гэсэн хариу авсныг нь л үлдээдэг. Хичээл 1.2-т бид тэдгээр iterator буцаадаг гэдгийг товч харсан. Одоо тэдгээрийг бүрэн эзэмшинэ.
Гэхдээ энэ хичээлийн гол асуулт “map ба filter хэрхэн ажилладаг вэ” гэдгээс илүүтэй "тэдгээрийг хэзээ хэрэглэх вэ" гэдэг. Учир нь map ба filter-ийн хийдэг бүх ажлыг comprehension болон generator expression хийж чаддаг. Python-д ижил зүйлийг хоёр өөр аргаар бичих боломжтой бол сонголт нь хурд биш, уншигдах байдал дээр тулгуурлах ёстой. Хурдны ялгаа ихэнх тохиолдолд бага бөгөөд хэрэв хэзээ нэгэн цагт энэ нь чухал болбол бид Бүлэг 12-т хэмжиж шалгах аргыг сурна. Энэ хичээлд бид хоёр чиглэлийн жишээг шударгаар харуулна: map, filter илүү уншигддаг тохиолдол ба comprehension илүү уншигддаг тохиолдол.
Ажилтандаа даалгавар өгөх хоёр арга төсөөлөөд үзээрэй. Эхний арга: "Эдгээр хайрцаг бүрийг хаягийн машинаар дамжуул." Энд та шинэ зүйл тайлбарлаагүй. Хаягийн машин аль хэдийн байгаа, бүгд түүнийг мэддэг, та зөвхөн түүний нэрийг хэлсэн. Даалгавар богино, тодорхой. Хоёр дахь арга: "Хайрцаг бүрийг аваад, дээд талын баруун буланд нь цэнхэр наалт наа, наалтан дээр хүлээн авагчийн нэрийг бичээд, дараа нь тавиур дээр тавь." Энд машин байхгүй тул та үйлдлийг өгүүлбэрээр тайлбарлах ёстой.
Одоо ингэж төсөөлье: та хоёр дахь даалгаврыг өгөхдөө эхлээд "яг одоо зохиосон нэргүй машин"-ыг тайлбарлаад, дараа нь "хайрцаг бүрийг тэр машинаар дамжуул" гэж хэлбэл яах вэ? Үүнийг сонсогч нэг даалгаврыг хоёр давхаргаар задлах ёстой болж, ойлгоход хэцүү болно. Шууд "хайрцаг бүрийг аваад ... тавь" гэж нэг өгүүлбэрээр хэлэх нь илүү байгалийн.
map бол эхний аргын хэлбэр: "энэ нэртэй функцээр бүгдийг нь дамжуул." Функц аль хэдийн нэртэй бол map маш цэвэрхэн. Comprehension бол хоёр дахь аргын хэлбэр: "элемент бүрийг аваад, ийм зүйл хий" гэж үйлдлийг шууд бичдэг. Гурав дахь, хамгийн муу хувилбар бол lambda-тай map: нэргүй функцийг яг тэр мөчид зохиогоод, дараа нь map-д өгөх. Энэ хичээлийн уншигдах байдлын дүрэм бүхэлдээ энэ зүйрлэлээс гарна.
Эхлээд хоёр функцийн ажиллагааг тодорхой болгоё. Сурагчдын нэрсийг том үсгээр бичих, дараа нь урт нэрсийг л үлдээх.
python
names = ["бат", "сараа", "тэмүүлэн", "ганаа"]
upper_names = map(str.upper, names)
print(list(upper_names))
def is_long(name):
return len(name) > 4
long_names = filter(is_long, names)
print(list(long_names))['БАТ', 'САРАА', 'ТЭМҮҮЛЭН', 'ГАНАА']
['сараа', 'тэмүүлэн', 'ганаа']map(str.upper, names) нь хоёр argument авна. Эхнийх нь функц: str.upper бол string-ийг том үсгээр бичдэг method бөгөөд бид түүнийг дуудалгүйгээр, хаалтгүйгээр өгсөн. Энэ бол функцийг утга мэт дамжуулах functional санааны гол цөм. Хоёр дахь нь iterable. map нэр бүрт str.upper-ийг хэрэглэж, үр дүнг нэг нэгээр гаргана.
filter(is_long, names) мөн функц ба iterable авна. Энэ удаа функцийн үүрэг нь шалгагч: элемент бүрийн хувьд True эсвэл False буцаана. filter зөвхөн True авсан элементүүдийг өөрчлөлтгүйгээр нь гаргана. Анзаараарай, filter элементийг өөрчилдөггүй, зөвхөн сонгодог. "бат" 3 үсэгтэй тул шүүгдэв. Ийм True/False буцаадаг функцийг англиар predicate гэж нэрлэдэг, "шалгуур функц" гэж ойлгож болно.
Trace: map-ийн lazy ажиллагаа
map ба filter бол iterator, тэгэхээр тэд асуухад л ажилладаг. Үүнийг өмнөх бүлгийн аргаар, функцэд чагнуур тавьж харъя.
python
def add_vat(price):
print(f" [{price}-д НӨАТ нэмж байна]")
return round(price * 1.1)
prices = [1000, 2500, 400]
with_vat = map(add_vat, prices)
print("map бэлэн боллоо")
print(next(with_vat))
print(next(with_vat))map бэлэн боллоо
[1000-д НӨАТ нэмж байна]
1100
[2500-д НӨАТ нэмж байна]
2750Алхам 1 (with_vat = map(add_vat, prices)). map объект үүснэ. Дотор нь хоёр зүйл хадгалагдана: функц add_vat ба prices-ээс авсан шинэ түгээгч, 0-р байрлалд. add_vat нэг ч удаа дуудагдаагүй.
Алхам 2 (эхний next). map түгээгчээсээ 1000-ыг авч, add_vat(1000)-ыг дуудаж, 1100-г буцаана. Түгээгч 1-р байрлалд.
Алхам 3 (хоёр дахь next). 2500 авч, add_vat дуудагдаж, 2750 буцна. Түгээгч 2-р байрлалд. 400 хараахан боловсруулагдаагүй бөгөөд хэрэв бид дахин асуухгүй бол хэзээ ч боловсруулагдахгүй.
Энэ бол Хичээл 2.2-ын generator expression-ийн trace-тэй яг ижил зан чанар. Тэгэхээр шударга харьцуулалт хийхийн тулд нэг чухал зүйлийг тэмдэглэе: map нь generator expression-ийн дүү, list comprehension-ийнх биш. map(add_vat, prices) нь (add_vat(p) for p in prices)-тэй тэнцүү. Хэрэв list хэрэгтэй бол list(map(add_vat, prices)) нь [add_vat(p) for p in prices]-тэй тэнцүү. filter-ийн хувьд ч мөн адил: filter(is_long, names) нь (n for n in names if is_long(n))-тэй тэнцүү. Энэ хичээлийн бүх харьцуулалтыг энэ тэнцүү байдлын дагуу хийнэ.
Жишээ 1: map ялах тохиолдол — бэлэн нэртэй функц
Хэрэглэгч терминалд хэд хэдэн тоог зайгаар тусгаарлан бичсэн, бид тэдгээрийг бүхэл тоо болгож нийлбэрийг нь бодох хэрэгтэй гэж бодъё.
python
raw = "12 7 30 5"
total = sum(map(int, raw.split()))
print(total)54Үүнийг generator expression-оор бичвэл:
python
total = sum(int(s) for s in raw.split())Хоёулаа зөв, хоёулаа lazy, хоёулаа ижил үр дүн өгнө. Гэвч эхний хувилбарыг чангаар уншиж үзээрэй: “split хийсэн хэсгүүдийг int-ээр дамжуулаад нийлбэрийг ол.” Хоёр дахь нь: “split хийсэн хэсэг бүр s-ийн хувьд int(s)-ийн нийлбэрийг ол.” Хоёр дахь хувилбарт s гэдэг хувьсагч зөвхөн дамжуулагчийн үүрэгтэй: тэр гарч ирээд шууд int()-д ороод алга болдог, өөр ямар ч утга агуулдаггүй. Функц аль хэдийн нэртэй бол энэ түр хувьсагч илүүдэл үг болно.
Энэ дүрэм бүх бэлэн нэртэй функцэд хамаарна: Python-ий суурь функцүүд (int, float, str, len, abs, round), string-ийн method-ууд (str.strip, str.lower, str.upper), мөн таны өөрийн бичсэн функцүүд. Өмнөх trace-ийн add_vat жишээнд map(add_vat, prices) нь (add_vat(p) for p in prices)-ээс богино бөгөөд ижил тодорхой.
python
lines = [" Бат ", "Сараа\n", " Ганаа"]
clean = list(map(str.strip, lines))
print(clean)['Бат', 'Сараа', 'Ганаа']Энд str.strip нь мөр бүрийн хоёр талын хоосон зай, шинэ мөрийн тэмдгийг арилгасан. Энэ загвар файлаас уншсан өгөгдлийг цэвэрлэхэд маш түгээмэл.
Жишээ 2: comprehension ялах тохиолдол — lambda шаардлагатай үед
Одоо эсрэг чиглэл. Үнэ бүрт 10 хувийн хөнгөлөлт хийх хэрэгтэй, гэхдээ тусдаа нэртэй функц байхгүй. map ашиглахын тулд яг тэр мөчид нэргүй функц буюу lambda бичих хэрэгтэй болно. Course 2-т та lambda-тай танилцсан: lambda x: x * 0.9 нь “x авч, x * 0.9 буцаадаг нэргүй функц” гэсэн утгатай.
python
prices = [1000, 2500, 400]
discounted = list(map(lambda p: p * 0.9, prices))
print(discounted)[900.0, 2250.0, 360.0]Comprehension-оор бичвэл:
python
discounted = [p * 0.9 for p in prices]Энд comprehension тод ялна. map хувилбарт уншигч эхлээд “lambda p: p * 0.9” гэдэг нэргүй функцийг толгойдоо бүтээгээд, дараа нь "энэ функцийг prices-ийн элемент бүрт хэрэглэ" гэж хоёр давхаргаар ойлгох ёстой. Энэ бол зүйрлэлийн "яг одоо зохиосон нэргүй машин"-ыг тайлбарлаад, дараа нь түүгээр дамжуулахыг хүссэн тохиолдол. Comprehension хувилбар нэг давхаргатай: “p бүрийн хувьд p * 0.9.” Мөн map хувилбарт list(map(lambda ...)) гэж гурван давхар хаалт үүрлэсэн бол comprehension нэг хос хаалттай.
Тиймээс Python нийгэмлэгт ерөнхийдөө lambda-тай map-ыг comprehension-оор солих хэвшил тогтсон. Course 4-т та ruff linter-ийг хэрэглэсэн. ruff-ийн flake8-comprehensions буюу C4 бүлгийн дүрмүүдийн нэг нь C417 бөгөөд lambda-тай map-ыг илрүүлж, generator expression болгон бичихийг санал болгодог. Таны pyproject.toml-д тохируулсан дүрмийн жагсаалтад C4 бүлэг ороогүй бол энэ дүрэм ажиллахгүй. Хүсвэл нэмж болох ч энэ сургалт үүнийг шаардахгүй.
Жишээ 3: map ба filter хослол — comprehension тод ялдаг
Хамгийн тод ялгаа хоёр функцийг хослуулахад гардаг. 90-ээс дээш оноо авсан сурагчдын нэрсийг авъя.
python
students = [
{"name": "Бат", "score": 95},
{"name": "Сараа", "score": 78},
{"name": "Тэмүүлэн", "score": 91},
]
top = list(map(lambda s: s["name"], filter(lambda s: s["score"] >= 90, students)))
print(top)['Бат', 'Тэмүүлэн']Энэ мөр техникийн хувьд зөв ч уншихад хэцүү. Үүнийг ойлгохын тулд дотроос гадагш уншиж, хоёр lambda-г толгойдоо барьж, map-ын argument filter-ийн үр дүн гэдгийг хаалтуудаас ялгах ёстой. Одоо comprehension:
python
top = [s["name"] for s in students if s["score"] >= 90]Энэ хувилбар зүүнээс баруун тийш, ярианы дарааллаар уншигдана: “students доторх s бүрийн хувьд, хэрэв s-ийн оноо 90-ээс дээш бол, s-ийн нэрийг ав.” Нэг s хувьсагч, нэг хос хаалт, ямар ч lambda байхгүй. Comprehension нь хувиргалт ба шүүлтийг нэг бүтцэд нэгтгэх зорилгоор зохиогдсон тул энэ тохиолдолд түүний давуу тал маргаангүй.
Шударгаар хэлэхэд, хоёр функц хоёулаа бэлэн нэртэй бол хослол хүлцэхүйц болно: map(str.upper, filter(is_long, names)) нь уншигдахуйц. Гэхдээ энэ ч гэсэн (n.upper() for n in names if is_long(n))-ээс илүү тод биш. Хослолын хувьд comprehension үргэлж аюулгүй сонголт.
Жишээ 4: map ялах тохиолдол — олон iterable
map-ийн нэг боломж comprehension-д байдаггүй: тэр олон iterable зэрэг хүлээн авдаг. Функц хоёр argument авдаг бол map-д хоёр iterable өгч, тэдгээрээс хос хосоор нь авна.
python
def line_total(price, quantity):
return price * quantity
prices = [1200, 3500, 800]
quantities = [3, 1, 5]
totals = list(map(line_total, prices, quantities))
print(totals)[3600, 3500, 4000]map эхний алхамд prices-ээс 1200, quantities-ээс 3-ыг зэрэг авч, line_total(1200, 3)-ыг дуудна. Дараагийн алхамд 3500 ба 1, гэх мэт. Iterable-уудын аль нэг нь дуусахад map зогсдог бөгөөд энэ нь Хичээл 1.2-ын zip()-тэй ижил дүрэм. Comprehension-оор бичвэл zip шаардлагатай:
python
totals = [line_total(p, q) for p, q in zip(prices, quantities)]Энд map илүү товч, илүү тодорхой. Comprehension хувилбарт p ба q гэдэг хоёр түр хувьсагч, нэмэлт zip(), задлах бичлэг бий. Тэд бүгд зөвхөн утгыг line_total руу дамжуулах үүрэгтэй. Функц бэлэн нэртэй, олон iterable-ийг хос хосоор нь өгөх шаардлагатай бол map хамгийн цэвэр сонголт юм.
Жишээ 5: filter(None, ...) — хоосон утгуудыг цэвэрлэх
filter-ийн нэг тусгай хэлбэр бий: функцийн оронд None өгвөл элементийг өөрөө True эсвэл False гэж үнэлнэ. Python-д хоосон string, 0, None, хоосон list False гэж тооцогддог тул энэ нь "хоосон утгуудыг хаях" хурдан арга юм.
python
answers = ["тийм", "", "үгүй", None, "магадгүй", ""]
filled = list(filter(None, answers))
print(filled)['тийм', 'үгүй', 'магадгүй']Comprehension хувилбар: [a for a in answers if a]. Энд ялгагч тодорхой биш, хоёулаа товч. filter(None, ...) нь туршлагатай Python хөгжүүлэгчдэд танил боловч анх харж буй хүнд “None-оор шүүнэ гэдэг юу гэсэн үг вэ” гэсэн асуулт төрүүлж болно. Comprehension хувилбар “a үнэн бол” гэж шууд уншигддаг. Энэ бол сонголт таны багийн, таны кодын хэв маягаас хамаарах жинхэнэ тэнцүү тохиолдол. Нэг чухал анхааруулга: хэрэв таны өгөгдөлд 0 хүчинтэй утга бол, жишээлбэл оноо эсвэл тоо ширхэг, энэ хоёр хувилбар хоёулаа 0-ийг хаяна. Тийм үед нөхцлийг тодорхой бичих хэрэгтэй: [a for a in answers if a is not None].
Сонголтын дүрэм
Бүх жишээг нэгтгэвэл энгийн дүрэм гарна. Хэрэглэх функц бэлэн нэртэй бол (суурь функц, method, таны өөрийн функц) map ба filter цэвэрхэн, ялангуяа map-д олон iterable өгөх үед. Функцийг lambda-аар зохиох шаардлагатай бол comprehension эсвэл generator expression сонго. Хувиргалт ба шүүлтийг хослуулах бол comprehension бараг үргэлж илүү тодорхой. Эргэлзвэл хоёр хувилбарыг бичээд чангаар уншиж үзээрэй, илүү байгалийн өгүүлбэр шиг сонсогдох нь зөв сонголт. Lazy байдлын хувьд map, filter, generator expression гурвуулаа адил тул pipeline-ий шат болгон аль нь ч ашиглаж болно.
Хүсвэл өөрийн кодын санд, жишээлбэл Course 2 эсвэл Course 3-ын төслүүдэд, comprehension ба map бүхий хэдэн мөр олж, тус бүрийг нөгөө хэлбэрт хөрвүүлж үзээрэй. Аль хувилбар чангаар уншихад илүү байгалийн сонсогдож байна вэ? Сонирхвол map-д гурван iterable өгч үзэж болно, жишээлбэл map(max, [1, 9, 3], [4, 2, 8], [7, 5, 6]) юу гаргах вэ гэдгийг эхлээд таамаглаад шалгах. Мөн Жишээ 4-т quantities жагсаалтыг богиносгож, map хаана зогсохыг харах нь zip-ийн дүрмийг батлах энгийн туршилт. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
TypeError: add_vat() missing 1 required positional argument: ‘price’. “add_vat()-д шаардлагатай ‘price’ argument дутуу байна” гэсэн утгатай. map-д функцийг өгөхдөө санамсаргүй хаалт бичиж дуудсан үед гарна: map(add_vat(), prices). Энэ мөрөнд Python map-ийг дуудахаас өмнө add_vat()-ыг argument-гүйгээр дуудах гэж оролдож, алдаа өгнө. map-д функцийн нэрийг өгөх ёстой, дуудлагыг биш: map(add_vat, prices). Функцийг утга мэт дамжуулах үед хаалт бичихгүй.
TypeError: ‘list’ object is not callable. “list объектыг дуудах боломжгүй” гэсэн утгатай. map эсвэл filter-ийн argument-уудын дарааллыг солих үед гарна: filter(names, is_long). Дүрэм нь үргэлж ижил: эхлээд функц, дараа нь iterable. Хэрэв таны функцийн оронд санамсаргүй тоо, string өгөгдсөн бол “‘int’ object is not callable”, “‘str’ object is not callable” гэсэн ижил төрлийн мессеж гарна. map ба filter lazy тул энэ алдаа map()-ыг үүсгэх мөрөнд биш, анх утга асуух мөрөнд гарч болохыг анхаараарай.
filter хоосон үр дүн гаргах. Мессежгүй алдаа. Шалгуур функц дотор return бичихээ мартсан тохиолдолд гардаг:
python
def is_long(name):
len(name) > 4Энэ функц харьцуулалтыг тооцоолох ч үр дүнг буцаадаггүй тул үргэлж None буцаана. filter None-ыг False гэж үзэх тул бүх элемент шүүгдэж, хоосон үр дүн гарна. Python ямар ч алдаа хэлэхгүй. filter гэнэт юу ч гаргахгүй байвал эхлээд шалгуур функцээ нэг элемент дээр гараар дуудаж, юу буцааж байгааг шалгаарай.
<map object at 0x...> хэвлэгдэх ба хоёр дахь удаа хоосон гарах. Бүлэг 1-2-оос танил. map ба filter iterator буцаадаг тул тэдгээрийг хэвлэхэд утгууд биш объектын тайлбар гарна, утга харахын тулд list() хэрэглэ. Мөн тэд нэг удаагийн хэрэглээтэй: map-ийн үр дүнг хувьсагчид хадгалаад хоёр удаа давтвал хоёр дахь удаа юу ч гарахгүй. Хоёр удаа хэрэглэх бол list() болгож хадгал.
map(функц, iterable) элемент бүрт функцийг хэрэглэж, filter(функц, iterable) функц True буцаасан элементүүдийг л үлдээдэг. Хоёулаа lazy iterator буцаадаг тул тэдгээрийн жинхэнэ дүйцэл нь list comprehension биш, generator expression юм. Тэдгээрийн хийдэг бүхнийг comprehension хийж чаддаг тул сонголт уншигдах байдал дээр тулгуурлана. Бэлэн нэртэй функцтэй, ялангуяа олон iterable-тай үед map цэвэрхэн. Lambda шаардлагатай эсвэл хувиргалт ба шүүлтийг хослуулах үед comprehension илүү тодорхой. filter(None, ...) нь хоосон утгуудыг хаядаг товчлол бөгөөд 0 хүчинтэй утга байх үед болгоомжтой хэрэглэх хэрэгтэй. Функцийг map ба filter-т өгөхдөө хаалтгүйгээр, нэрээр нь өгнө.
Энэ хичээлд бид функцийг өөр функцэд өгөх санааг map ба filter-ээр дамжуулан ашиглалаа. Дараагийн хичээлд энэ санаа Python-д хамгийн их хэрэглэгддэг газраа очно: sorted(), min(), max() функцүүдийн key= argument. Сурагчдыг оноогоор, дараа нь нэрээр нь эрэмбэлэх, хамгийн сүүлд нэвтэрсэн хэрэглэгчийг олох зэрэг өдөр тутмын ажлууд бүгд key функцээр шийдэгддэг. Бид олон түвшний эрэмбэлэлтийг tuple key ашиглан хийж, Python хоёр tuple-ийг хэрхэн харьцуулдгийг trace-ээр алхам алхмаар дагах болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.