Ачааллж байна...
Өмнөх бүлэгт itertools-ийг iterator-уудтай ажиллах бэлэн хэрэгслийн хайрцаг гэж танилцуулсан. Python-ий стандарт санд түүний ах дүү модуль бий: functools. Нэр нь “function tools” буюу "функцийн хэрэгслүүд" гэсэн үгийн товчлол. itertools iterator-уудыг боловсруулдаг бол functools функцүүдийг өөрсдийг нь боловсруулдаг: функцээс шинэ функц бүтээх, функцийн үр дүнг санах, функцийг олон элементэд дараалан хэрэглэх. Энэ бүлгийн functional сэдэвтэй бүрэн нийцэж байгаа биз: функц бол утга, түүнийг авч, өөрчилж, буцааж болно.
Энэ хичээлд бид гурван хэрэгслийг судална бөгөөд тус бүрт шударга үнэлгээ өгнө. partial функцийн зарим argument-ийг урьдчилан бэхэлж, шинэ, илүү энгийн функц бүтээдэг. Энэ нь өдөр тутмын кодонд хааяа хэрэгтэй болдог, ялангуяа өмнөх хичээлүүдийн map ба key-тэй хамт. reduce олон элементийг нэг утга болгон "эвхдэг". Энэ бол functional програмчлалын сонгодог хэрэгсэл боловч Python-д бараг хэзээ ч шаардлагагүй, учир нь түүний ихэнх ажлыг илүү тодорхой бэлэн функцүүд хийдэг. Үүнийг бид шударгаар хэлнэ. lru_cache бол энэ хичээлийн од. Тэр функцийн өмнөх үр дүнг санаж, ижил argument-аар дахин дуудахад тооцоолохгүйгээр шууд буцаадаг.
lru_cache-ийн тухай "функцийг хурдасгадаг" гэх мэдэгдэл энэ сургалтын зарчмаар хэмжилтгүйгээр гарах ёсгүй. Тиймээс бид сонгодог рекурсив Fibonacci функцийг кэштэй ба кэшгүйгээр ажиллуулж, хугацааг хэмжиж, дуудлагын тоог тоолно. Үр дүн энэ сургалтын хамгийн гайхмаар хэмжилтүүдийн нэг болох бөгөөд яагаад ийм болдгийг trace тайлбарлана.
Гурван хэрэгсэлд гурван жижиг зүйрлэл хэрэгтэй.
partial бол урьдчилан тохируулсан хэрэгсэл. Гагнуурын аппаратыг төсөөлөөд үзээрэй: хүчдэл, гүйдэл, утасны төрөл гэх мэт олон тохиргоотой. Нэг ажилчин өдөржин ижил төрлийн хоолой гагнадаг бол тэр тохиргоог өглөө нэг удаа хийгээд түгжчихнэ. Дараа нь өдөржин зөвхөн "энэ хоолойг гагна" гэсэн ганц үйлдэл хийнэ. partial функцийн тохиргоог түгжиж, шинэ, нэг товчтой хэрэгсэл гаргаж өгдөг.
reduce бол цаас нугалах. Урт цаасны туузыг төсөөлөөд үзээрэй. Та эхний хоёр хэсгийг нийлүүлж нугална, дараа нь тэр нугалааг гурав дахь хэсэгт нийлүүлнэ, дараа нь дөрөв дэхтэй гээд эцэст нь ганц жижиг боодол үлдэнэ. reduce элементүүдийг яг ингэж нэг нэгээр нь хуримтлуулан нэг утга болгодог.
lru_cache бол багшийн хариултын дэвтэр. Шинэ багш анхны жилдээ сурагч бүрийн асуултад шинээр тооцоолж хариулдаг. Туршлагатай багш ширээн дээрээ дэвтэр тавьдаг: нэг удаа бодсон бодлогын хариултыг тэнд бичдэг. Дараагийн удаа ижил асуулт ирэхэд дахин бодохгүй, дэвтрээ нээгээд хариултыг уншина. Дэвтэр нь хязгаартай бол хамгийн удаан хэрэглэгдээгүй хариултыг баллаж, шинэд зай гаргана. LRU гэдэг нь яг энэ дүрмийн нэр: “Least Recently Used”, "хамгийн сүүлд хэрэглэгдсэнээс хамгийн хол байгаа".
Python-ий int() функц хоёр argument авч чаддаг: хөрвүүлэх string ба тооллын систем. int("101", base=2) нь хоёртын тоо “101”-ийг аравтын 5 болгоно. Хэрэв бид олон хоёртын тоо хөрвүүлэх хэрэгтэй бол base=2-ыг дахин дахин бичих нь уйтгартай.
python
from functools import partial
from_binary = partial(int, base=2)
print(from_binary("101"))
print(from_binary("1111"))
print(from_binary)5
15
functools.partial(<class 'int'>, base=2)partial(int, base=2) нь шинэ дуудагдах объект буцаана. Түүнийг дуудахад int-ийг base=2 бэхэлсэн хэвээр, бидний өгсөн бусад argument-тай хамт дуудна. Тэгэхээр from_binary("101") нь int("101", base=2)-тэй яг ижил. Сүүлийн мөр partial объект юуг санаж байгааг ил харуулж байна: ямар функцийг (int), ямар бэхэлсэн argument-тай (base=2).
partial-ийн бодит хүч өмнөх хоёр хичээлтэй холбогдоход харагдана. Хичээл 3.1-д бид map-д бэлэн нэртэй функц өгөхөд цэвэрхэн, lambda хэрэгтэй үед comprehension илүү гэж дүгнэсэн. Гэтэл функц маань нэртэй ч нэмэлт тохиргоо шаардвал яах вэ?
python
from functools import partial
def apply_discount(price, rate):
return round(price * (1 - rate))
prices = [1000, 2500, 400]
ten_off = partial(apply_discount, rate=0.10)
print(list(map(ten_off, prices)))[900, 2250, 360]apply_discount хоёр argument авдаг тул map-д шууд өгөх боломжгүй. map элемент бүрт нэг л argument өгнө. partial rate=0.10-ийг бэхлээд, нэг argument авдаг шинэ функц ten_off-ийг бүтээсэн. Одоо map-д нэрээр нь өгч болно. Тохиргоо нь хувьсагчийн нэрт тусгагдсан тул код "үнийн жагсаалтад арван хувийн хөнгөлөлт хэрэглэ" гэж уншигдана.
Шударгаар хэлэхэд, ижил үр дүнг lambda-аар ч гаргаж болно: lambda p: apply_discount(p, rate=0.10). partial ба lambda хоёулаа зөв. partial-ийн давуу тал нь хэвлэхэд юу бэхлэгдсэнээ харуулдаг, мөн "энэ функцийн нэг хэсгийг бэхэлсэн" гэсэн санааг тодорхой илэрхийлдэг. Lambda-ийн давуу тал нь илүү уян хатан: argument-уудын дарааллыг өөрчлөх, нэмэлт тооцоолол хийх боломжтой. Хоёуланг нь бусдын кодонд харах тул хоёуланг нь уншиж чаддаг байхад хангалттай.
Жишээ 1: reduce — шударга үнэлгээтэй
reduce хоёр argument авдаг функц ба iterable авна. Эхний хоёр элементэд функцийг хэрэглэж, үр дүнг дараагийн элементтэй хамт дахин функцэд өгнө, гэх мэтээр төгсгөл хүртэл. 1-ээс 5 хүртэлх тоонуудын үржвэрийг бодъё:
python
from functools import reduce
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda acc, x: acc * x, numbers)
print(product)120reduce-ийн доторх нуугдмал төлөв нь хуримтлагч утга. Үүнийг trace хийе. Lambda-ийн эхний argument-ийг acc буюу “accumulator”, "хуримтлагч" гэж нэрлэсэн.
Алхам 1. reduce эхний хоёр элементийг авна: acc = 1, x = 2. Lambda 2-ыг буцаана. Хуримтлагч: 2.
Алхам 2. acc = 2, x = 3. Үр дүн 6. Хуримтлагч: 6.
Алхам 3. acc = 6, x = 4. Үр дүн 24. Хуримтлагч: 24.
Алхам 4. acc = 24, x = 5. Үр дүн 120. Хуримтлагч: 120. Элемент дууссан тул reduce 120-ыг буцаана.
Цаас нугалах зүйрлэл яг энэ: нугалаа бүр өмнөх боодлыг дараагийн хэсэгтэй нийлүүлдэг. Гэхдээ одоо шударга асуулт тавья: энэ кодыг уншихад хялбар байна уу? Python-д үүнийг хийдэг бэлэн функц бий:
python
import math
print(math.prod(numbers))120math.prod нэрээрээ "үржвэр" гэж хэлж байна, lambda, хуримтлагч, trace шаардлагагүй. reduce-ийн сонгодог жишээнүүд бараг бүгдэд Python бэлэн, нэртэй хариулттай: нийлбэрт sum(), хамгийн их нь max(), хамгийн бага нь min(), үржвэрт math.prod(), string нийлүүлэхэд “”.join(), бүгд үнэн эсэхэд all(), аль нэг нь үнэн эсэхэд any(). Энэ шалтгаанаар Python 3 reduce-ийг суурь функцээс functools модуль руу шилжүүлсэн. Python нийгэмлэг reduce-ийг "сүүлчийн сонголт" гэж үздэг.
Тэгвэл reduce хэзээ хэрэгтэй вэ? Хуримтлуулах үйлдэлд бэлэн нэртэй функц байхгүй, мөн энгийн for loop-оос илүү товч, тодорхой байх үед. Нэг жишээ бол хэд хэдэн тохиргооны dict-ийг дарааллаар нь нэгтгэх, сүүлийнх нь давуу эрхтэй:
python
from functools import reduce
defaults = {"theme": "light", "lang": "mn", "page_size": 20}
org_settings = {"page_size": 50}
user_settings = {"theme": "dark"}
config = reduce(lambda a, b: a | b, [defaults, org_settings, user_settings], {})
print(config){'theme': 'dark', 'lang': 'mn', 'page_size': 50}Python 3.9-өөс хойш a | b нь хоёр dict-ийг нэгтгэж, давхардсан түлхүүрт b-ийн утгыг авдаг. reduce гурван dict-ийг дарааллаар нь нэгтгэсэн: хэрэглэгчийн тохиргоо байгууллагынхыг, тэр нь анхдагчийг дарав. Сүүлийн argument {} бол анхны утга: хуримтлагч эхний элементээс биш, хоосон dict-ээс эхэлнэ. Анхны утга нь жагсаалт хоосон байсан ч reduce алдаа өгөхгүй байхыг баталгаажуулна. Энэ ч гэсэн гурван мөрт for loop-оор бичихэд адил тодорхой гэдгийг шударгаар хэлье. reduce-ийг унших чадвартай бай, бичихээсээ өмнө бэлэн хувилбар бий эсэхийг шалга.
Жишээ 2: lru_cache — асуудал
Одоо хичээлийн од. Эхлээд түүний шийддэг асуудлыг харъя. Fibonacci дараалал 0, 1-ээр эхэлж, дараагийн тоо бүр өмнөх хоёрын нийлбэр: 0, 1, 1, 2, 3, 5, 8, 13, ... Үүнийг шууд рекурсив функцээр бичих нь маш тод:
python
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(10))55Энэ функц математикийн тодорхойлолтыг үгчлэн давтаж байна. Гэвч n-ийг томруулахад гайхмаар удааширдаг. Яагаад гэдгийг харахын тулд дуудлагын тоог тоолъё:
python
calls = 0
def fib_counted(n):
global calls
calls += 1
if n < 2:
return n
return fib_counted(n - 1) + fib_counted(n - 2)
for n in [10, 20, 30]:
calls = 0
result = fib_counted(n)
print(f"fib({n}) = {result}, дуудлага: {calls:,}")fib(10) = 55, дуудлага: 177
fib(20) = 6765, дуудлага: 21,891
fib(30) = 832040, дуудлага: 2,692,537global гэдэг түлхүүр үг функц доторх calls нь гадна талын calls хувьсагч гэдгийг Python-д хэлж байна. Энэ түлхүүр үгийн оронд илүү цэвэр шийдлийг Бүлэг 4-т closure-ийг судлахдаа сурна. Одоо тоонуудыг хараарай. n 10-аар өсөх бүрт дуудлагын тоо зуу гаруй дахин өсч байна. fib(30)-ийг бодохын тулд функц бараг гурван сая удаа дуудагдсан. Гэтэл fib(30)-д хэрэгтэй өөр өөр утга ердөө 31 л байгаа: fib(0)-оос fib(30) хүртэл. Үлдсэн сая сая дуудлага бүгд ижил зүйлийг дахин дахин бодсон.
Шалтгааныг жижиг n дээр харъя. fib(5) нь fib(4) ба fib(3)-ыг дуудна. fib(4) нь fib(3) ба fib(2)-ыг дуудна. Аль хэдийн fib(3) хоёр удаа гарч ирлээ, тус бүр бүхэл модоороо бодогдоно. Доод түвшин рүү орох тусам давхардал улам ихэснэ: fib(5)-д fib(2) гурван удаа, fib(1) таван удаа бодогдоно. Энэ бол хариултын дэвтэргүй шинэ багш: ижил бодлогыг асуух болгонд эхнээс нь бодно.
Жишээ 3: lru_cache — шийдэл ба хэмжилт
Одоо функцийн дээр нэг мөр нэмье:
python
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)@lru_cache(maxsize=None) бол decorator. Course 2-т та @ тэмдэгтэй танилцсан бөгөөд Бүлэг 5-д түүнийг дотроос нь бүрэн ойлгох болно. Одоохондоо ийм ойлголт хангалттай: decorator нь функцийг "боож", түүний дуудлага бүрийн өмнө ба дараа нэмэлт ажил хийдэг. lru_cache-ийн нэмэлт ажил бол дэвтэр хөтлөх. Дуудлага ирэхэд argument-ийг дэвтэрт хайна. Олдвол хадгалсан хариуг шууд буцаана, функцийн бие огт ажиллахгүй. Олдохгүй бол функцийг жинхэнээр ажиллуулж, үр дүнг дэвтэрт бичээд буцаана. maxsize=None нь дэвтэр хязгааргүй гэсэн утгатай. Python 3.9-өөс хойш үүнтэй ижил @cache гэсэн богино хэлбэр бий.
Одоо хэмжилт. Хоёр функцийг зэрэгцүүлж, нэг дуудлагын хугацааг time.perf_counter()-аар хэмжинэ. perf_counter бол богино хугацааг хэмжихэд зориулагдсан, маш нарийн секундомер. Эхлэхийн өмнө ба дууссаны дараа дуудаж, хоёрын зөрүүг авна.
python
import time
from functools import lru_cache
def fib_slow(n):
if n < 2:
return n
return fib_slow(n - 1) + fib_slow(n - 2)
@lru_cache(maxsize=None)
def fib_fast(n):
if n < 2:
return n
return fib_fast(n - 1) + fib_fast(n - 2)
for func in (fib_slow, fib_fast):
start = time.perf_counter()
result = func(30)
elapsed = time.perf_counter() - start
print(f"{func.__name__}(30) = {result}, {elapsed:.6f} секунд")
print(fib_fast.cache_info())Ажиллуулах команд:
python fib_measure.pyМиний компьютер дээрх нэг ажиллуулалт:
fib_slow(30) = 832040, 0.187432 секунд
fib_fast(30) = 832040, 0.000021 секунд
CacheInfo(hits=28, misses=31, maxsize=None, currsize=31)Хоёр функц ижил хариу өгсөн. Кэшгүй хувилбар 0.19 секунд орчим зарцуулсан, кэштэй хувилбар 0.00002 секунд буюу 21 микросекунд. Ойролцоогоор найман мянга дахин хурдан. Таны компьютер дээр тоонууд өөр гарах нь хэвийн, ялангуяа кэштэй хувилбарын микросекундүүд маш тогтворгүй байдаг. Гэвч зэрэглэлийн ялгаа хаана ч ижил. Хэрэв n-ийг 35 болговол кэшгүй хувилбар хэдэн секунд зарцуулах бол кэштэй хувилбар микросекундэд хэвээр үлдэнэ. fib_fast(100) нэн даруй 354224848179261915075 гэж хариулна. Харин fib_slow(100) таны насан туршид дуусахгүй.
Сүүлийн мөр cache_info() бол lru_cache-ийн өөрийн тайлан. Түүнийг уншъя: misses=31 нь "дэвтэрт олдоогүй, жинхэнээр тооцоолсон" дуудлага 31 удаа, яг fib(0)-оос fib(30) хүртэлх 31 өөр утга. hits=28 нь "дэвтрээс шууд уншсан" дуудлага 28 удаа. currsize=31 нь дэвтэрт одоо 31 хариулт хадгалагдаж байгааг хэлнэ. Нийт 59 дуудлага, кэшгүй хувилбарын 2,692,537-той харьцуулаарай. Хурд эндээс ирж байна: функц хамаагүй хурдан ажилласан биш, хамаагүй цөөн удаа ажилласан.
Trace: дэвтэр хэрхэн дүүрдэг вэ
Кэш нүдэнд үл харагдах төлөв тул fib_fast(4)-ийн дуудлагуудыг дэвтрийн төлөвтэй хамт мөрдье. Шинэ, хоосон кэштэй эхэлнэ гэж үзнэ.
Алхам 1 (fib(4) дуудагдах). Дэвтэрт 4 байхгүй, miss. Функцийн бие ажиллаж, fib(3)-ыг дуудна. Дэвтэр: хоосон.
Алхам 2 (fib(3)). Дэвтэрт байхгүй, miss. fib(2)-ыг дуудна. Дэвтэр: хоосон.
Алхам 3 (fib(2)). Miss. fib(1)-ийг дуудна. Дэвтэр: хоосон.
Алхам 4 (fib(1)). Miss. n < 2 тул 1 буцаана. Дэвтэр: {1: 1}.
Алхам 5 (fib(2) fib(0)-ийг дуудах). Miss. 0 буцаана. Дэвтэр: {1: 1, 0: 0}. fib(2) нь 1 + 0 = 1 болж буцна. Дэвтэр: {1: 1, 0: 0, 2: 1}.
Алхам 6 (fib(3) fib(1)-ийг дуудах). Дэвтэрт 1 бий, hit. Функцийн бие ажиллахгүй, 1 шууд буцна. fib(3) = 1 + 1 = 2. Дэвтэр: {1: 1, 0: 0, 2: 1, 3: 2}.
Алхам 7 (fib(4) fib(2)-ыг дуудах). Дэвтэрт 2 бий, hit, 1 шууд буцна. fib(4) = 2 + 1 = 3. Дэвтэр: {1: 1, 0: 0, 2: 1, 3: 2, 4: 3}.
Нийт: 5 miss, 2 hit. Кэшгүйгээр fib(4) 9 удаа дуудагдах байсан бөгөөд fib(2) хоёр удаа бүтэн модоороо бодогдох байсан. Алхам 6 ба 7-д тэр давтагдсан мод бүхэлдээ ганц дэвтрийн хайлтаар солигдсон. n томрох тусам эдгээр хайчлагдсан мод экспоненциал хурдаар томордог тул хэмнэлт ч мөн экспоненциал. Алхам 7-ийн дараа дэвтэр хэвээр үлдэнэ: дараа нь fib(4)-ийг дахин дуудвал шууд hit болж, тооцоолол огт хийгдэхгүй.
lru_cache-ийг хэзээ хэрэглэх вэ
Fibonacci бол сурах бичгийн жишээ. Бодит амьдралд lru_cache гурван нөхцөл зэрэг биелэх үед хамгийн их ашиг өгнө. Нэгдүгээрт, функц удаан: хүнд тооцоолол, том файл задлах, тохиргоо унших. Хоёрдугаарт, ижил argument-аар олон удаа дуудагддаг. Гуравдугаарт, ижил argument үргэлж ижил хариу өгдөг. Сүүлийн нөхцөл хамгийн чухал. Функцийн үр дүн цаг хугацаа, санамсаргүй тоо, өгөгдлийн сангийн одоогийн төлөв, гадны API-аас хамаардаг бол кэш хуучирсан, буруу хариу өгнө. Үүнийг Түгээмэл алдаанууд хэсэгт дэлгэрэнгүй харна.
Хэрэв кэш хязгааргүй өсөх нь санаа зовоосон бол maxsize-д тоо өгнө. @lru_cache(maxsize=256) нь хамгийн ихдээ 256 хариулт хадгалж, дэвтэр дүүрэхэд хамгийн удаан хэрэглэгдээгүйг хаяна. Энэ нь хэрэглэгчийн ID, хайлтын үг зэрэг маш олон өөр argument ирж болох функцэд тохиромжтой. Кэшийг цэвэрлэх шаардлагатай бол fib.cache_clear() дуудна.
Хүсвэл хэмжилтийн скриптийн n-ийг 25, 30, 32, 35 гэж өөрчилж, fib_slow-ийн хугацаа хэрхэн өсч байгааг тэмдэглэж болно. n нэгээр өсөх бүрт хугацаа ойролцоогоор хэд дахин нэмэгдэж байна вэ? Сонирхвол fib_fast-ийг хоёр удаа дараалан дуудаж, хоёр дахь дуудлагын хугацааг хэмжиж үзээрэй. Дэвтэр аль хэдийн дүүрсэн тул тэр нь юу болох вэ? Мөн @lru_cache(maxsize=2) гэж маш жижиг дэвтэртэйгээр fib(30)-ийг ажиллуулж, cache_info()-д юу харагдахыг харах нь LRU дүрмийн нөлөөг ойлгох сонирхолтой туршилт. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
TypeError: unhashable type: ‘list’. “list төрөл hash хийгдэх боломжгүй” гэсэн утгатай. lru_cache-тай функцэд list, dict, set зэрэг өөрчлөгдөх боломжтой утгыг argument болгон өгөхөд гарна. Дэвтэр нь дотроо dict ашигладаг бөгөөд dict-ийн түлхүүр өөрчлөгдөхгүй, hash хийгдэх боломжтой байх ёстой. Өөрчлөгдөх list-ийг түлхүүр болговол дараа нь өөрчлөгдөхөд хариулт буруу түлхүүрт бичигдсэн болно. Шийдэл нь list-ийн оронд tuple өгөх: total(tuple(prices)). Хэрэв функцийн гол зорилго нь list хүлээн авах бол тэр функц кэшлэхэд тохиромжгүй байж магадгүй.
Хуучирсан хариу буцаах. Энэ бол мессежгүй, хамгийн аюултай алдаа. Үр дүн нь гадны төлөвөөс хамаардаг функцийг кэшлэвэл гарна:
python
@lru_cache(maxsize=None)
def get_exchange_rate(currency):
return fetch_rate_from_api(currency)Эхний дуудлага ханшийг API-аас авч дэвтэрт бичнэ. Дараагийн өдөр ханш өөрчлөгдсөн ч функц өчигдрийн ханшийг дэвтрээс уншиж буцаана, Python ямар ч анхааруулга өгөхгүй. Ижил асуудал одоогийн цаг, санамсаргүй тоо, өгөгдлийн сангаас уншдаг функцүүдэд гарна. Дүрэм: lru_cache-ийг зөвхөн ижил оролтод үргэлж ижил гаралт өгдөг функцэд хэрэглэ.
RecursionError: maximum recursion depth exceeded. "Рекурсийн дээд гүнээс хэтэрлээ" гэсэн утгатай. Python функц өөрийгөө дуудах гүнийг анхдагчаар мянга орчмоор хязгаарладаг. fib_fast(2000) гэж хоосон кэштэй дуудвал функц fib(1999), fib(1998), ... гэж мянга гаруй түвшин гүнзгийрэх тул энэ хязгаарт хүрнэ. Энэ нь кэшийн алдаа биш, рекурсийн хязгаар. Шийдлүүдийн нэг нь кэшийг аажмаар дүүргэх: for i in range(2001): fib_fast(i) гэж жижгээс нь эхлэн дуудвал дуудлага бүр дэвтрээс өмнөх хоёрыг олох тул гүн хэзээ ч хоёроос хэтрэхгүй. Илүү энгийн шийдэл нь рекурсгүй, for loop-той хувилбар бичих.
TypeError: reduce() of empty iterable with no initial value. "Анхны утгагүйгээр хоосон iterable дээр reduce хийлээ" гэсэн утгатай. reduce эхний хоёр элементээс эхэлдэг тул хоосон жагсаалтад эхлэх зүйлгүй. Шийдэл нь гурав дахь argument болгон анхны утга өгөх: үржвэрт 1, нийлбэрт 0, dict нэгтгэхэд {}. Анхны утга нь мөн хуримтлагчийн төрлийг тодорхой болгодог тул reduce хэрэглэх бүрдээ өгөх нь сайн дадал.
TypeError: apply_discount() got multiple values for argument ‘rate’. “apply_discount()-ийн ‘rate’ argument-д олон утга ирлээ” гэсэн утгатай. partial-аар бэхэлсэн argument-ийг дуудлага дээр дахин байрлалаар өгөхөд гарна: ten_off(1000, 0.2). Энд 0.2 байрлалаараа rate-д очих гэж оролдсон ч rate аль хэдийн 0.10-оор бэхлэгдсэн. Түлхүүр үгээр бол дарж болдог: ten_off(1000, rate=0.2) ажиллаж, 0.2-ыг хэрэглэнэ. Гэхдээ бэхэлсэн утгыг дарах шаардлага гарвал тэр partial өөрөө хэрэггүй байж магадгүй.
functools бол функцүүдийг боловсруулах стандарт сан юм. partial функцийн зарим argument-ийг бэхэлж шинэ функц бүтээдэг бөгөөд map, key зэрэг нэг argument хүлээдэг газарт олон argument-тай функцийг lambda-гүйгээр өгөхөд тустай. reduce элементүүдийг хуримтлагчаар дамжуулан нэг утга болгон эвхдэг. Гэхдээ sum, max, math.prod, join, all, any зэрэг бэлэн функцүүд түүний ихэнх ажлыг илүү тодорхой хийдэг тул reduce-ийг сүүлчийн сонголт гэж үз, хэрэглэх бол анхны утга өг. lru_cache функцийн үр дүнг argument-аар нь санаж, давтан дуудлагыг тооцоололгүйгээр хариулдаг. Рекурсив Fibonacci дээр fib(30) кэшгүйгээр 2.7 сая дуудлага, 0.19 секунд, кэштэй 59 дуудлага, 21 микросекунд гэж хэмжигдсэн. Хурд функц хурдан ажилласнаас биш, цөөн ажилласнаас ирсэн. lru_cache-ийг зөвхөн ижил оролтод ижил гаралт өгдөг, hash хийгдэх argument-тай функцэд хэрэглэнэ.
Ингээд Түвшин 1-ийн бүх хэрэгсэл бидний гарт байна: iterator протокол, generator ба pipeline, itertools, map, filter, key=, functools. Дараагийн хичээл бол Түвшин 1-ийн хамтарсан бүтээл. Бид их хэмжээний log файл үүсгэж, түүнийг санах ой дүүргэлгүйгээр боловсруулдаг бүрэн хэрэгслийг алхам алхмаар бүтээнэ: generator pipeline-аар задалж шүүх, itertools-оор таслах, key=-ээр эрэмбэлж дүгнэх. Ажлыг Git branch дээр хийж, Course 4-ийн дадлаар жижиг pytest тест нэмэх болно. Энэ түвшинд сурсан бүх зүйл нэг бодит програмд нэгдэнэ.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.