Ачааллж байна...
Өмнөх хичээлд бид Timer context manager-ийг class-аар бичсэн: __init__, __enter__, __exit__, гурван attribute. Ажил энгийн байсан ч бүтэц нь бага зэрэг урт: "эхлэх цагийг тэмдэглэ, блокийг ажиллуул, дуусах цагийг тэмдэглэ" гэсэн гурван алхмын санааг илэрхийлэхийн тулд класс ба хоёр тусдаа method шаардлагатай болсон. Хоёр method мэдээллээ хуваалцахын тулд self дээр attribute хадгалах хэрэгтэй байсан.
Энэ хичээлд бид ижил Timer-ийг гурван мөрөнд бичнэ. Үүнд стандарт сангийн contextlib модулийн contextmanager decorator хэрэгтэй. Энэ decorator generator функцийг context manager болгон хувиргадаг. Бүлэг 2-т бид generator-ийг "түр зогсоох товчтой машин" гэж ойлгосон: yield хүрэхэд бүх төлөвөө хөлдөөж, гаднаас дахин дуудагдах хүртэл хүлээдэг. Энэ хичээлд тэр машины зогсолт with блокийн хаалга болно. yield-ээс өмнөх код бол __enter__, yield-ийн дараах код бол __exit__, машин зогсож байх хооронд with блок ажиллана.
Энэ хичээл Түвшин 1 ба Түвшин 2-ын санаануудыг нэг дор нэгтгэдгээрээ онцлог. Generator-ийн түр зогсолт, decorator-ын боолт, context manager-ийн протокол гурвуулаа нэг хэрэгсэлд нийлнэ. Гэвч энэ товчлол нэг урхитай ирдэг бөгөөд түүнийг алдааг хэрхэн дамжуулдгийг trace-ээр мөрдөж ойлгоно. Урхийг мэдэхгүйгээр generator хэлбэрийн context manager бичих нь өмнөх хичээлийн "цэвэрлэгээний баталгаа"-г чимээгүй эвдэж болно.
Серверийн өрөөний харуул руугаа буцъя. Class-аар бичсэн context manager-т харуулын ажил хоёр тусдаа заавар болгон бичигдсэн байсан: "орохдоо юу хийх" ба "гарахдаа юу хийх". Харуул хоёр зааврын хооронд мэдээллээ дэвтэртээ хадгалах ёстой байв.
Одоо өөр төрлийн харуулыг төсөөлөөд үзээрэй: түр зогсоох товчтой хүн. Тэр хаалган дээр зогсож, зочныг угтаж, нэрийг бичиж, түлхүүр өгнө. Дараа нь хөлдөнө: яг тэр байрлалдаа, гартаа үзэг барьсаар, толгойд нь бүх зүйл хадгалагдсан. Зочин өрөөнд ажиллана. Зочин гарахад харуул сэрж, яг зогссон газраасаа үргэлжилнэ: дэвтэр рүүгээ харж, гарсан цагийг бичиж, түлхүүрийг хураана. Түүнд тусдаа "гарах үеийн заавар" хэрэггүй, учир нь тэр ердөө нэг тасралтгүй үйл ажиллагааг хийж байна, дундаа нь нэг удаа хөлддөг.
Энэ бол @contextmanager-тай generator. Нэг функц, нэг тасралтгүй дараалал, дундаа нэг yield. Гэвч зүйрлэлд чухал асуулт бий: зочин галын дохиогоор гүйж гарвал харуул хэрхэн сэрэх вэ? Тэр сэрэхдээ "зочин хэвийн гарлаа" гэсэн мэдээ биш, "гал гарлаа!" гэсэн хашгираан сонсоно. Харуул энэ хашгирааныг хүлээн авахад бэлтгэлгүй бол гарах үеийн ажлаа хийлгүй зүгээр л зугтана. Энэ бол хичээлийн урхи.
python
import time
from contextlib import contextmanager
@contextmanager
def timer(label="Блок"):
start = time.perf_counter()
yield
print(f"{label}: {time.perf_counter() - start:.4f} секунд")
with timer("Жагсаалт бүтээх"):
squares = [x * x for x in range(1_000_000)]Миний компьютер дээрх нэг ажиллуулалт:
Жагсаалт бүтээх: 0.0418 секундФункцийн бие гурван мөр. Эхний мөр эхлэх цагийг хадгална: энэ бол __enter__-ийн ажил. Хоёр дахь мөр yield: энд машин зогсож, with блок ажиллана. Гурав дахь мөр дуусах цагийг тооцоолж хэвлэнэ: энэ бол __exit__-ийн ажил. Өмнөх хичээлийн Timer class арван гаруй мөр байсан. Үр дүн ижил, хугацаа таны компьютер дээр өөр байна.
Хоёр санааг анзаараарай. Нэгдүгээрт, start бол энгийн локал хувьсагч. self ч, attribute ч, closure-ийн хайрцаг ч биш. Энэ нь yield дээр хөлдсөн generator-ийн frame-д, Хичээл 2.1-ийн "хөлдсөн төлөв"-д амьдардаг. Хоёрдугаарт, timer нь энгийн функцийн нэртэй, жижиг үсгээр. contextlib-ийн бүтээсэн context manager-уудыг ихэвчлэн функц шиг нэрлэдэг, учир нь тэд функц хэлбэрээр бичигддэг.
Trace: машин ба хаалга
with timer("Жагсаалт бүтээх") мөр ажиллахад юу болдгийг алхам алхмаар мөрдье. Энэ trace Бүлэг 2 ба Бүлэг 6-ыг нэгтгэнэ.
Алхам 1 (timer("Жагсаалт бүтээх")). Timer бол @contextmanager-оор боогдсон generator функц. Хичээл 5.1-ийн томьёогоор timer = contextmanager(timer), тэгэхээр timer нэр одоо contextmanager-ийн wrapper-ийг заадаг. Энэ wrapper-ийг дуудахад тэр дотроо анхны generator функцийг дуудаж, generator объект үүсгэнэ. Хичээл 2.1-ийн гол баримтыг санаарай: generator функцийг дуудахад бие огт ажиллахгүй. Wrapper тэр generator-ийг context manager объект дотор хийж буцаана. Санах ойд: context manager объект, дотор нь эхэндээ зогссон generator, түүний frame-д label = "Жагсаалт бүтээх", start хараахан алга.
Алхам 2 (__enter__). with context manager объектын __enter__-ийг дуудна. Тэр дотроо next(generator) дуудна: машин анх удаа асна. Функцийн бие эхнээс ажиллаж, start-ийг тэмдэглэж, yield-д хүрнэ. yield утгагүй тул None гаргаж, машин хөлдөнө. __enter__ тэр None-ыг буцаана, хэрэв as бичсэн бол тэр нэрэнд None очно. Генераторын хөлдсөн frame-д: label, start = 2918.4417, зогсолтын цэг: yield мөр.
Алхам 3 (блок). Жагсаалт бүтээгдэнэ. Generator хөлдсөн хэвээр, юу ч мэдэхгүй.
Алхам 4 (__exit__, хэвийн). Блок хэвийн дууссан тул with __exit__(None, None, None) дуудна. Context manager алдаа байхгүйг хараад дахин next(generator) дуудна. Машин yield-ийн дараагаас сэрнэ, start-ийн утга хадгалагдсан хэвээр. Хугацааг тооцоолж хэвлэнэ. Функцийн бие дуусч, generator StopIteration шиднэ. Context manager энэ StopIteration-ийг хүлээж байсан: "машин хэвийн дууслаа" гэсэн дохио. Тэр түүнийг барьж, False буцаана.
Хичээл 2.1-ийн trace-тэй харьцуулаарай: тэнд next() бүр машиныг дараагийн yield хүртэл ажиллуулдаг байсан, эцэст нь StopIteration гарсан. Энд яг ижил зүйл болж байна, гэхдээ next()-ийг бид биш, context manager дуудаж байна: нэг удаа __enter__-д, нэг удаа __exit__-д. Хоёр next()-ийн хооронд машин хөлдсөн байх үед бидний with блок ажилладаг. Энэ бол “yield нь хаалга” гэсэн санааны бүрэн утга.
Төлөвийн гурван хадгалах газрыг одоо нэгтгэж болно. Class-аар бичсэн context manager төлөвийг self дээр хадгалдаг. Closure төлөвийг үүргэвчин доторх хайрцагт хадгалдаг. Generator хэлбэрийн context manager төлөвийг хөлдсөн frame-д хадгалдаг. Гурван өөр механизм, нэг зорилго: хоёр мөчийн хооронд мэдээллийг амьд байлгах.
Одоо харуулын зүйрлэлийн "гал гарлаа" хэсэг. Блок дотор алдаа шидье:
python
try:
with timer("Хуваах"):
result = 10 / 0
except ZeroDivisionError:
print("Гадна талд алдаа барьсан")Гадна талд алдаа барьсанХугацаа хэвлэгдээгүй. Өмнөх хичээлийн Timer class алдаа гарсан ч хэмжилтээ хэвлэдэг байсан. Энд хэвлэлт алга. Яагаад?
Алхам 1-3 өмнөхтэй ижил: машин yield дээр хөлдөж, блок эхэлнэ.
Алхам 4 (блок дотор алдаа). ZeroDivisionError шидэгдэнэ. with __exit__(ZeroDivisionError, алдаа, traceback) дуудна.
Алхам 5 (алдааг машин руу шидэх). Энд гол ялгаа. Context manager алдаа байгааг хараад next() биш, generator-ийн throw() method-ыг дуудна. throw() нь машиныг сэрээнэ, гэхдээ yield-ийн дараагаас хэвийн үргэлжлүүлэхийн оронд yield мөрөн дээр алдаа шидэгдсэн мэт сэрээнэ. Generator-ийн дотроос харахад yield мөр гэнэт ZeroDivisionError шидсэн мэт харагдана.
Алхам 6 (алдаа generator дотор). yield мөрийг ямар ч try хүрээлээгүй тул алдаа generator-ийн биеэс шууд гарна. print мөр хэзээ ч ажиллахгүй: алдаа түүнээс өмнө функцийг орхисон. Хэрэв yield-ийн дараа файл хаах, тохиргоо сэргээх зэрэг цэвэрлэгээний код байсан бол тэр ч ажиллахгүй.
Алхам 7 (алдаа гадагш). Generator-оос гарсан ZeroDivisionError context manager-т буцаж ирнэ. Тэр "энэ миний шидсэн алдаа" гэж таниад, __exit__-ээс False буцааж, алдааг гадагш дамжуулна. Гадна талын except барина.
Тэгэхээр зүйрлэлийн харуул "гал гарлаа!" гэсэн хашгирааныг хүлээн аваад гарах үеийн ажлаа хийлгүй зугтсан. Өмнөх хичээлийн бүх баталгаа, "юу ч болсон __exit__ заавал ажиллана", энд эвдэрсэн мэт харагдаж байна. Үнэндээ __exit__ ажилласан, гэвч бидний yield-ийн дараах код ажиллаагүй.
Засвар: try/finally yield-ийн эргэн тойронд
Шийдэл нь Хичээл 6.1-ээс танил: finally. yield-ийг try дотор хийж, цэвэрлэгээг finally-д тавина:
python
@contextmanager
def timer(label="Блок"):
start = time.perf_counter()
try:
yield
finally:
print(f"{label}: {time.perf_counter() - start:.4f} секунд")
try:
with timer("Хуваах"):
result = 10 / 0
except ZeroDivisionError:
print("Гадна талд алдаа барьсан")Хуваах: 0.0000 секунд
Гадна талд алдаа барьсанОдоо Алхам 6 өөрчлөгдөнө: yield мөрөн дээр шидэгдсэн алдаа try блокийг орхиж байгаа тул finally заавал ажиллана. Хугацаа хэвлэгдэж, дараа нь алдаа үргэлжлэн гадагш гарна. Хэвийн тохиолдолд ч finally ажиллана, учир нь try блок хэвийн дууссан ч finally ажилладаг. Тэгэхээр нэг бүтэц хоёр тохиолдлыг хамардаг.
Энэ хичээлийн гол дүрэм: цэвэрлэгээ хийдэг @contextmanager бүрт yield-ийг try/finally-д хий. Анхны жишээний хувилбар хэвийн тохиолдолд ажилласан тул алдааг анзаарахад хэцүү. Энэ нь яг алдаа гарахад л илэрдэг асуудал, бас тэр мөч цэвэрлэгээ хамгийн чухал мөч. Timer-ийн хувьд үр дагавар нь зөвхөн хэвлэлт алга болох. Гэвч файл, холболт, түгжээний хувьд нөөц суллагдахгүй үлдэнэ.
Жишээ 1: үр дүнг хэрэглэгчид өгөх
Өмнөх хичээлийн Timer class as t гэж хэрэглэгдэж, блокийн дараа t.elapsed-ээр хугацааг өгдөг байсан. Generator хэлбэрт self байхгүй. Тэгвэл хэмжилтийг хэрхэн хэрэглэгчид өгөх вэ? yield-ийн гаргасан утга as-ийн нэрэнд очдог. Хэрэв бид өөрчлөгдөж болох объект, жишээлбэл dict гаргавал, дараа нь түүнийг finally дотор дүүргэж болно:
python
@contextmanager
def timer(label="Блок"):
result = {"label": label, "elapsed": None}
start = time.perf_counter()
try:
yield result
finally:
result["elapsed"] = time.perf_counter() - start
print(f"{label}: {result['elapsed']:.4f} секунд")
with timer("Нийлбэр") as t:
total = sum(range(10_000_000))
print("Хэмжилт:", t)Нийлбэр: 0.1503 секунд
Хэмжилт: {'label': 'Нийлбэр', 'elapsed': 0.15034217}Trace: Алхам 2-т generator result dict үүсгэж, yield-ээр гадагш гаргана, тэр t нэрэнд очно. Одоо t ба generator-ийн result хоёулаа нэг ижил dict-ийг заана. Блок дууссаны дараа finally тэр dict-ийн дотрыг өөрчилж, elapsed-ийг бичнэ. t ижил dict-ийг заадаг тул өөрчлөлтийг харна.
Энэ нь Хичээл 4.2-ын "өөрчлөх ба солих"-ын ялгаатай яг ижил. Хэрэв бид yield elapsed гэж тоо гаргаад дараа нь elapsed-ийг шинэчилбэл t огт өөрчлөгдөхгүй: тоо өөрчлөгдөхгүй объект, шинэчлэх нь нэрэнд шинэ тоо оноох л болно. dict-ийн дотрыг өөрчлөх нь гадна талын нэрэнд харагддаг. Энэ бол Python-ий нэр ба объектын харилцааны нэг санаа, гурав дахь удаагаа өөр нөхцөлд гарч ирж байна.
Жишээ 2: түр өөрчлөлт, богино хэлбэрээр
Өмнөх хичээлийн TemporarySetting class гурван method-той байсан. Generator хэлбэрээр:
python
@contextmanager
def temporary_setting(settings, key, value):
old_value = settings[key]
settings[key] = value
try:
yield settings
finally:
settings[key] = old_value
config = {"page_size": 20, "lang": "mn"}
with temporary_setting(config, "page_size", 500):
print("Блок дотор:", config["page_size"])
print("Блокийн дараа:", config["page_size"])Блок дотор: 500
Блокийн дараа: 20Class хувилбарт __init__ гурван argument-ыг self дээр хадгалж, __enter__ хуучин утгыг self.old_value-д хадгалж, __exit__ түүнийг буцааж тавьдаг байсан. Энд бүгд нэг функцийн локал хувьсагчид, нэг тасралтгүй дарааллаар уншигдана: "хуучнаа хадгал, шинийг тавь, хүлээ, хуучнаа буцаа". try/finally хэвээр байгааг анзаараарай: тохиргоо сэргээх бол яг алдаа гарахад хамгийн чухал цэвэрлэгээ.
Жишээ 3: алдааг залгих — try/except yield-ийн эргэн тойронд
Өмнөх хичээлд __exit__ True буцааж алдааг залгидаг байсан. Generator хэлбэрт алдааг залгихын тулд yield-ийн эргэн тойронд except хэрэглэнэ. Trace-ийн Алхам 5-ын дагуу блокийн алдаа yield мөрөн дээр шидэгдэх тул ердийн except түүнийг барьж чадна:
python
import os
@contextmanager
def ignore(*error_types):
try:
yield
except error_types as error:
print(f" Алгаслаа: {type(error).__name__}: {error}")
with ignore(FileNotFoundError):
os.remove("old_report.txt")
print("Програм үргэлжилж байна") Алгаслаа: FileNotFoundError: [Errno 2] No such file or directory: 'old_report.txt'
Програм үргэлжилж байна*error_types нь Хичээл 4.3-аас танил: хэдэн ч алдааны төрөл хүлээн авч tuple болгоно. Python-ий except tuple хүлээн авдаг тул except error_types түүний аль ч төрлийг барина. Алдаа барьсны дараа generator хэвийн дуусна: алдааг дахин шидэхгүй. Context manager “generator алдаагүй дууслаа” гэдгийг хараад, алдааг залгигдсан гэж үзэн True буцаана. Програм үргэлжилнэ.
Хэрэв барьсан төрөлд багтахгүй алдаа гарвал except түүнийг барихгүй, алдаа generator-оос гарч, ердийнхөөрөө гадагш дамжина. Энэ функц бол өмнөх хичээлд дурдсан contextlib.suppress-ийн бүрэн хувилбар. Class-аар 12 мөр, энд 5 мөр.
Жишээ 4: decorator болгон хэрэглэх
Энэ хичээлийн сүүлийн нээлт Бүлэг 5-тай холбогдоно. @contextmanager-аар бүтээсэн context manager-ийг decorator болгон ч хэрэглэж болно. Энэ нь contextlib-ийн нэмэлт боломж бөгөөд функцийн бүх биеийг with блок дотор ороосонтой ижил.
python
@timer("Тайлан бэлдэх")
def build_report():
return sum(x * x for x in range(2_000_000))
build_report()
build_report()Тайлан бэлдэх: 0.1162 секунд
Тайлан бэлдэх: 0.1149 секунд@timer("Тайлан бэлдэх") нь Хичээл 5.3-ын argument-тай decorator шиг харагдаж байна. Үнэндээ timer("Тайлан бэлдэх") context manager объект буцаадаг бөгөөд тэр объект decorator болж ажиллах чадвартай. build_report дуудагдах бүрт тэр шинэ generator үүсгэж, функцийн биеийг with дотор ажиллуулна. Тэгэхээр нэг хэрэгсэл хоёр хэлбэрээр: кодын хэдэн мөрийг хэмжихэд with, бүхэл функцийг хэмжихэд decorator. Хичээл 5.1-ийн timed decorator ба энэ хичээлийн timer одоо нэг хэрэгсэл болж нийлсэн. Нэг хязгаарлалт бий: decorator хэлбэрт as байхгүй тул Жишээ 1-ийн result dict-д хандах боломжгүй, зөвхөн хэвлэлт харагдана.
Class уу, generator уу?
Хоёр хэлбэрийн аль нь хэзээ тохирох вэ? Энгийн "бэлдэх, хүлээх, цэвэрлэх" дарааллын хувьд @contextmanager бараг үргэлж товч, уншигдахуйц: бүх логик нэг функцэд дарааллаар нь бичигддэг. Class хэлбэр гурван тохиолдолд илүү. Нэгдүгээрт, context manager нэмэлт method-уудтай байх ёстой бол, жишээлбэл Timer-т reset() эсвэл хэд хэдэн хэмжилтийн дундаж. Хоёрдугаарт, ижил объектыг давтан хэрэглэх бол: Хичээл 6.2-ын Жишээ 1-д нэг Timer объект loop дотор гурван удаа ашиглагдсан. Generator хэлбэрийн context manager объект нэг удаагийнх, учир нь доторх generator нэг удаагийн машин, Бүлэг 1-ийн дүрэм. Гуравдугаарт, алдааны нарийн боловсруулалт хэрэгтэй бол __exit__-ийн гурван argument-ыг шууд харах илүү тодорхой. Бусад бүх тохиолдолд @contextmanager-ээс эхэл.
Хүсвэл Анхны жишээний try/finally-гүй timer-т yield-ийн өмнө ба дараа print нэмж, блок дотор алдаа шидээд аль хэвлэлт гарч, аль нь гарахгүйг өөрийн нүдээр харж болно. Энэ нь урхийг хамгийн тод харуулна. Сонирхвол Хичээл 3.4-ийн read_lines-тэй ижил санаатай opened(path) context manager бичиж, файлыг нээж, yield-ээр өгч, finally-д хааж, мөн нээх, хаах үед мэдээ хэвлэдэг болгож болно. Мөн timer-ийг decorator болгон Хичээл 5.4-ийн давхарлалттай хослуулж, @timer("гадна") ба @log_calls-ийн дарааллыг сольж хэвлэлтийн өөрчлөлтийг харах нь хоёр бүлгийг нэгтгэх сонирхолтой туршилт. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
Цэвэрлэгээ алдаа гарахад ажиллахгүй байх. Мессежгүй, энэ хичээлийн гол урхи. yield-ийг try/finally-гүйгээр бичихэд блок дотор алдаа гарвал yield-ийн дараах код ажиллахгүй. Шинж тэмдэг нь хэвийн үед бүх зүйл зөв, харин алдааны дараа файл нээлттэй үлдэх, тохиргоо сэргээгдэхгүй байх, хэмжилт хэвлэгдэхгүй байх. Шийдэл: цэвэрлэгээ хийдэг бүх @contextmanager-т try: yield ... finally: цэвэрлэгээ.
RuntimeError: generator didn’t yield. “Generator yield хийсэнгүй” гэсэн утгатай. @contextmanager-тай функц yield-д хүрэлгүйгээр дуусахад гарна. Ихэвчлэн yield-ийг мартсан, эсвэл if нөхцөл дотор байгаа yield ажиллаагүй үед. Context manager __enter__ дотор next() дуудахад шууд StopIteration ирж, "хаалга" олдсонгүй. Функцийн бүх замд яг нэг yield байгаа эсэхийг шалгаарай.
RuntimeError: generator didn’t stop. “Generator зогссонгүй” гэсэн утгатай. Эсрэг асуудал: generator хоёр дахь yield-д хүрсэн. Ихэвчлэн yield-ийг loop дотор бичихэд гарна. __exit__ машиныг дуусгах гэж next() дуудахад тэр дуусахын оронд дахин зогссон. Context manager-ийн generator яг нэг удаа yield хийх ёстой: нэг хаалга, нэг блок. Хэрэв олон утга дарааллаар гаргах шаардлагатай бол энэ нь context manager биш, Бүлэг 2-ын ердийн generator.
TypeError: ‘function’ object does not support the context manager protocol. “function объект context manager протоколыг дэмждэггүй” гэсэн утгатай. timer-ийг хаалтгүй бичсэн үед гарна: with timer:. timer бол context manager биш, context manager үйлдвэрлэдэг функц. Хичээл 6.1-ийн with open as f алдаатай ижил. Анхдагч тохиргоогоор ч хоосон хаалттай бичнэ: with timer():.
Нэг context manager объектыг давтан ашиглах үед алдаа гарах. t = timer("Давталт") гэж нэг удаа үүсгээд with t:-ийг хоёр удаа бичвэл хоёр дахь with алдаа өгнө. Мессеж Python-ий хувилбараас хамааран ялгаатай. Шалтгаан нь доторх generator эхний удаад дууссан бөгөөд дахин эхлүүлэх боломжгүй. Шийдэл нь with бүрт шинэ дуудлага хийх: with timer("Давталт"):, эсвэл давтан хэрэглэх шаардлагатай бол Хичээл 6.2-ын class хувилбарыг ашиглах.
contextlib.contextmanager decorator нь яг нэг yield агуулсан generator функцийг context manager болгон хувиргадаг. yield-ээс өмнөх код __enter__-ийн, yield-ийн дараах код __exit__-ийн үүргийг гүйцэтгэдэг. yield-ийн гаргасан утга as-ийн нэрэнд очно. Хоёрын хооронд generator хөлдсөн байх үед with блок ажилладаг: yield нь хаалга. Төлөв self дээр биш, хөлдсөн frame-д хадгалагдана. Блок дотор алдаа гарвал context manager түүнийг generator-ийн yield мөрөн дээр шиддэг, тиймээс yield-ийг try/finally-д хийхгүй бол yield-ийн дараах цэвэрлэгээ ажиллахгүй. Энэ бол гол урхи. try/except-ээр алдааг барьж, дахин шидэхгүй бол алдаа залгигдана. Хэрэглэгчид үр дүн өгөхийн тулд өөрчлөгдөж болох объект yield хийж, дараа нь дотрыг нь дүүргэнэ. @contextmanager-ийн бүтээсэн context manager-ийг decorator болгон ч хэрэглэж болно. Энгийн тохиолдолд generator хэлбэр, нэмэлт method, давтан хэрэглээ хэрэгтэй бол class хэлбэр.
Ингээд Түвшин 2-ын бүх хэрэгсэл бидний гарт байна: closure, decorator бүх хэлбэрээрээ, wraps, context manager хоёр хэлбэрээрээ. Дараагийн хичээл бол Түвшин 2-ын хамтарсан бүтээл. Бид жижиг хэрэгслийн сан бүтээнэ: хугацаа хэмжих decorator, хугацаа хэмжих context manager, мөн argument-тай retry decorator, бүгд wraps-тай, бүгд pytest тесттэй. Ажлыг Git branch дээр хийнэ. Энэ бүтээлийн төгсгөлд @ ба with тэмдгүүд таных болно: та тэдгээрийг хэрэглэхээс гадна, бусдад зориулж бүтээдэг болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.