Ачааллж байна...
Өмнөх хоёр хичээлд бид closure-ийн механизмыг дотроос нь ойлгосон. Хичээл 4.1-д үүргэвч ба түүний хайрцгуудыг нээж харсан. Хичээл 4.2-т nonlocal-оор хайрцгийн агуулгыг өөрчилж сурсан. Тэр хоёр хичээлийн жишээнүүд ихэвчлэн жижиг, сургалтын зорилготой байсан: мэндчилгээ, үржүүлэгч, тоолуур. Энэ хичээлийн асуулт өөр: бодит кодонд closure хаана, яагаад хэрэглэгддэг вэ? Механизмыг ойлгох нь нэг хэрэг, түүнийг хэзээ хэрэглэхээ мэдэх нь өөр хэрэг.
Closure өдөр тутмын Python кодонд гурван түгээмэл загвараар хамгийн их гарч ирдэг. Эхнийх нь тохируулсан функц: нэг удаа тохиргоо өгч, тэр тохиргоог дахин дахин хэрэглэдэг функц үүсгэх. Хоёр дахь нь хуримтлуулагч: дуудлага бүрт өгөгдөл хүлээн авч, хуримтлагдсан дүнг нууцаар хадгалдаг функц. Гурав дахь нь memo буюу хариултын дэвтэр: функцийн үр дүнг санаж, давтан тооцоолохоос сэргийлэх. Гурван загвар тус бүрийг бүрэн, бодит хэрэглээний хамт судална.
Гурав дахь загвар онцгой ач холбогдолтой. Хичээл 3.3-т бид lru_cache-ийг бэлэн хэрэгсэл болгон ашигласан. Энэ хичээлд түүний энгийн хувилбарыг closure-оор өөрсдөө бүтээнэ. Тэгэх явцдаа нэг гайхалтай нээлт хийнэ: @ тэмдэг ердөө л нэг мөр closure кодын товчлол юм. Энэ нээлт Бүлэг 5-ийн decorator-ын бүх ойлголтын суурь болно.
Гурван загварт гурван мэргэжилтнийг төсөөлөөд үзээрэй.
Эхнийх нь тохируулсан хэрэгсэлтэй гар урчууд. Мод засварын газарт олон хөрөө бий, тус бүр өөр зузаанд тохируулагдсан. Мастер хөрөөг өглөө нэг удаа тохируулаад, өдөржин түүнийг тохиргоогүйгээр хэрэглэнэ. Өөр зузаан хэрэгтэй бол өөр хөрөө авна. Closure-оор үүсгэсэн тохируулсан функц бол тэр хөрөө: тохиргоо нь үүргэвчинд, хэрэглэгч зөвхөн материалаа өгнө.
Хоёр дахь нь ээлжийн нягтлан. Тэр өдөржин орж ирэх баримт бүрийг хүлээн авч, нийт дүн, хамгийн их, хамгийн бага дүнг дэвтэртээ хөтөлнө. Хүн бүр баримтаа өгөөд явна, хэн ч өмнөх баримтуудыг дахин өгөх шаардлагагүй. Нягтлангийн дэвтэр бол closure-ийн хайрцаг.
Гурав дахь нь Хичээл 3.3-ын хариултын дэвтэртэй багш. Тэнд бид энэ багшийг бэлнээр хөлсөлсөн. Энэ хичээлд бид түүнийг өөрсдөө сургана. Тэгээд багшийг ажилд авахын тулд нэг жижиг заль хэрэгтэйг олж мэдэнэ: хуучин багшийн нэрийн хавтанг шинэ багшийнхаар солих.
Мөнгөн дүнг харуулах функц хэрэгтэй гэж бодъё. Төгрөгийг мянгатын таслалтай, бутархайгүй, ард нь ₮ тэмдэгтэй. Долларыг урд нь $ тэмдэгтэй, хоёр бутархай оронтой. Еврог ч мөн адил, өөр тэмдэгтэй. Нэг арга нь бүх тохиргоог argument болгон авдаг ганц функц бичих:
python
def format_money(amount, symbol, decimals, symbol_first):
number = f"{amount:,.{decimals}f}"
return f"{symbol}{number}" if symbol_first else f"{number}{symbol}"
print(format_money(12500, "₮", 0, False))
print(format_money(12500, "₮", 0, False))
print(format_money(19.5, "$", 2, True))12,500₮
12,500₮
$19.50Ажиллаж байна, гэвч дуудлага бүрт дөрвөн argument бичих хэрэгтэй, гурав нь үргэлж ижил. Хэрэв програмын тавин газарт төгрөг хэвлэдэг бол тавин удаа "₮", 0, False гэж бичнэ. Нэг газар 1 гэж алдаа гаргахад тэр газарт л буруу формат гарна. Тохиргоог нэг удаа өгч, түүнийг хадгалдаг функц бүтээе:
python
def make_money_formatter(symbol, decimals=0, symbol_first=False):
def fmt(amount):
number = f"{amount:,.{decimals}f}"
return f"{symbol}{number}" if symbol_first else f"{number}{symbol}"
return fmt
mnt = make_money_formatter("₮")
usd = make_money_formatter("$", decimals=2, symbol_first=True)
print(mnt(12500))
print(mnt(1250000))
print(usd(19.5))
print(mnt.__code__.co_freevars)12,500₮
1,250,000₮
$19.50
('decimals', 'symbol', 'symbol_first')make_money_formatter нь гурван тохиргоо авч, fmt функцийг үйлдвэрлэнэ. fmt өөрөө нэг л argument авна: дүн. Гурван тохиргоо түүний үүргэвчинд байгааг сүүлийн мөр баталж байна. f"{amount:,.{decimals}f}" нь Course 1-2-оос танил формат: таслал мянгатыг тусгаарлана, .{decimals}f нь decimals оронтой бутархай гаргана. decimals өөрөө f-string дотор байрлаж байгаа нь closure-ийн хувьсагчийг формат тохиргоонд шууд хэрэглэж болдгийг харуулна.
Одоо ашиг тус нь. Програм бүхэлдээ mnt(...)-ийг хэрэглэнэ, тохиргоо нэг газарт. Хэрэв захиалагч "төгрөгийн тэмдгийн өмнө зай тавь" гэвэл нэг газарт засна. Хичээл 3.1-ийн дүрэм энд мөн ашигтай: mnt бол бэлэн нэртэй, нэг argument-тай функц, тиймээс map-д шууд өгч болно.
python
prices = [12500, 3400, 980000]
print(list(map(mnt, prices)))['12,500₮', '3,400₮', '980,000₮']Хичээл 3.3-ын partial-тай харьцуулбал: хэрэв format_money аль хэдийн байгаа бол partial(format_money, symbol="₮", decimals=0, symbol_first=False) ижил үр дүн өгнө. Сонголтын шударга дүрэм: бэлэн функцийн argument-ыг л бэхлэх бол partial хамгийн товч. Тохиргоо дээр тулгуурласан шинэ логик бичих, эсвэл тохиргоог бэлтгэх нэмэлт ажил хийх бол closure илүү уян хатан. Жишээлбэл, closure тохиргоог нэг удаа шалгаж, буруу бол factory дуудагдах мөчид алдаа өгөх боломжтой: decimals сөрөг байвал fmt үүсэхээс өмнө ValueError шидэж болно. partial ийм шалгалт хийж чадахгүй.
Хичээл 4.2-т бид дундажлагч бичсэн. Одоо түүнийг бодит хэрэгсэл болгон өргөтгөе: тоонуудын урсгалыг нэг нэгээр хүлээн авч, тоо, нийлбэр, хамгийн бага, хамгийн их, дундажийг хадгалдаг статистик хөтлөгч.
python
def make_tracker():
count = 0
total = 0
smallest = None
largest = None
def add(value):
nonlocal count, total, smallest, largest
count += 1
total += value
if smallest is None or value < smallest:
smallest = value
if largest is None or value > largest:
largest = value
return {
"count": count,
"average": total / count,
"min": smallest,
"max": largest,
}
return add
track = make_tracker()
track(120)
track(45)
print(track(300)){'count': 3, 'average': 155.0, 'min': 45, 'max': 300}add бүх дөрвөн хувьсагчид оноодог тул дөрвүүлээ nonlocal-д зарлагдсан. smallest ба largest эхэндээ None: "хараахан утга алга" гэсэн дохио. Эхний утга ирэхэд smallest is None нөхцөл үнэн тул тэр утга шууд хадгалагдана. Дараагийн утгууд жинхэнэ харьцуулалтаар шалгагдана. Дуудлага бүр одоогийн статистикийг dict болгон буцаана.
Энэ загварын жинхэнэ хүч Түвшин 1-тэй холбогдоход харагдана. Хичээл 3.4-ийн log боловсруулагчийг санаарай: сая бичлэг урсгалаар дамжсан, санах ойд нэг удаад нэг л бичлэг. Хэрэв бид хүсэлтийн хугацааны статистик хэрэгтэй бол бүх хугацааг list-д цуглуулах нь урсгалын бүх ашгийг устгана. Хуримтлуулагч энэ асуудлыг шийднэ:
python
from log_tools import build_records
track = make_tracker()
stats = None
for r in build_records(["app.log"]):
stats = track(r["duration_ms"])
print(stats){'count': 1000000, 'average': 124.6, 'min': 5, 'max': 1874}Таны тоонууд өөр байж болно, дундаж 125 орчим байх ёстой. Сая хугацаа нэг нэгээр нь track-д орж, тэр нь дөрвөн тоог л хадгалсан. Санах ойд сая тоо хэзээ ч зэрэг байгаагүй. Энэ бол урсгал боловсруулалтын сонгодог хос: generator өгөгдлийг нэг нэгээр нь гаргадаг, closure хуримтлуулагч нэг нэгээр нь хүлээн авч, хураангуйг хадгалдаг. Хоёулаа санах ойг тогтмол байлгадаг.
Хичээл 4.2-ын төгсгөлд хэлсэн шударга дүрэм энд ч хамаарна. Хэрэв хуримтлуулагчаас "статистикийг тэглэх", "зөвхөн дунджийг авах", "өөр хуримтлуулагчтай нэгтгэх" зэрэг олон үйлдэл хэрэгтэй болбол class бичих цаг болсон гэсэн үг. Нэг үйлдэлтэй, нэг зорилготой хуримтлуулагчид closure хамгийн хөнгөн.
Одоо хичээлийн хамгийн чухал загвар. Бид өөрсдийн lru_cache-ийг бүтээнэ. Санаа энгийн: ямар ч функц авч, түүний үр дүнг dict-д хадгалдаг шинэ функц буцаах. Энэ шинэ функц нь хуучны "орлогч": гаднаас ижил харагдана, гэхдээ дотроо эхлээд дэвтэр шалгана.
python
def make_memo(func):
cache = {}
def memoized(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return memoizedМөр бүрийг уншъя. make_memo argument болгон функц авна. Энэ бол Хичээл 3.1-ээс хойшхи "функц бол утга" санааны шинэ хэрэглээ: өмнө нь бид функцийг map, sorted-д өгдөг байсан бол одоо өөрсдийн функцэд өгч байна. cache хоосон dict, бидний дэвтэр. memoized бол орлогч функц. *args нь Course 2-оос танил: хэдэн ч байрлалын argument хүлээн авч, тэдгээрийг tuple болгон args-д хийнэ. Тэгэхээр memoized(5) дуудагдвал args = (5,), memoized(2, 3) бол args = (2, 3). Tuple нь hash хийгдэх боломжтой тул dict-ийн түлхүүр болж чадна.
memoized-ийн бие гурван мөр. Хэрэв энэ argument-ийн хослол дэвтэрт байхгүй бол жинхэнэ функцийг func(*args) гэж дуудаж, үр дүнг дэвтэрт бичнэ. Энд *args эсрэг үүрэгтэй: tuple-ийг задалж, тус тусдаа argument болгон дамжуулна. Эцэст нь дэвтрээс хариултыг буцаана.
memoized-ийн үүргэвчинд юу байгааг бодъё: func ба cache. Хоёулаа make_memo-ийн гэрээс. cache-д бид cache[args] = ... гэж бичиж байна, гэвч nonlocal байхгүй. Яагаад? Хичээл 4.2-ын дүрэм: энэ нь cache нэрэнд шинэ утга оноох биш, cache-ийн заасан dict-ийн дотрыг өөрчилж байна. Дэвтэрт мөр бичих, дэвтрийг солих биш.
Хэмжилт: удаан функц дээр
Хурдны тухай мэдэгдэл хэмжилт шаарддаг. Эхлээд санаатайгаар удаан функц бичье. time.sleep() нь програмыг өгсөн секундээр зогсооно. Энэ нь өгөгдлийн сан эсвэл сүлжээнээс хариу хүлээж буй функцийг дуурайлгана.
python
import time
def slow_square(n):
time.sleep(0.5)
return n * n
fast_square = make_memo(slow_square)
for attempt in range(3):
start = time.perf_counter()
result = fast_square(12)
elapsed = time.perf_counter() - start
print(f"{attempt + 1}-р удаа: {result}, {elapsed:.6f} секунд")Миний компьютер дээрх нэг ажиллуулалт:
1-р удаа: 144, 0.500612 секунд
2-р удаа: 144, 0.000002 секунд
3-р удаа: 144, 0.000001 секундЭхний дуудлага хагас секунд үргэлжилсэн, учир нь дэвтэр хоосон байсан тул жинхэнэ slow_square ажиллаж, унтсан. Хоёр, гурав дахь дуудлага микросекунд хүрэхгүй хугацаанд дууссан: дэвтэрт (12,) түлхүүр байсан тул slow_square огт дуудагдаагүй. Таны тоонууд, ялангуяа микросекундүүд өөр байх ч ялгааны зэрэглэл ижил байна. Хэрэв slow_square-ийн оронд өгөгдлийн сангийн асуулга байсан бол хоёр дахь дуудлага өгөгдлийн санд огт хүрэхгүй.
Нээлт: рекурс ба нэрийн хавтан
Одоо ижил memo-г Хичээл 3.3-ын Fibonacci дээр туршъя. Тэнд lru_cache fib(30)-ийг 0.19 секундээс 21 микросекунд болгосон.
python
import time
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
fast_fib = make_memo(fib)
start = time.perf_counter()
fast_fib(30)
print(f"fast_fib(30): {time.perf_counter() - start:.4f} секунд")fast_fib(30): 0.1912 секундХурдсаагүй. Кэшгүй хувилбартай бараг ижил. Юу болсон бэ? Trace хийе.
Алхам 1. fast_fib = make_memo(fib). fast_fib нь memoized функц, үүргэвчинд нь func = анхны fib, cache = {}. Global түвшинд хоёр нэр бий: fib нь анхны функц, fast_fib нь орлогч.
Алхам 2. fast_fib(30). memoized дэвтрээ шалгана: (30,) байхгүй. func(30)-ийг дуудна, тэр нь анхны fib.
Алхам 3. Анхны fib(30)-ийн бие ажиллаж, fib(n - 1) + fib(n - 2) мөрөнд хүрнэ. Энд “fib” гэдэг нэрийг LEGB дагуу хайна: Local-д байхгүй, Enclosing байхгүй, Global-д олдоно: анхны fib. Тэгэхээр fib(29) ба fib(28) нь memoized-ийг огт дайралгүй анхны fib руу шууд очно.
Алхам 4. Үлдсэн 2.7 сая дуудлага бүгд анхны fib-ээр, дэвтэргүйгээр явна. Эцэст нь хариу memoized руу буцаж, дэвтэрт ганцхан мөр бичигдэнэ: (30,).
Асуудал нь рекурсив дуудлага fib нэрээр явдагт оршино. Орлогч зөвхөн fast_fib нэрээр дуудагдах үед л ажилладаг, гэтэл fib өөрийгөө анхны нэрээр нь дууддаг. Шийдэл нь гайхмаар энгийн: орлогчийг fib нэрэнд оноох. Нэрийн хавтанг солих.
python
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
fib = make_memo(fib)
start = time.perf_counter()
print(fib(30))
print(f"fib(30): {time.perf_counter() - start:.6f} секунд")832040
fib(30): 0.000024 секундОдоо 24 микросекунд, lru_cache-тай бараг ижил. Trace дахин:
Алхам 1. def fib анхны функцийг үүсгэж, fib нэрэнд онооно.
Алхам 2. fib = make_memo(fib). Баруун тал эхэлж бодогдоно: make_memo анхны функцийг авч, үүргэвчиндээ func болгон хийж, memoized-ийг буцаана. Дараа нь зүүн тал: fib нэр одоо memoized-ийг заана. Анхны функц устаагүй, тэр memoized-ийн үүргэвчинд func нэрээр амьдарч байна. Гэвч global fib нэр түүнийг заахаа больсон.
Алхам 3. fib(30). memoized дуудагдана, дэвтэрт (30,) байхгүй, func(30)-ийг дуудна.
Алхам 4. Анхны функцийн бие fib(n - 1)-д хүрэхэд “fib” нэрийг Global-аас хайна, тэр одоо memoized-ийг заана. Тэгэхээр рекурсив дуудлага бүр дэвтрээр дамжина. fib(28) хоёр дахь удаагаа хэрэгтэй болоход дэвтэрт аль хэдийн бий.
Нэг мөр, fib = make_memo(fib), бүх ялгааг гаргалаа. Одоо энэ мөрийг анхааралтай хараарай. Функц тодорхойлоод, шууд түүнийг өөр функцэд өгч, үр дүнг ижил нэрэнд буцааж оноох. Python энэ загварт тусгай товчлол өгдөг:
python
@make_memo
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)Энэ нь яг ижил код. @make_memo гэж def-ийн дээр бичих нь Python-д "энэ функцийг тодорхойлсны дараа make_memo-д өгөөд, үр дүнг ижил нэрэнд оноо" гэж хэлж байна. Хичээл 3.3-т бидний бичсэн @lru_cache(maxsize=None) ч мөн энэ зарчмаар ажилласан. Decorator бол ид шид биш: энэ нь функц хүлээн авч, closure-тэй орлогч функц буцаадаг энгийн функц, мөн нэрийн хавтанг солих нэг мөрийн товчлол. Энэ бүлгийн гурван хичээлийн бүх мэдлэг үүргэвч, хайрцаг, хувь хайрцгийн дотрыг өөрчлөх энэ нэг @ тэмдэгт нуугдаж байна.
Гурван загварыг нэгтгэвэл closure хэзээ зөв сонголт болохыг харж болно. Тохиргоог нэг удаа өгч, олон удаа хэрэглэх бол тохируулсан функц. Бэлэн функцийн argument-ыг л бэхлэх бол partial илүү товч. Дуудлага хооронд жижиг төлөв хадгалах бол хуримтлуулагч. Олон үйлдэлтэй төлөв бол class. Функцийн зан чанарыг өөрчлөлгүйгээр түүнд нэмэлт ажил нэмэх бол, тухайлбал кэш, хэмжилт, бүртгэл, орлогч функц, өөрөөр хэлбэл decorator. Хоёр дахь, гурав дахь загвар хоёулаа дуудлага хооронд амьдардаг хайрцагт тулгуурладаг. Энэ бол closure-ийн хувьд ердийн функц хийж чаддаггүй цорын ганц зүйл.
Хүсвэл make_money_formatter-д тохиргоо шалгалт нэмж, decimals сөрөг байвал factory дуудагдах мөчид ValueError шиддэг болгож болно. Fmt үүсэхээс өмнө алдаа гарч байгааг шалгаарай. Сонирхвол make_memo-ийн memoized-д нэг print() нэмж, "дэвтрээс уншлаа" эсвэл "шинээр тооцоолж байна" гэж хэвлүүлээд, жижиг fib(5) дээр Хичээл 3.3-ын lru_cache trace-тэй харьцуулж болно. Hit ба miss-ийн дараалал ижил байна уу? Мөн fast_fib = make_memo(fib) хувилбарт fast_fib(30)-ийг хоёр удаа дуудаж, хоёр дахь удаа хурдан байгааг харах нь орлогч дор хаяж гадна талын дуудлагад ажиллаж байгааг баталдаг. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
<function make_money_formatter.<locals>.fmt at 0x...> хэвлэгдэх. Энэ нь алдааны мессеж биш, гэвч үр дүн буруу. Factory-г дуудах ёстой газарт хаалтгүй бичсэн, эсвэл factory-ийн үр дүнг дахин дуудахаа мартсан. Жишээлбэл print(make_money_formatter("₮")) нь форматлагдсан дүнгийн оронд fmt функцийг өөрийг нь хэвлэнэ. Хоёр алхмыг санаарай: эхлээд factory-г дуудаж функц авна, дараа нь тэр функцийг дүнтэй дуудна. Нэг мөрөнд бол make_money_formatter("₮")(12500).
ZeroDivisionError: division by zero. "Тэгээр хуваах" гэсэн утгатай. Хуримтлуулагчийн дунджийг утга ирээгүй байхад тооцоолох гэж оролдоход гарна. Манай make_tracker-д дундаж зөвхөн add дотор, count нэмэгдсэний дараа бодогддог тул энэ алдаа гарахгүй. Харин та "одоогийн статистикийг утга нэмэлгүйгээр авах" функц нэмбэл count = 0 үед хуваах аюул гарна. Шийдэл нь хуваахаас өмнө count-ийг шалгаж, хоосон үед None эсвэл 0 буцаах.
TypeError: unhashable type: ‘list’. “list төрөл hash хийгдэх боломжгүй” гэсэн утгатай. Хичээл 3.3-ын lru_cache-ийн алдаатай ижил, учир нь манай memo ч мөн dict ашигладаг. memo-той функцэд list argument өгөхөд args tuple дотор list байх тул tuple өөрөө hash хийгдэх боломжгүй болж, cache-ийн түлхүүр болж чадахгүй. Шийдэл ижил: list-ийн оронд tuple өгөх, эсвэл ийм функцийг кэшлэхгүй байх.
TypeError: make_memo.<locals>.memoized() got an unexpected keyword argument ’n’. “make_memo-ийн локал memoized()-д таамаглаагүй ’n’ түлхүүр үгийн argument ирлээ” гэсэн утгатай. Манай энгийн memo зөвхөн *args хүлээн авдаг, түлхүүр үгийн argument-ийг биш. fib(n=30) гэж дуудвал энэ алдаа гарна. lru_cache түлхүүр үгийг ч дэмждэг, гэвч хэрэгжүүлэлт нь илүү нарийн. Бүлэг 5-д бид **kwargs нэмж орлогч функцийг бүрэн ерөнхий болгоно. Мессеж дэх make_memo.<locals>.memoized нэрийг анзаараарай: бид fib-ийг дуудсан ч Python алдааны мессежинд memoized гэж хэлж байна. Энэ нь дараагийн асуудлын шинж тэмдэг.
Функцийн нэр алга болох. Мессежгүй, гэвч Бүлэг 5-ийн гол сэдэв болох асуудал. @make_memo-той fib-ийн нэрийг шалгая:
python
print(fib.__name__)memoizedНэрийн хавтанг сольсон тул fib нэр одоо memoized функцийг заадаг. Тэр функц өөрийгөө memoized гэж нэрлэдэг. Алдааны мессеж, traceback, help() бүгд жинхэнэ fib-ийн оронд memoized-ийн тухай ярина. Олон функцийг нэг decorator-оор боовол бүгд “memoized” гэж нэрлэгдэж, debug хийхэд хүндрэлтэй болно. Энэ асуудлыг засах мэргэжлийн стандарт хэрэгсэл бий бөгөөд Хичээл 5.2-ын сэдэв юм.
Closure бодит кодонд гурван гол загвараар гарч ирдэг. Тохируулсан функц тохиргоог үүргэвчинд хадгалж, хэрэглэгчээс зөвхөн өгөгдлийг хүлээн авдаг. Мөнгөний форматлагч map-тай саадгүй холбогдсон. Бэлэн функцийн argument-ыг л бэхлэх бол partial илүү товч. Хуримтлуулагч дуудлага хооронд хураангуй төлөвийг хадгалж, generator-тэй хослоход сая утгыг тогтмол санах ойгоор боловсруулсан. Memo нь функц хүлээн авч, түүний үр дүнг dict-д хадгалдаг орлогч функц буцаадаг. dict-ийн дотрыг өөрчилж байгаа тул nonlocal шаардлагагүй. Удаан функц дээр хоёр дахь дуудлага хагас секундээс микросекунд болсон. Рекурсив функцэд орлогч зөвхөн ижил нэрэнд оноогдсон үед л ажилладаг, учир нь рекурсив дуудлага нэрээр явдаг. fib = make_memo(fib) гэсэн нэрийн хавтан солих загварын товчлол бол @ тэмдэг юм.
Энэ хичээлийн төгсгөлийн нээлт Бүлэг 5-ийн бүх ойлголтын эхлэл. Course 2-т та decorator-ыг хэрэглэх аргыг сурсан. Дараагийн хичээлд бид түүнийг дахин, энэ удаа closure-ийн бүрэн мэдлэгтэйгээр эхнээс нь бүтээнэ. Бүртгэлийн decorator бичиж, түүнийг тодорхойлолтоос дуудлага хүртэл trace-ээр мөрдөж, @ тэмдгийн ард байгаа бүх давхаргыг ил гаргана.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.