Ачааллж байна...
Өмнөх хоёр хичээлд бид decorator-ыг бүрэн бүтээж, wraps-ээр мэргэжлийн түвшинд хүргэсэн. Тэр decorator-ууд бүгд ижил хэлбэртэй байсан: @log_calls, @timed, @memo, хаалтгүй, тохиргоогүй. Гэтэл бид хоёр удаа өөр хэлбэртэй decorator хэрэглэсэн: Хичээл 3.3-т @lru_cache(maxsize=None), Хичээл 5.2-т @wraps(func). Хаалттай, argument-тай. Эдгээр нь decorator-т тохиргоо өгөх боломжийг олгодог. Энэ хичээлд бид ийм decorator-ыг өөрсдөө бүтээнэ.
Эхлээд шударгаар хэлье. Энэ бол энэ сургалтын хамгийн хэцүү хичээлүүдийн нэг. Argument-тай decorator гурван давхар үүрлэсэн функцтэй бөгөөд гурван өөр цагт ажилладаг. Туршлагатай Python хөгжүүлэгчид ч ийм decorator бичихдээ ихэвчлэн загварыг харж бичдэг, бүтцийг цээжээр гаргадаггүй. Хэрэв энэ хичээлийн дунд толгой эргэж эхэлбэл энэ нь та буруу зүйл хийж байгааг биш, сэдвийн жинхэнэ хүндийг илтгэнэ. Бид маш удаан явна: эхлээд танил хоёр давхар хувилбарыг бүтээгээд, дараа нь гурав дахь давхаргыг нэмэх нь яагаад хэрэгтэйг тайлбарлана.
Бидний бүтээх decorator бол retry: функц алдаа шидвэл түүнийг тохируулсан тоогоор дахин оролддог. Энэ бол бодит програмд маш түгээмэл хэрэгсэл. Сүлжээгээр өгөгдөл татах, өгөгдлийн санд холбогдох, гадны API дуудах зэрэг үйлдлүүд түр зуурын шалтгаанаар бүтэлгүйтэх нь элбэг, ихэнхдээ хоёр дахь оролдлогоор амжилттай болдог. Хичээлийн төгсгөлд бид @retry(times=3) гэж бичиж чаддаг болох бөгөөд энэ бичлэгийн ард юу болдгийг бүрэн ойлгоно.
Бэлэг боох үйлчилгээний цэгийг төсөөлөөд үзээрэй. Тэнд том боолтын машин бий. Та бэлгээ шууд машинд хийхгүй, эхлээд ажилтанд тохиргоогоо хэлнэ: "цэнхэр цаас, алтан тууз". Ажилтан машиныг тохируулж, тэр тохиргоогоор ажилладаг машиныг танд өгнө. Дараа нь л та бэлгээ машинд хийж, машин түүнийг боож өгнө. Эцэст нь найз тань бэлгийг хүлээн авч задална.
Энд гурван өөр мөч байна. Тохиргооны мөч: та өнгө, туузаа сонгож, тохируулсан машин авна. Боолтын мөч: машин бэлгийг боож өгнө. Задлах мөч: найз бэлэгтэй харилцана. Тохиргоо нэг удаа хийгдэнэ, гэхдээ тохируулсан машинаар та хэдэн ч бэлэг боож болно. Тэр бүр цэнхэр цаас, алтан тууз байна.
Өмнөх хичээлүүдийн decorator-т тохиргооны мөч байгаагүй: машин үргэлж ижил цаасаар боодог байсан. Argument-тай decorator тэр мөчийг нэмнэ. Гурван мөч бол гурван давхар функц, давхарга бүр өөрийн цагт ажиллаж, өөрийн үүргэвчтэй. Энэ хичээлийн туршид "энэ аль мөч вэ?" гэсэн асуултыг байнга тавьж явна.
Retry-г туршихын тулд эхний хэдэн удаа бүтэлгүйтэж, дараа нь амжилттай болдог функц хэрэгтэй. Бодит сүлжээг ашиглах нь тогтворгүй тул ийм функцийг Бүлэг 4-ийн мэдлэгээр бүтээе: closure тоолуур.
python
def make_flaky(fail_times):
calls = 0
def fetch_data():
nonlocal calls
calls += 1
if calls <= fail_times:
raise ConnectionError(f"сүлжээ тасарлаа ({calls}-р дуудлага)")
return "өгөгдөл ирлээ"
return fetch_data
fetch = make_flaky(2)
for _ in range(3):
try:
print(fetch())
except ConnectionError as error:
print("Алдаа:", error)Алдаа: сүлжээ тасарлаа (1-р дуудлага)
Алдаа: сүлжээ тасарлаа (2-р дуудлага)
өгөгдөл ирлээЭнэ бол Хичээл 4.2-ын тоолуур, нэг нэмэлттэй. fetch_data дуудагдах бүрт calls нэмэгдэнэ. calls нь fail_times-аас бага эсвэл тэнцүү бол ConnectionError шиднэ. ConnectionError бол сүлжээний холболтын асуудлыг илэрхийлдэг Python-ий бэлэн exception. fail_times = 2 тул эхний хоёр дуудлага бүтэлгүйтэж, гурав дахь нь амжилттай болсон. Бидний retry яг энэ давталтыг автоматаар хийх ёстой.
Эхлээд үргэлж гурван удаа оролддог, тохиргоогүй хувилбар бичье. Энэ нь Хичээл 5.2-ын мэргэжлийн араг ястай яг ижил бүтэцтэй.
python
from functools import wraps
def retry(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, 4):
try:
return func(*args, **kwargs)
except Exception as error:
last_error = error
print(f" {attempt}-р оролдлого бүтэлгүйтэв: {error}")
raise last_error
return wrapperwrapper-ийн дотрыг уншъя. for loop 1, 2, 3 гэж гурван удаа давтана. Оролдлого бүрт try дотор анхны функцийг дуудна. Хэрэв амжилттай бол return тэр даруй үр дүнг буцааж, loop ба wrapper хоёулаа дуусна: дахин оролдох шаардлагагүй. Хэрэв алдаа гарвал except түүнийг барьж, мэдээ хэвлээд, loop дараагийн оролдлого руу шилжинэ. except Exception нь бараг бүх энгийн алдааг барина. Гурван оролдлого бүгд бүтэлгүйтвэл loop дуусч, хамгийн сүүлийн алдааг raise last_error-оор дахин шиднэ. Ингэснээр дуудагч тал жинхэнэ шалтгааныг харна.
last_error гэсэн тусдаа хувьсагч яагаад хэрэгтэй вэ? Python нэг нарийн дүрэмтэй: except ... as error блок дуусахад error нэрийг автоматаар устгадаг. Тэгэхээр loop-ийн дараа шууд raise error гэж бичвэл алдаа гарна. Үүнийг Түгээмэл алдаанууд хэсэгт уншина. Тиймээс алдааг блок дотор тусдаа нэрэнд хадгалсан.
Шалгая. Хичээл 5.1-ийн дагуу decorator-ыг гараар хэрэглэж болно, make_flaky-гийн буцаасан функцэд @ бичих def мөр байхгүй тул энэ нь яг тохиромжтой:
python
fetch = retry(make_flaky(2))
print(fetch()) 1-р оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (1-р дуудлага)
2-р оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (2-р дуудлага)
өгөгдөл ирлээRetry ажиллаж байна. Нэг дуудлага fetch() дотроо гурван удаа оролдож, гурав дахь дээр амжилттай болсон.
Одоо асуудал: гурав гэдэг тоо кодонд хатуу бичигдсэн. Нэг функцэд хоёр оролдлого хангалттай, нөгөөд таван оролдлого хэрэгтэй байж болно. Бид @retry(times=5) гэж бичмээр байна. Гэвч одоогийн retry нэг л argument авдаг: func. Тохиргоог хаана өгөх вэ?
Хичээл 5.1-ийн томьёог @retry(times=5)-д хэрэглэе. @ мөрийн ард ямар ч илэрхийлэл бичиж болно, Python түүнийг эхлээд бодож, үр дүнг нь decorator болгон хэрэглэдэг. Тэгэхээр:
python
@retry(times=5)
def fetch_data():
...нь дараахтай яг ижил:
python
def fetch_data():
...
fetch_data = retry(times=5)(fetch_data)Хоёр дахь хэлбэрийн сүүлийн мөрийг анхааралтай уншъя. Тэнд хоёр дуудлага дараалан байна. Эхлээд retry(times=5) дуудагдана. Түүний үр дүн ямар нэг зүйл бөгөөд тэр зүйлийг (fetch_data) гэж функцээр дуудна. Тэгэхээр retry(times=5)-ийн үр дүн decorator байх ёстой: функц хүлээн авч wrapper буцаадаг зүйл. Үүнийг хоёр алхам болгон бичвэл илүү тодорхой:
python
decorator = retry(times=5)
fetch_data = decorator(fetch_data)Энэ бол хичээлийн гол санаа. Argument-тай decorator гэдэг нь үнэндээ decorator үйлдвэрлэдэг функц. retry тохиргоо авч, тохируулсан decorator буцаана. Тэр decorator функц авч, wrapper буцаана. Тэр wrapper дуудагдах бүрт ажиллана. Хичээл 4.3-ын тохируулсан форматлагчийг санаарай: make_money_formatter тохиргоо авч, тохируулсан функц буцаадаг байсан. Энд яг ижил санаа, зөвхөн буцаах функц нь өөрөө decorator. Хоёр танил загвар нийлж байна: function factory ба decorator.
Одоо бүтээе. Алхам 2-ын хоёр давхар retry-г бүтнээр нь авч, түүнийг тохиргоо хүлээн авдаг шинэ гадна функцэд хийнэ. Хуучин retry-ийн нэрийг decorator гэж өөрчилнө, 4 гэсэн хатуу тоог times + 1 болгоно.
python
from functools import wraps
def retry(times=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except Exception as error:
last_error = error
print(f" {attempt}/{times} оролдлого бүтэлгүйтэв: {error}")
raise last_error
return wrapper
return decoratorГурван давхаргыг гаднаас дотогш тодорхойлъё.
Гадна давхарга, retry(times). Тохиргоо авна. Бүх ажил нь decorator-ыг тодорхойлж буцаах. Энэ бол зүйрлэлийн "ажилтан машиныг тохируулах".
Дунд давхарга, decorator(func). Функц авна. Ажил нь wrapper-ийг тодорхойлж буцаах. Энэ бол Алхам 2-ын retry, бараг үгчлэн. "Машин бэлгийг боох".
**Дотоод давхарга, wrapper(*args, kwargs). Дуудлага бүрийн argument-ыг авна. Дахин оролдох жинхэнэ ажлыг хийнэ. "Найз бэлгийг задлах".
Давхарга бүрийн төгсгөлд return байгааг анзаараарай: decorator wrapper-ийг буцаана, retry decorator-ыг буцаана. Хаалтгүй, функцийг утга болгон. @wraps(func) хэвээр wrapper-ийн яг дээр байна. wrapper-ийн дотор хоёр гадаад нэр хэрэглэгдэж байна: func ба times. func нь нэг шат гадна, decorator-ийн гэрт. times нь хоёр шат гадна, retry-ийн гэрт. LEGB-ийн Enclosing түвшин олон давхар байж болно гэж Хичээл 4.1-д хэлснийг санаарай: Python дотроос гадагш шат шатаар хайдаг.
Одоо @-тэйгээр хэрэглэе:
python
@retry(times=4)
def unstable_download():
return fetch_once()
fetch_once = make_flaky(3)
print(unstable_download()) 1/4 оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (1-р дуудлага)
2/4 оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (2-р дуудлага)
3/4 оролдлого бүтэлгүйтэв: сүлжээ тасарлаа (3-р дуудлага)
өгөгдөл ирлээunstable_download нь make_flaky-гийн буцаасан fetch_once-ийг дотроо дууддаг энгийн функц. Ингэснээр @ бичих def мөртэй болсон. fetch_once-ийг unstable_download-ийн дараа тодорхойлсон нь асуудалгүй, учир нь функцийн бие зөвхөн дуудагдах үед ажилладаг, тэр үед fetch_once аль хэдийн бий. Гурван удаа бүтэлгүйтэх функцийг дөрвөн оролдлоготой retry дөрөв дэх дээр амжилттай болгосон.
Одоо хичээлийн хамгийн чухал хэсэг. Доорх кодыг гурван цагт хувааж, алхам бүрийг мөрдье.
python
@retry(times=3)
def fetch():
return fetch_once()Тохиргооны цаг
Алхам 1 (retry(times=3) дуудагдах). Python @ мөрийн илэрхийллийг эхэлж бодно. retry-ийн гэр үүснэ. retry-ийн гэрт: times = 3.
Алхам 2 (def decorator ажиллах). decorator функц төрнө. Python түүний кодыг шинжилнэ: decorator өөрөө times-ийг шууд хэрэглэхгүй ч дотор нь тодорхойлогдох wrapper times-ийг хэрэглэдэг. Тиймээс times-ийн хайрцаг decorator-ын үүргэвчинд ч орно, ингэснээр wrapper дараа нь түүнийг авах боломжтой. decorator-ын үүргэвчинд: times хайрцаг, дотор нь 3.
Алхам 3 (return decorator). retry дуусч, гэр нь хаагдана. times-ийн хайрцаг decorator-ын үүргэвчинд амьд үлдэнэ. Энэ мөчид fetch хараахан тодорхойлогдоогүй ч бидэнд тохируулсан машин бий: үүргэвчиндээ “3” гэж бичигдсэн decorator.
Боолтын цаг
Алхам 4 (def fetch ба decorator-т өгөгдөх). Python анхны fetch функцийг үүсгээд, Алхам 3-ын decorator-т өгнө. decorator-ын гэр үүснэ. decorator-ын гэрт: func = анхны fetch. Үүргэвчинд: times = 3.
Алхам 5 (def wrapper ажиллах). wrapper төрнө. Түүний кодонд func ба times хоёулаа гадаад нэр. Python хоёр хайрцгийг wrapper-ийн үүргэвчинд хийнэ: func-ийн хайрцгийг decorator-ын гэрээс, times-ийн хайрцгийг decorator-ын үүргэвчнээс. Энэ нь retry-ийн гэрт анх үүссэн ижил хайрцаг. Дараа нь @wraps(func) wrapper-ийн шошгыг fetch-ийнхээр солино. wrapper-ийн үүргэвчинд: func = анхны fetch, times = 3.
Алхам 6 (return wrapper, нэрийн хавтан). decorator дуусна. wrapper fetch нэрэнд оноогдоно.
Үүргэвчийг нээж баталъя:
python
print(fetch.__name__)
print(fetch.__code__.co_freevars)
print([cell.cell_contents for cell in fetch.__closure__])fetch
('func', 'times')
[<function fetch at 0x7f4a2c1d3e20>, 3]wraps-ийн ачаар нэр fetch. __code__ нь wrapper-ийн код хэвээр тул түүний гадаад нэрс func ба times. Хайрцгуудыг нээхэд анхны fetch ба 3 гарч ирлээ. Тохиргооны цагийн times ба боолтын цагийн func хоёулаа нэг үүргэвчинд нийлсэн.
Дуудлагын цаг
Алхам 7 (fetch() дуудагдах). wrapper-ийн гэр үүснэ. Түүний гэрт: args = (), kwargs = {}, last_error = None. range(1, times + 1)-ийн times-ийг үүргэвчнээс олж 3 гэж уншина.
Алхам 8 (оролдлого бүр). attempt = 1: func-ийг үүргэвчнээс олж дуудна. Алдаа гарвал last_error-т хадгалж, хэвлэнэ. attempt = 2, 3 ижил. Аль нэг нь амжилттай бол return тэр даруй үр дүнг буцаана.
Алхам 9 (бүх оролдлого бүтэлгүйтвэл). loop дуусч, raise last_error сүүлийн алдааг дуудагч руу шиднэ.
Гурван цагийг нэгтгэвэл: тохиргооны цагт retry нэг удаа ажиллаж, times-ийг барьсан decorator үйлдвэрлэнэ. Боолтын цагт decorator функц бүрт нэг удаа ажиллаж, func ба times-ийг барьсан wrapper үйлдвэрлэнэ. Дуудлагын цагт wrapper дуудлага бүрт ажиллаж, хоёр хайрцгаа ашиглана. Эхний хоёр цаг файл импортлогдох үед, хоёулаа @ мөрөнд нэгэн зэрэг өнгөрдөг тул нүдэнд нэг мөч мэт харагддаг. Гэвч тэдгээр нь хоёр тусдаа дуудлага бөгөөд үүнийг дараагийн жишээ тод харуулна.
Жишээ 1: тохируулсан decorator-ыг дахин ашиглах
Тохиргооны цаг ба боолтын цаг тусдаа гэдгийн хамгийн тод нотолгоо: тэдгээрийг хооронд нь салгаж болно. Тохируулсан decorator-ыг нэг удаа үүсгээд, олон функцэд хэрэглэе.
python
network_retry = retry(times=5)
@network_retry
def download_report():
return "тайлан татагдлаа"
@network_retry
def sync_users():
return "хэрэглэгчид шинэчлэгдлээ"
print(download_report(), "|", sync_users())тайлан татагдлаа | хэрэглэгчид шинэчлэгдлээnetwork_retry = retry(times=5) бол зөвхөн тохиргооны цаг: decorator үйлдвэрлэгдэж, нэрэнд хадгалагдсан. Ямар ч функц хараахан боогдоогүй. Дараа нь @network_retry гэж хаалтгүй бичсэн, учир нь network_retry аль хэдийн бэлэн decorator. Боолтын цаг функц бүрт тусдаа болсон. Хоёр функц тус тусдаа wrapper, тус тусдаа func хайрцагтай ч times-ийн нэг хайрцгийг хуваалцаж байна: хоёулаа network_retry-ийн үүргэвчнээс авсан. Энэ бол бодит төслүүдэд түгээмэл загвар: "манай сүлжээний бүх дуудлага таван оролдлоготой" гэдгийг нэг газарт тодорхойлж, тэр тохиргоог олон газарт хэрэглэх. Хэрэв дараа нь тоог өөрчлөх шаардлага гарвал ганц мөр засна.
Жишээ 2: олон тохиргоо — хүлээх хугацаа ба алдааны төрөл
Бодит retry ихэвчлэн хоёр нэмэлт тохиргоотой байдаг. Нэгдүгээрт, оролдлогын хооронд хүлээх хугацаа: сүлжээ тасрахад шууд дахин оролдох нь ихэнхдээ дэмий, хэсэг хүлээх нь сервер сэргэх боломж олгоно. Хоёрдугаарт, ямар алдаанд дахин оролдох вэ: сүлжээний алдаа түр зуурынх, харин TypeError бол кодын алдаа бөгөөд мянга дахин оролдсон ч засагдахгүй. Тохиргоог өргөтгөе:
python
import time
from functools import wraps
def retry(times=3, delay=0.0, exceptions=(Exception,)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions as error:
last_error = error
print(f" {attempt}/{times}: {error}")
if attempt < times:
time.sleep(delay)
raise last_error
return wrapper
return decoratorГурван өөрчлөлт бий. Гадна давхарга одоо гурван тохиргоо авна, бүгд анхдагч утгатай. except exceptions as error нь зөвхөн exceptions tuple-д байгаа төрлийн алдааг барина. Бусад алдаа барьдгүй, шууд дуудагч руу өнгөрнө. time.sleep(delay) оролдлогын хооронд хүлээнэ, гэхдээ сүүлийн оролдлогын дараа хүлээх утгагүй тул нөхцөлтэй. Гурван тохиргоо бүгд wrapper-ийн үүргэвчинд орно. Бүтэц огт өөрчлөгдөөгүй, зөвхөн гадна давхаргын argument нэмэгдсэн.
Хүлээх хугацааг хэмжилтээр шалгая:
python
@retry(times=3, delay=0.5, exceptions=(ConnectionError,))
def fetch():
return fetch_once()
fetch_once = make_flaky(2)
start = time.perf_counter()
print(fetch())
print(f"Нийт хугацаа: {time.perf_counter() - start:.2f} секунд") 1/3: сүлжээ тасарлаа (1-р дуудлага)
2/3: сүлжээ тасарлаа (2-р дуудлага)
өгөгдөл ирлээ
Нийт хугацаа: 1.00 секундХоёр бүтэлгүйтэл, тус бүрийн дараа 0.5 секунд хүлээлт, нийт 1.00 секунд орчим. Таны үр дүн хэдэн мянганы нэгээр зөрж болно. Одоо алдааны төрлийн шүүлтийг шалгая. ConnectionError биш алдаа шиддэг функц:
python
@retry(times=3, exceptions=(ConnectionError,))
def parse_amount(text):
return int(text)
print(parse_amount("abc"))Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'abc'Нэг ч "оролдлого бүтэлгүйтэв" мөр хэвлэгдсэнгүй. ValueError exceptions tuple-д байхгүй тул except түүнийг барьсангүй, алдаа эхний оролдлогоос шууд гадагш гарсан. “abc”-ийг тоо болгох гэж гурван удаа оролдох нь гурван ижил алдаа гаргахаас өөр юу ч хийхгүй байсан. Энэ бол retry-ийн мэргэжлийн хэрэглээний гол дүрэм: зөвхөн түр зуурын, дахин оролдоход засагдаж болох алдаанд дахин оролд.
Жишээ 3: бэлэн decorator-уудын бүтцийг таних
Одоо та Python-ий бэлэн argument-тай decorator-уудын бүтцийг таних боломжтой боллоо. @lru_cache(maxsize=None) бол retry-тэй ижил гурван давхар: lru_cache(maxsize=None) тохиргоо авч decorator буцаадаг, тэр decorator fib-ийг авч кэштэй wrapper буцаадаг. @wraps(func) ч гурван давхар загварын ах дүү: wraps(func) "анхны функц" гэсэн тохиргоо авч, wrapper-ийн шошгыг солих decorator буцаадаг. Бид Хичээл 5.2-т “wrapper = wraps(func)(wrapper)” гэж задалсан нь яг энэ хичээлийн retry(times=3)(fetch) томьёо.
Course 3-ын FastAPI-д @app.get("/users/{user_id}") гэж бичсэнээ санаарай. Энэ ч мөн argument-тай decorator: app.get("/users/{user_id}") тохиргоо буюу URL замыг авч, функцийг тэр замд бүртгэх decorator буцаадаг. Дотор нь нэмэлт нарийн ширийн зүйл бий ч араг яс нь яг энэ хичээлийнх. Одоо та тэр мөрийг харахад "энэ гурван цагт ажилладаг" гэдгийг мэдэж байна.
Хүсвэл retry-д backoff гэсэн дөрөв дэх тохиргоо нэмж, оролдлого бүрийн дараа хүлээх хугацааг үржүүлдэг болгож болно: delay=0.2, backoff=2 бол 0.2, 0.4, 0.8 секунд. Энэ нь бодит системүүдэд “exponential backoff” нэрээр өргөн хэрэглэгддэг арга бөгөөд нийт хугацааг perf_counter-оор хэмжиж шалгаж болно. Сонирхвол Хичээл 5.1-ийн log_calls-ийг argument-тай болгож, @log_calls(prefix="[API]") гэж бичиж болдог болгох нь гурван давхар бүтцийг өөрийн гараар нэг удаа бичих сайн дасгал. Мөн Trace-ийн Алхам 2-т тайлбарласан decorator-ын үүргэвчийг retry(times=3).__code__.co_freevars-аар шалгаж, times тэнд байгааг өөрийн нүдээр харж болно. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
TypeError: retry.<locals>.decorator() missing 1 required positional argument: ‘func’. “retry-ийн локал decorator()-д шаардлагатай ‘func’ argument дутуу байна” гэсэн утгатай. Argument-тай decorator-ыг хаалтгүй бичсэн үед гарна: @retry. Trace хийе: хаалтгүй бол Python fetch функцийг шууд retry-д өгнө, тэгэхээр times = fetch болно. retry decorator-ыг буцааж, тэр fetch нэрэнд оноогдоно. Дараа нь fetch() дуудахад decorator-ыг argument-гүйгээр дуудна, тэр func хүлээж байна. Алдаа функцийг дуудах үед гарч, мессеж нь decorator-ын тухай ярина. Энэ нь Хичээл 5.1-ийн “@log_calls()” алдааны эсрэг тал. Дүрэм: argument-тай decorator-ыг анхдагч тохиргоогоор хэрэглэх бол ч хоосон хаалттай бич: @retry().
TypeError: ‘NoneType’ object is not callable. Гурван давхар бүтцэд хоёр return байх ёстой бөгөөд аль нэгийг нь мартвал энэ алдаа гарна. Хэрэв retry-ийн төгсгөлд return decorator мартвал retry(times=3) None буцааж, Python тэр None-оор функцийг боох гэж оролдоно. Алдаа тодорхойлох цагт, @ мөрөнд, файл импортлогдох мөчид гарна. Хэрэв decorator-ын төгсгөлд return wrapper мартвал алдаа функцийг анх дуудах үед гарна. Traceback-ийн мөрийн дугаар хоёрыг ялгахад тусална: @ мөр бол гадна давхарга, дуудлагын мөр бол дунд давхарга.
UnboundLocalError: cannot access local variable ‘error’ where it is not associated with a value. "Утга холбогдоогүй локал хувьсагч ‘error’-д хандах боломжгүй" гэсэн утгатай. Хичээл 4.2-ын танил алдаа, гэвч энэ удаа өөр шалтгаантай. last_error-ийн оронд loop-ийн дараа шууд raise error гэж бичсэн үед гарна. Python except ... as error блок дуусахад error нэрийг санаатайгаар устгадаг. Ингэснээр алдаа ба түүний traceback санах ойд шаардлагагүй удаан үлдэхээс сэргийлдэг. Нэр устсан тул loop-ийн дараа хандахад тэр локал хувьсагч утгагүй байна. Шийдэл нь хичээлийн загвар: блок дотор алдааг тусдаа нэрэнд хадгалах.
Retry огт ажиллахгүй, алдаа шууд гарах. Мессеж нь анхны функцийн алдаа, жишээлбэл ValueError, retry-ийн ямар ч хэвлэлтгүйгээр. Шалтгаан нь Жишээ 2-ын шүүлт: шидэгдсэн алдааны төрөл exceptions tuple-д байхгүй. Хэрэв энэ таны санаа байсан бол бүх зүйл зөв. Хэрэв санамсаргүй бол tuple-д шаардлагатай төрлийг нэмнэ. Ганц төрөл өгөхдөө ч tuple болгох хэрэгтэйг анхаар: exceptions=(ConnectionError,), таслалтай. exceptions=ConnectionError гэж ганц класс өгсөн ч Python-ий except түүнийг хүлээн авдаг тул ажиллана. Харин exceptions=[ConnectionError] гэж list өгвөл except list хүлээн авдаггүй тул TypeError гарна.
Argument-тай decorator бол decorator үйлдвэрлэдэг функц: @retry(times=3) нь fetch = retry(times=3)(fetch) гэсэн утгатай. Энэ нь гурван давхар бүтэц шаарддаг. Гадна давхарга тохиргоо авч decorator буцаана, дунд давхарга функц авч wrapper буцаана, дотоод давхарга дуудлага бүрт жинхэнэ ажлыг хийнэ. Гурван давхарга гурван цагт ажилладаг: тохиргооны цаг, боолтын цаг, дуудлагын цаг. Эхний хоёр @ мөрөнд нэгэн зэрэг өнгөрдөг тул нэг мөч мэт санагдах ч тусдаа. Тохиргоо ба func хоёулаа wrapper-ийн үүргэвчинд нийлдэг бөгөөд үүнийг __closure__-аар шалгаж болно. Тохируулсан decorator-ыг нэрэнд хадгалж, олон функцэд хэрэглэх боломжтой. Retry-ийн мэргэжлийн хэрэглээ хүлээх хугацаа ба алдааны төрлийн шүүлтийг шаарддаг. lru_cache, wraps, FastAPI-ийн app.get бүгд энэ бүтэцтэй. Энэ бүтэц хэцүү бөгөөд үүнийг хэд хэдэн удаа эргэж харах нь хэвийн.
Одоо бид decorator-ыг бүх хэлбэрээр нь бичиж чаддаг боллоо. Дараагийн хичээлд бүлгийг хоёр сэдвээр дуусгана. Нэгдүгээрт, нэг функц дээр хэд хэдэн decorator давхарлавал юу болох вэ, тэдгээр ямар дарааллаар боож, ямар дарааллаар ажилладаг вэ? Хичээл 5.2-ын бүртгэлийн системд бид хоёр decorator давхарласан бөгөөд одоо түүнийг trace-ээр бүрэн ойлгоно. Хоёрдугаарт, бид Python-ий хуучин танилууд болох lru_cache, property, dataclass-тай дахин уулзаж, тэдгээрийг одоо дотроос нь харах болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.