Ачааллж байна...
Course 2-т та decorator-тэй анх танилцсан. Функцийн дээр @ тэмдэгтэй мөр бичиж, функцэд нэмэлт зан чанар өгөх аргыг сурсан, мөн энгийн decorator-ыг загварын дагуу бичсэн. Тэр үед бид ихэнх зүйлийг "ийм л ажилладаг" гэж хүлээн авах ёстой байсан, учир нь decorator-ын ард байгаа механизм болох closure-ийг хараахан судлаагүй байлаа. Одоо байдал өөр. Бүлэг 4-т бид үүргэвч, хайрцаг, nonlocal-ийг бүрэн ойлгосон. Хичээл 4.3-ын төгсгөлд нэг чухал нээлт хийсэн: @ тэмдэг бол fib = make_memo(fib) гэсэн нэрийн хавтан солих мөрийн товчлол.
Энэ хичээлд бид decorator-ыг тэр нээлт дээр тулгуурлан эхнээс нь дахин бүтээнэ. Энэ удаа хоёр шинэ зүйл нэмэгдэнэ. Нэгдүгээрт, бидний decorator ямар ч функцтэй ажиллах ёстой: хэдэн ч байрлалын argument, хэдэн ч түлхүүр үгийн argument, ямар ч буцаах утга. Хичээл 4.3-ын make_memo түлхүүр үгийн argument-ыг дэмждэггүй байсныг санаарай. Энэ хичээлд тэр дутагдлыг засна. Хоёрдугаарт, decorator-ын ажиллагааг хоёр өөр цаг болгон салгаж харна: функц тодорхойлогдох цаг ба функц дуудагдах цаг. Энэ хоёрыг ялгах нь decorator-ын бүх нууцыг тайлах түлхүүр юм.
Хичээлийн гол хэрэгсэл нь бүртгэлийн decorator: функц бүрийн дуудлага, argument-ууд, буцаах утгыг хэвлэдэг. Бид түүнийг тодорхойлолтоос дуудлага хүртэл trace-ээр алхам алхмаар мөрдөнө. Хичээлийн төгсгөлд @ тэмдэг танд ердөө нэг мөр closure код мэт харагдах болно.
Бэлэг боох цаасыг төсөөлөөд үзээрэй. Та найздаа ном бэлэглэхээр шийдсэн. Ном өөрөө бэлэн, түүний агуулгыг та өөрчлөхгүй. Гэхдээ та түүнийг гоё цаасаар боож, гадна нь жижиг карт хавсаргана. Найз тань бэлгийг хүлээн авахад эхлээд цаас, картыг харна, дараа нь боодлыг задалж номыг олно. Ном боодлын дотор бүрэн бүтэн, өөрчлөгдөөгүй байна. Гэвч найзын бэлэгтэй харилцах туршлага өөрчлөгдсөн: тэр эхлээд картыг уншсан.
Decorator бол яг ийм боодол. Анхны функц бол ном: түүний код огт өөрчлөгдөхгүй. Decorator түүнийг wrapper функцээр "боож", гадна нь нэмэлт ажил хавсаргана: бүртгэл, хугацааны хэмжилт, шалгалт. Хэн нэгэн функцийг дуудахад эхлээд боодол ажиллана, боодол дотроосоо анхны функцийг дуудна, дараа нь үр дүнг гадагш дамжуулна.
Энэ зүйрлэлийн чухал хэсэг бол хэзээ боодог вэ гэдэг. Та бэлгийг төрсөн өдрийн үдэшлэгийн өмнө, гэртээ нэг удаа боодог. Найз бэлгийг задлах бүрт дахин боогдоггүй. Decorator ч мөн адил: боолт функц тодорхойлогдох үед нэг удаа хийгдэнэ. Харин боодлын доторх ажил, тухайлбал карт унших, функц дуудагдах бүрт хийгдэнэ. Энэ хоёр цагийг хичээлийн туршид ялгаж явна.
Ямар ч функцтэй ажилладаг бүртгэлийн decorator бичье. Хоёр цагийг ялгаж харахын тулд боолт хийгдэх мөчид ч, дуудлага бүрт ч хэвлэлт нэмсэн.
python
def log_calls(func):
print(f" [боолт: {func.__name__} функцийг боож байна]")
def wrapper(*args, **kwargs):
print(f" -> {func.__name__} дуудагдлаа: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f" <- {func.__name__} буцаалаа: {result!r}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
print("Функц тодорхойлогдож дууслаа")
print(add(2, 3))
print(add(10, b=5))Ажиллуулахад:
[боолт: add функцийг боож байна]
Функц тодорхойлогдож дууслаа
-> add дуудагдлаа: args=(2, 3), kwargs={}
<- add буцаалаа: 5
5
-> add дуудагдлаа: args=(10,), kwargs={'b': 5}
<- add буцаалаа: 15
15Кодыг давхарга давхаргаар нь уншъя. log_calls бол гадна функц, Бүлэг 4-ийн function factory. Тэр argument болгон функц авна. wrapper бол дотоод функц, боодол өөрөө. Тэр func-ийг гадна гэрээс хэрэглэдэг тул closure. log_calls wrapper-ийг буцаана.
wrapper-ийн гарын үсэг (*args, **kwargs) нь хичээлийн шинэ хэсэг. *args нь Хичээл 4.3-аас танил: бүх байрлалын argument-ыг tuple болгон цуглуулна. **kwargs нь түүний түлхүүр үгийн хамтрагч: b=5 гэх мэт бүх түлхүүр үгийн argument-ыг dict болгон цуглуулна. Хамтдаа тэд wrapper-ийг ямар ч хэлбэрийн дуудлагыг хүлээн авах чадвартай болгоно. wrapper дотор func(*args, **kwargs) нь эсрэг ажил хийнэ: tuple-ийг байрлалын argument болгон, dict-ийг түлхүүр үгийн argument болгон задалж, анхны функцэд яг гаднаас ирсэн хэлбэрээр дамжуулна. Энэ хос бол decorator-ын бүх нийтийн загвар бөгөөд та энэ хэлбэрийг Python-ий бараг бүх decorator-оос харна.
wrapper-ийн сүүлийн хэсэг: анхны функцийн үр дүнг result-д хадгалж, дуудлагын дараах бүртгэлийг хийж, тэгээд result-ийг буцаана. {result!r} нь f-string-ийн repr() хэлбэр: string-ийг хашилттай хэвлэнэ, ингэснээр буцаах утгын төрөл тодорхой харагдана.
Trace: тодорхойлох цаг
Гаралтын эхний мөрийг анхаар: “[боолт: ...]” нь "Функц тодорхойлогдож дууслаа"-аас өмнө хэвлэгдсэн. Гэтэл бид add-ийг хараахан дуудаагүй байсан. Энэ нь decorator тодорхойлох цагт ажилладгийг баталж байна.
Алхам 1 (def log_calls). log_calls функц объект үүсч, global нэрэнд оноогдоно. Бие ажиллаагүй.
Алхам 2 (@log_calls ба def add хамтдаа). Python эхлээд def add мөрийг ажиллуулж, анхны add функц объектыг үүсгэнэ. Гэвч түүнийг шууд add нэрэнд оноохгүй, харин дээрх @ мөрийг хараад, тэр функц объектыг log_calls-д өгнө. Энэ бол Хичээл 4.3-ын add = log_calls(add).
Алхам 3 (log_calls-ийн бие ажиллах). log_calls-ийн гэрт func = анхны add. Эхний print ажиллаж “[боолт: add ...]” хэвлэгдэнэ. Дараа нь def wrapper мөр ажиллаж, wrapper функц төрнө. Түүний кодонд func гарч ирдэг тул Python func-ийн хайрцгийг үүргэвчинд нь хийнэ. log_calls wrapper-ийг буцааж, гэр хаагдана. wrapper-ийн үүргэвчинд: func хайрцаг, дотор нь анхны add.
Алхам 4 (нэрийн хавтан солигдох). log_calls-ийн буцаасан wrapper global add нэрэнд оноогдоно. Global түвшинд: log_calls нь decorator функц, add нь wrapper. Анхны add функц ямар ч global нэргүй, зөвхөн wrapper-ийн үүргэвчинд амьдарна.
Энэ trace-ийн дүгнэлт: @ мөр файл уншигдах үед, функц тодорхойлогдох мөчид нэг удаа ажиллана. Хэрэв таны програмд decorator-той зуун функц байвал файл импортлогдох мөчид log_calls зуун удаа ажиллаж, зуун wrapper үүснэ. Үүний дараа decorator өөрөө дахин хэзээ ч ажиллахгүй. Ажиллах нь зөвхөн wrapper-ууд.
Үүргэвчийг Хичээл 4.1-ийн аргаар нээж баталъя:
python
print(add)
print(add.__code__.co_freevars)
print(add.__closure__[0].cell_contents)<function log_calls.<locals>.wrapper at 0x7f3c1a2b4e00>
('func',)
<function add at 0x7f3c1a2b4d60>add нэр одоо log_calls.<locals>.wrapper-ийг заана, яг log_calls-ийн гэрт төрсөн wrapper. Түүний үүргэвчинд func гэсэн ганц хувьсагч бий. Тэр хайрцгийг нээхэд анхны <function add ...> гарч ирнэ. Бэлгийн ном боодлын дотор бүрэн бүтэн байна.
Trace: дуудах цаг
Алхам 5 (add(2, 3)). add нэр wrapper-ийг заадаг тул wrapper дуудагдана. wrapper-ийн шинэ frame үүснэ. Түүний гэрт: args = (2, 3), kwargs = {}.
Алхам 6 (эхний print). func-ийг үүргэвчнээс олж, түүний __name__ буюу “add”-ыг хэвлэнэ.
Алхам 7 (result = func(*args, **kwargs)). func үүргэвчнээс олдоно: анхны add. *args нь (2, 3)-ийг 2 ба 3 гэсэн хоёр argument болгон задлана. kwargs хоосон тул юу ч нэмэгдэхгүй. Энэ нь add_original(2, 3)-тай ижил. Анхны add-ийн frame үүсч, 5 буцаана. wrapper-ийн гэрт: args, kwargs, result = 5.
Алхам 8 (хоёр дахь print ба return). “<- add буцаалаа: 5” хэвлэгдэнэ. wrapper 5-ыг буцаана. frame хаагдана, үүргэвч хэвээр.
Алхам 9 (add(10, b=5)). Ижил зам, гэвч одоо args = (10,), kwargs = {‘b’: 5}. func(*args, **kwargs) нь add_original(10, b=5) болж задарна. (10,) гэсэн бичлэг нь нэг элементтэй tuple-ийн Python дахь хэлбэр, төгсгөлийн таслал нь түүнийг жирийн хаалттай тооноос ялгадаг.
Хоёр цагийг нэгтгэвэл: тодорхойлох цагт log_calls нэг удаа ажиллаж, боодол бэлтгэж, нэрийн хавтанг солино. Дуудах цагт wrapper дуудлага бүрт ажиллаж, үүргэвчнээсээ анхны функцийг гаргаж дуудна. Decorator-ын тухай аливаа асуулт "энэ аль цагт болж байна вэ?" гэсэн асуултаар эхэлбэл ихэнхдээ хариулт өөрөө олддог.
Жишээ 1: хугацаа хэмжигч — @-гүй ч ажилладаг
Энэ сургалтын хэмжилтийн зарчим decorator-ын хамгийн түгээмэл хэрэглээний нэг. Функц бүрийн ажиллах хугацааг хэмждэг decorator бичье.
python
import time
def timed(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__}: {elapsed:.4f} секунд")
return result
return wrapper
@timed
def build_squares(n):
return [x * x for x in range(n)]
squares = build_squares(1_000_000)
print(len(squares))Миний компьютер дээрх нэг ажиллуулалт:
build_squares: 0.0412 секунд
1000000Бүтэц log_calls-тай яг ижил: хэмжилт эхлэх, анхны функцийг дуудах, хэмжилт дуусгах, үр дүнг буцаах. build_squares өөрөө хэмжилтийн тухай нэг ч мөр агуулаагүй. Тоо таны компьютер дээр өөр байна.
Одоо чухал санаа. @ бол товчлол, тиймээс decorator-ыг @-гүйгээр, гараар ч хэрэглэж болно. Энэ нь өөрийн бичээгүй, def мөр нь таны гарт байдаггүй функцүүдэд маш хэрэгтэй. Хичээл 3.4-ийн log боловсруулагчийг санаарай. count_levels функцийг өөрчлөхгүйгээр хугацааг нь хэмжье:
python
from log_tools import build_records, count_levels
timed_count = timed(count_levels)
print(timed_count(build_records(["app.log"])))count_levels: 1.6283 секунд
{'INFO': 849822, 'WARNING': 100341, 'ERROR': 49837}timed(count_levels) нь count_levels-ийг боосон wrapper буцаасан. Бид түүнийг timed_count гэж шинэ нэрээр хадгалсан тул анхны count_levels нэр өөрчлөгдөөгүй, log_tools-ийн бусад хэрэглэгчдэд нөлөөлөхгүй. @ ашиглавал нэрийн хавтан солигдоно. Гараар хэрэглэвэл та шинэ нэр сонгох эсвэл ижил нэрэнд оноох шийдвэрийг өөрөө гаргана. Decorator бол ердийн функц тул бэлэн функцүүдийг ч боож болно: timed(sorted) нь Python-ий суурь sorted-ийн хугацааг хэмждэг хувилбар үүсгэнэ.
Жишээ 2: шалгагч — дуудлагыг зогсоох боодол
Боодол заавал анхны функцийг дуудах албагүй. Тэр дуудлагаас өмнө шалгалт хийж, нөхцөл биелэхгүй бол анхны функцийг огт дуудахгүйгээр алдаа шидэж болно. Бүх argument эерэг тоо байхыг шаарддаг decorator бичье.
python
def require_positive(func):
def wrapper(*args, **kwargs):
for value in list(args) + list(kwargs.values()):
if value <= 0:
raise ValueError(
f"{func.__name__}: бүх утга эерэг байх ёстой, {value} ирлээ"
)
return func(*args, **kwargs)
return wrapper
@require_positive
def rectangle_area(width, height):
return width * height
print(rectangle_area(4, 5))
print(rectangle_area(4, height=-2))20
Traceback (most recent call last):
File "C:\python-gunzgii\validate.py", line 18, in <module>
print(rectangle_area(4, height=-2))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python-gunzgii\validate.py", line 6, in wrapper
raise ValueError(
ValueError: rectangle_area: бүх утга эерэг байх ёстой, -2 ирлээwrapper байрлалын ба түлхүүр үгийн бүх утгыг нэг жагсаалт болгон шалгаж байна. kwargs.values() нь dict-ийн утгуудыг өгнө. Эхний дуудлага шалгалтыг давж, анхны функц 20 буцаасан. Хоёр дахь дуудлагад height = -2 тул wrapper ValueError шидсэн, анхны rectangle_area огт дуудагдаагүй.
Traceback-ийг анхааралтай уншъя. Хоёр frame харагдаж байна: 18-р мөрөнд rectangle_area-г дуудсан газар, дараа нь 6-р мөрөнд in wrapper. Python алдаа wrapper дотор гарсныг хэлж байна, rectangle_area дотор биш. Энэ үнэн: бид rectangle_area гэж дуудсан ч тэр нэр wrapper-ийг заадаг. Энэ нь decorator-той кодыг debug хийхэд чухал мэдлэг: traceback-д “wrapper” гэж харагдвал тэр нь боодлын доторх frame гэдгийг ойлгоорой. Энэ шалгагч ердөө тоон argument-тай функцэд тохиромжтой гэдгийг шударгаар хэлье. String өгвөл value <= 0 харьцуулалт TypeError өгнө. Бодит шалгагчид төрлийг ч шалгадаг.
Жишээ 3: олон функц, олон боодол
Нэг decorator-ыг олон функцэд хэрэглэвэл тус бүр өөрийн wrapper, өөрийн үүргэвчтэй болно. Энэ бол Хичээл 4.1-ийн "нэг гэр, олон хүүхэд, олон үүргэвч"-ийн шууд хэрэглээ.
python
@log_calls
def greet(name):
return f"Сайн байна уу, {name}"
@log_calls
def square(x):
return x * x
greet("Бат")
square(7)
print(greet.__closure__[0].cell_contents)
print(square.__closure__[0].cell_contents)
print(greet.__closure__[0] is square.__closure__[0]) [боолт: greet функцийг боож байна]
[боолт: square функцийг боож байна]
-> greet дуудагдлаа: args=('Бат',), kwargs={}
<- greet буцаалаа: 'Сайн байна уу, Бат'
-> square дуудагдлаа: args=(7,), kwargs={}
<- square буцаалаа: 49
<function greet at 0x7f3c1a2b5080>
<function square at 0x7f3c1a2b5260>
FalseЭхний хоёр мөр тодорхойлох цагт гарсан: log_calls хоёр удаа, функц бүрт нэг удаа ажилласан. Дараагийн дөрвөн мөр дуудах цагт гарсан. greet ба square хоёулаа одоо “wrapper” нэртэй функцийг заадаг ч тэд хоёр өөр wrapper, хоёр өөр үүргэвчтэй. Нэг нь анхны greet-ийг, нөгөө нь анхны square-ийг барьж байна. Сүүлийн мөрийн False тэдний хайрцгууд өөр гэдгийг баталж байна. {result!r} нь greet-ийн үр дүнг хашилттай хэвлэсэн тул string буцсан нь тодорхой.
Жишээ 4: @-ийг бүрэн задлах
Хичээлийн гол томьёог нэг дор бичье. Доорх хоёр код яг ижил:
python
@log_calls
def add(a, b):
return a + bpython
def add(a, b):
return a + b
add = log_calls(add)Энэ томьёо ямар ч decorator-д хүчинтэй: lru_cache, timed, require_positive, бусдын бичсэн бүх decorator. Хэрэв таныг ямар нэг decorator-ын зан чанар гайхшруулбал хоёр дахь хэлбэрт хөрвүүлээд асуугаарай: “log_calls(add) юу буцааж байна вэ? Тэр юуг үүргэвчиндээ барьж байна вэ? add нэр одоо юуг заадаг вэ?” Эдгээр гурван асуултын хариулт decorator-ын тухай бүх зүйлийг хэлнэ. Бүлэг 4-ийн гурван хичээл яг энэ гурван асуултад хариулах хэрэгслийг бидэнд өгсөн.
Хүсвэл log_calls-ийг Хичээл 4.3-ын @make_memo-тэй fib дээр гараар хэрэглэж, logged_fib = log_calls(fib) хийгээд logged_fib(5)-ийг дуудаж үзээрэй. Хэдэн мөр хэвлэгдэх вэ, яагаад рекурсив дуудлагууд бүртгэгдэхгүй байна вэ? Хичээл 4.3-ын нэрийн хавтангийн trace энэ асуултад хариулна. Сонирхвол timed-ийг Хичээл 3.4-ийн slowest ба first_errors функцүүдэд хэрэглэж, эрт зогсолтын хурдны ялгааг өөрийн хэмжилтээр харж болно. Мөн log_calls-ийн wrapper-т дуудлагын дугаар нэмэх: Хичээл 4.2-ын nonlocal тоолуурыг wrapper-ийн гадна, log_calls-ийн гэрт байрлуулбал функц бүр өөрийн тоолууртай болох уу? Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
Функц None буцаах. Мессежгүй алдаа, decorator бичихэд хамгийн түгээмэл нь. wrapper дотор func(*args, **kwargs) дуудсан ч үр дүнг буцаахаа мартсан:
python
def wrapper(*args, **kwargs):
print("дуудагдлаа")
func(*args, **kwargs)Анхны функц ажиллаж үр дүнгээ буцаана, гэвч wrapper тэр үр дүнг хаяж, өөрөө return-гүйгээр дуусна. Тэгэхээр боосон функц бүр None буцаана. print(add(2, 3)) нь 5-ын оронд None хэвлэнэ, Python юу ч хэлэхгүй. Шийдэл нь анхны функцийн үр дүнг заавал хадгалж буцаах: result = func(*args, **kwargs), return result, эсвэл шууд return func(*args, **kwargs).
TypeError: ‘NoneType’ object is not callable. “NoneType объектыг дуудах боломжгүй” гэсэн утгатай. Decorator-ын төгсгөлд return wrapper мөрийг мартсан үед гарна. Энэ алдаа сонирхолтой цагт гардаг. Тодорхойлох цагт log_calls None буцааж, add нэрэнд None оноогдоно, гэвч ямар ч алдаа гарахгүй. Алдаа зөвхөн add-ийг анх дуудах үед, файлын огт өөр мөрөнд гарна. Traceback дуудлагын мөрийг заах тул decorator-ын төгсгөлийг шалгахаа санаарай.
TypeError: add() missing 2 required positional arguments: ‘a’ and ‘b’. “add()-д шаардлагатай ‘a’ ба ‘b’ хоёр argument дутуу байна” гэсэн утгатай. Энэ алдаа return wrapper биш return wrapper() гэж хаалттай бичсэн үед тодорхойлох цагт гарна. Decorator wrapper-ийг буцаахын оронд яг тэр мөчид argument-гүйгээр дуудна. wrapper дотроосоо анхны add-ийг argument-гүйгээр дуудаж, энэ алдааг үүсгэнэ. Traceback-д @log_calls мөр харагдана, бид add-ийг хэзээ ч дуудаагүй байхад. Хэрэв програм ямар ч функц дуудагдахаас өмнө, файл импортлогдох мөчид унаж байвал decorator-ын return мөрийг шалгаарай.
TypeError: log_calls.<locals>.wrapper() takes 0 positional arguments but 2 were given. “log_calls-ийн локал wrapper() байрлалын 0 argument авдаг боловч 2 ирлээ” гэсэн утгатай. wrapper-ийг def wrapper(): гэж argument-гүйгээр бичсэн үед гарна. Боосон функцийн бүх дуудлага wrapper-ээр дамждаг тул wrapper анхны функцийн хүлээн авах бүх argument-ыг хүлээн авах ёстой. Шийдэл нь бүх нийтийн хэлбэр: def wrapper(*args, **kwargs):. Мессеж дэх log_calls.<locals>.wrapper нэрийг анзаараарай: алдаа таны дуудсан функцийн нэрээр биш, wrapper-ийн нэрээр гарч байна.
TypeError: log_calls() missing 1 required positional argument: ‘func’. “log_calls()-д шаардлагатай ‘func’ argument дутуу байна” гэсэн утгатай. Decorator-ыг хаалттай бичсэн үед гарна: @log_calls(). Хоосон хаалт нь Python-д “log_calls-ийг одоо argument-гүйгээр дуудаад, үр дүнг decorator болгон хэрэглэ” гэж хэлж байна. Гэтэл log_calls func хүлээж байна. Хичээл 3.3-т бид @lru_cache(maxsize=None) гэж хаалттай бичсэн, тэгвэл lru_cache яагаад ажилласан бэ? Учир нь тэр argument-тай decorator, огт өөр бүтэцтэй бөгөөд Хичээл 5.3-ын сэдэв. Одоохондоо дүрэм: энэ хичээлийн decorator-уудыг хаалтгүйгээр бичнэ.
Decorator бол функц хүлээн авч, closure-тэй wrapper функц буцаадаг энгийн функц бөгөөд @d гэдэг нь f = d(f)-ийн товчлол. Decorator-ын ажиллагаа хоёр цагт хуваагддаг. Тодорхойлох цагт decorator нэг удаа ажиллаж, wrapper-ийг үүсгэж, анхны функцийг түүний үүргэвчинд хийж, нэрийн хавтанг солино. Дуудах цагт wrapper дуудлага бүрт ажиллаж, үүргэвчнээсээ анхны функцийг гаргаж дуудна. *args, **kwargs хос wrapper-ийг ямар ч дуудлагын хэлбэрийг хүлээн авч, анхны функцэд өөрчлөлтгүй дамжуулдаг болгодог. wrapper анхны функцийн үр дүнг заавал буцаах ёстой. Decorator-ыг @-гүйгээр гараар хэрэглэж, бэлэн функцүүдийг ч боож болно. Нэг decorator олон функцэд хэрэглэгдэхэд тус бүр өөрийн wrapper, өөрийн үүргэвчтэй болно.
Энэ хичээлийн туршид нэг асуудал бидний хажуугаар хэд хэдэн удаа өнгөрсөн. add-ийг хэвлэхэд log_calls.<locals>.wrapper гэж гарсан. Traceback-д “in wrapper” харагдсан. Хичээл 4.3-т fib.name нь “memoized” болсон. Decorator хэрэглэх бүрт функц өөрийн нэр, тайлбар, тодорхойлолтоо алддаг. Дараагийн хичээлд бид энэ "хувийн мэдээллийн алдагдал"-ыг бүрэн харуулж, help() хэрхэн эвдэрдгийг үзэж, стандарт сангийн functools.wraps-ээр засна. Энэ нь бүх мэргэжлийн decorator-ын заавал дагах стандарт юм.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.