Ачааллж байна...
Энэ сургалтын туршид бид олон exception шидсэн: оноо хүрээнээс гарахад ValueError, сонсогч дуудагдах боломжгүй үед TypeError, хоосон овоолгоос авахад IndexError. Python-ий бэлэн exception-ууд энгийн тохиолдолд хангалттай. Гэвч програм томрох тусам тэдгээр хэтэрхий ерөнхий болдог. Онлайн дэлгүүрийн захиалгын код ValueError шидлээ гэж бодъё. Энэ нь хэрэглэгч буруу тоо оруулсан уу? Агуулахад бараа дууссан уу? Төлбөрийн систем татгалзсан уу? Эсвэл бидний кодонд алдаа гарсан уу? Хэрэв бүгд ValueError бол дуудагч тал тэдгээрийг ялгах ганц арга нь мессежийн текстийг унших, энэ бол хэврэг, найдваргүй арга.
Энэ бүлэг exception дизайн-д зориулагдсан: алдаануудыг програмын нэг хэсэг болгон, бусад код шиг төлөвлөх. Эхний хичээл түүний суурь: өөрийн exception шатлал. Жижиг програмд зориулж нэг эх exception class тодорхойлж, түүнээс тодорхой алдааны төрлүүдийг удамшуулна. Үр дүн нь алдааны гэр бүлийн мод: дээд түвшинд "дэлгүүрийн аливаа алдаа", доор нь "агуулахын алдаа", "төлбөрийн алдаа", тэдгээрийн доор илүү нарийн төрлүүд. Дуудагч тал аль түвшинд барихаа өөрөө сонгоно.
Хичээлийн хоёр дахь чухал сэдэв бол шударга зөвлөмж: юуг барих вэ. Python-д except Exception: эсвэл бүр except: гэж бүх алдааг нэг дор барих боломжтой бөгөөд энэ уруу таталт хүчтэй: програм "хэзээ ч унахгүй". Гэвч энэ нь бидний кодын жинхэнэ алдааг, үсгийн алдаа, None-ийн attribute, хэрэглэгчийн буруу оролтын хамт нуудаг. Бид үүнийг санаатайгаар харуулж, өөрийн алдаануудыг барих нь яагаад бүх зүйлийг барихаас илүүг ойлгоно. Хичээл 9.4-ийн event bus except Exception хэрэглэсэн, тэр хаана, яагаад зөв байсныг ч тодорхой болгоно.
Шуудангийн хаягийг төсөөлөөд үзээрэй: Улаанбаатар хот, Баянзүрх дүүрэг, 26-р хороо. Хаяг шатлалтай. Хотын захиргаа "Улаанбаатарын бүх иргэдэд" гэсэн мэдэгдэл явуулбал Баянзүрхийн 26-р хорооны иргэн түүнийг хүлээн авна. Дүүргийн захиргаа "Баянзүрхийн иргэдэд" гэвэл 26-р хорооны иргэн мөн хүлээн авна, харин Сүхбаатар дүүргийн иргэн хүлээн авахгүй. Хорооны мэдэгдэл зөвхөн тэр хороонд хүрнэ. Хаягийн хэсэг бүр өөр хүрээг заадаг бөгөөд нэг иргэн бүх дээд түвшиндээ харьяалагддаг.
Exception шатлал яг ийм. OutOfStockError бол хорооны түвшин: "агуулахад энэ бараа хүрэлцэхгүй". Түүний эх нь ShopError, хотын түвшин: "дэлгүүрийн аливаа алдаа". Хэрэв код except OutOfStockError: гэвэл зөвхөн нөөцийн алдааг барина. except ShopError: гэвэл дэлгүүрийн бүх алдааг, нөөцийн алдааг ч оролцуулан барина. Гэвч дэлгүүрийн гаднах алдааг, жишээлбэл бидний кодын үсгийн алдаанаас үүсэх NameError-ийг, барихгүй: тэр өөр хотын иргэн.
except Exception: бол "бүх улсын иргэдэд" гэсэн мэдэгдэл. Энэ нь заримдаа зөв: улсын баяр. Гэвч ихэнх тохиолдолд мэдэгдлийг зөвхөн хамааралтай хүмүүст явуулах нь зүйтэй, эс бөгөөс хүмүүс мэдэгдлийг тоохоо больж, жинхэнэ чухал мэдээ анзаарагдахгүй өнгөрнө.
Өөрийн шатлалыг бүтээхээс өмнө Python-ий бэлэн exception-ууд ч шатлалтай гэдгийг харъя. Хичээл 9.2-т бид class-ийн __mro__-ийн тухай шууд яриагүй ч Хичээл 7.3-т attribute хайлт эцэг class-уудын дагуу явдгийг харсан. __mro__ бол яг тэр дараалал: class өөрөө, дараа нь түүний эцэг, эцгийн эцэг, гэх мэт.
python
print(FileNotFoundError.__mro__)
print(KeyError.__mro__)
print(KeyboardInterrupt.__mro__)(<class 'FileNotFoundError'>, <class 'OSError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)
(<class 'KeyError'>, <class 'LookupError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)
(<class 'KeyboardInterrupt'>, <class 'BaseException'>, <class 'object'>)FileNotFoundError бол OSError-ийн хүүхэд, тэр нь Exception-ийн хүүхэд. Тэгэхээр except OSError: нь файл олдохгүй, эрхгүй, диск дүүрсэн зэрэг үйлдлийн системийн бүх алдааг барина. KeyError бол LookupError-ийн хүүхэд, IndexError-тэй ах дүү: except LookupError: хоёуланг нь барина. Python-ий шатлал энэ хичээлийн санааг аль хэдийн хэрэглэж байна.
Гурав дахь мөрийг анхааралтай хараарай: KeyboardInterrupt Exception-ийн хүүхэд биш, шууд BaseException-ийн хүүхэд. Хичээл 2.4-т Ctrl+C дарахад гардаг гэж харсан тэр алдаа. SystemExit, програмыг sys.exit()-ээр дуусгахад гардаг, ч мөн адил. Python-ий дизайнерууд тэдгээрийг санаатайгаар Exception-ий гадна байрлуулсан: тэд "алдаа" биш, "програмыг зогсоох хүсэлт". Ингэснээр except Exception: хэрэглэгчийн Ctrl+C-г залгиж, програмыг зогсоохоос сэргийлдэггүй. Энэ баримт Жишээ 4-т чухал болно. Өөрийн exception-ууд үргэлж Exception-ээс удамшина, BaseException-ээс хэзээ ч биш.
Хичээл 9.4-ийн дэлгүүрт зориулж алдааны шатлал бүтээе:
python
class ShopError(Exception):
"""Дэлгүүрийн бүх алдааны эх."""
class ValidationError(ShopError):
"""Хэрэглэгчийн оруулсан захиалга буруу."""
class OutOfStockError(ShopError):
"""Агуулахад хүссэн хэмжээгээр бараа байхгүй."""
def __init__(self, product, requested, available):
self.product = product
self.requested = requested
self.available = available
super().__init__(
f"'{product}': {requested} хүссэн, {available} үлдсэн"
)
class PaymentError(ShopError):
"""Төлбөрийн системтэй холбоотой алдаа."""
class PaymentDeclinedError(PaymentError):
"""Банк гүйлгээг татгалзсан. Дахин оролдох утгагүй."""
class PaymentTimeoutError(PaymentError):
"""Төлбөрийн систем хариу өгөөгүй. Дахин оролдож болно."""Зургаан class, дээд тал нь хэдэн мөр, ихэнх нь ердөө docstring-тэй. Тэдгээрийн бүтэц:
ShopError
├── ValidationError
├── OutOfStockError
└── PaymentError
├── PaymentDeclinedError
└── PaymentTimeoutErrorДизайны шийдвэр бүрийг тайлбарлая.
Нэг эх. ShopError нь Exception-ээс удамшдаг бөгөөд бусад бүх дэлгүүрийн алдаа түүнээс удамшдаг. Энэ нь хамгийн чухал шийдвэр: дуудагч тал "дэлгүүрийн аливаа алдаа"-г ганц нэрээр барих боломжтой болно, бидний кодын жинхэнэ алдааг барихгүйгээр.
Нэрс Error-оор төгсдөг. Энэ бол Python-ий хэв маягийн гарын авлага PEP 8-ын уламжлал. Нэр нь юу болсныг хэлдэг байх ёстой: OutOfStockError нь "нөөц дууссан алдаа", “ShopError2” биш.
Docstring бүрт. Ихэнх class-ууд docstring-ээс өөр бие байхгүй. Python-д class-ийн бие хоосон байж болохгүй, docstring энэ шаардлагыг хангаж, мөн алдаа хэзээ гардгийг, түүнийг барьсан код юу хийх ёстойг тайлбарлана. PaymentDeclinedError ба PaymentTimeoutError-ийн docstring-ууд дахин оролдох эсэхийн тухай чухал мэдээлэл агуулж байгааг анзаараарай.
Өгөгдөлтэй exception. OutOfStockError бусдаас ялгаатай: тэр __init__-тэй, бараа, хүссэн ба үлдсэн тоог attribute болгон хадгалдаг. Exception бол энгийн объект, Бүлэг 7-ын бүх мэдлэг түүнд хамаарна. Мессежийг super().__init__(...)-ээр эх class-д дамжуулсан: энэ мессеж str(error) ба traceback-ийн сүүлийн мөрөнд харагдана. Яагаад attribute хэрэгтэй вэ? Учир нь алдааг барьсан код "хэрэглэгчид үлдсэн тоог санал болгох" шаардлагатай болж магадгүй бөгөөд тэр тоог мессежийн текстээс задлах нь хэврэг. attribute-аас шууд унших нь найдвартай.
Хэр олон class вэ? Энэ бол шударга асуулт. Exception шатлалыг хэт нарийн, алдааны мөр бүрт тусдаа class болгох уруу таталт гардаг. Дүрэм: тусдаа class нь дуудагч тал түүнийг өөрөөр шийдэх шаардлагатай үед л хэрэгтэй. PaymentDeclinedError ба PaymentTimeoutError хоёр тусдаа, учир нь нэгэнд нь дахин оролдох утгатай, нөгөөд нь утгагүй. Хэрэв хоёр алдааг дуудагч тал үргэлж ижил шийддэг бол тэд нэг class байх ёстой.
Одоо захиалга өгөх функц ба түүнийг дуудагч кодыг бичье:
python
STOCK = {"Ном": 1, "Дэвтэр": 10}
def place_order(product, quantity):
if quantity <= 0:
raise ValidationError(f"Тоо ширхэг эерэг байх ёстой, {quantity} ирлээ")
available = STOCK.get(product, 0)
if available < quantity:
raise OutOfStockError(product, quantity, available)
STOCK[product] -= quantity
return f"{product} x{quantity} захиалагдлаа"
def handle(product, quantity):
try:
print(place_order(product, quantity))
except OutOfStockError as error:
print(f"Уучлаарай, {error.product} {error.available} ширхэг л үлдсэн.")
except ValidationError as error:
print(f"Захиалга буруу: {error}")
except ShopError as error:
print(f"Захиалга амжилтгүй: {error}")
handle("Ном", 5)
handle("Дэвтэр", 0)
handle("Дэвтэр", 2)Уучлаарай, Ном 1 ширхэг л үлдсэн.
Захиалга буруу: Тоо ширхэг эерэг байх ёстой, 0 ирлээ
Дэвтэр x2 захиалагдлааЭхний дуудлагыг алхам алхмаар мөрдье.
Алхам 1 (raise OutOfStockError("Ном", 5, 1)). place_order дотор шинэ OutOfStockError объект үүсч, __init__ attribute-уудыг хадгалж, мессежийг эх class-д дамжуулна. raise тэр объектыг шиднэ. place_order-ийн үлдсэн мөрүүд алгасагдана.
Алхам 2 (try блок руу буцах). Алдаа place_order-оос гарч, handle-ийн try блокт хүрнэ. Python except мөрүүдийг дээрээс доош нэг нэгээр шалгана.
Алхам 3 (эхний except). except OutOfStockError. Python асууна: алдааны объект OutOfStockError-ийн instance мөн үү? Өөрөөр хэлбэл isinstance(error, OutOfStockError). Тийм. Энэ except блок ажиллаж, бусад except-үүд огт шалгагдахгүй.
Хоёр дахь дуудлага: ValidationError шидэгдэнэ. Алхам 3: OutOfStockError мөн үү? Үгүй, тэд ах дүү, нэг нь нөгөөгийн удам биш. Дараагийн except: ValidationError мөн үү? Тийм, тэр блок ажиллана.
Одоо PaymentTimeoutError шидэгдсэн гэж бодъё. Эхний хоёр except таарахгүй. Гурав дахь: isinstance(error, ShopError)? PaymentTimeoutError-ийн __mro__ нь PaymentTimeoutError, PaymentError, ShopError, Exception, ... ShopError түүнд байгаа тул тийм. Энэ бол хаягийн зүйрлэлийн "хотын мэдэгдэл хорооны иргэнд хүрэх". handle төлбөрийн алдааг тусад нь мэддэггүй ч ShopError-ийн түвшинд түүнийг зөв барьсан.
Хэрэв place_order дотор бидний кодын алдаа, жишээлбэл үсгийн алдаанаас NameError гарвал? Гурван except бүгд таарахгүй, NameError ShopError-ийн удам биш. Алдаа handle-ээс гарч, traceback-аар програмыг зогсооно. Энэ бол зөв зан чанар: бидний алдааг бид засах ёстой, хэрэглэгчид "захиалга амжилтгүй" гэж харуулж нуух биш.
Нэг чухал дүгнэлт: except-ийн дараалал чухал. Python эхний таарсанд зогсдог тул нарийн төрлүүд эхэнд, ерөнхий нь сүүлд байх ёстой. Энэ дүрмийг зөрчвөл юу болохыг Түгээмэл алдаанууд хэсэгт харна.
Жишээ 1: шатлал ба retry
Хичээл 6.4-ийн measurekit-ийн retry decorator-ыг санаарай: тэр exceptions тохиргоотой байсан бөгөөд бид "зөвхөн түр зуурын алдаанд дахин оролд" гэсэн дүрмийг тогтоосон. Одоо энэ шатлалын ачаар тэр дүрэм нэрээр нь илэрхийлэгдэнэ:
python
from measurekit import retry
attempts = 0
@retry(times=3, exceptions=(PaymentTimeoutError,))
def charge(amount):
global attempts
attempts += 1
if attempts < 3:
raise PaymentTimeoutError("Банкны сервер хариу өгсөнгүй")
return f"{amount:,}₮ төлөгдлөө"
print(charge(50_800))charge: 1/3 оролдлого бүтэлгүйтэв: Банкны сервер хариу өгсөнгүй
charge: 2/3 оролдлого бүтэлгүйтэв: Банкны сервер хариу өгсөнгүй
50,800₮ төлөгдлөөglobal тоолуурыг энд товч байлгах үүднээс хэрэглэсэн, Хичээл 4.2-ын closure илүү цэвэр гэдгийг санаарай. retry зөвхөн PaymentTimeoutError-ийг дахин оролдсон. Хэрэв charge PaymentDeclinedError шидсэн бол retry түүнийг барихгүй, алдаа шууд гарна: банк татгалзсан гүйлгээг дахин оролдох нь хэрэглэгчийн картыг гурав дахин татгалзуулах л болно. Тусдаа class-ийн утга энд тод харагдаж байна: дуудагч тал, энэ тохиолдолд retry, хоёр алдааг өөрөөр шийдэх шаардлагатай байсан.
Жишээ 2: өөрийн алдаануудыг тестлэх
Course 4-ийн pytest-д pytest.raises тодорхой exception гарахыг шалгадаг. Өөрийн шатлалтай тестүүд илүү нарийн, илүү утгатай болно:
python
import pytest
def test_out_of_stock_reports_available_quantity():
STOCK["Ном"] = 1
with pytest.raises(OutOfStockError) as info:
place_order("Ном", 5)
assert info.value.available == 1
assert info.value.requested == 5
def test_zero_quantity_is_validation_error():
with pytest.raises(ValidationError, match="эерэг"):
place_order("Дэвтэр", 0)
def test_all_shop_errors_share_one_base():
for error_type in (ValidationError, OutOfStockError, PaymentDeclinedError):
assert issubclass(error_type, ShopError)Эхний тестэд as info нь pytest-ийн тусгай объект бөгөөд түүний .value нь шидэгдсэн exception өөрөө. Тест мессежийг биш, attribute-уудыг шалгаж байна: мессежийн текст хожим өөрчлөгдөж болох, attribute бол гэрээ. Хоёр дахь тест match-ээр мессежийн нэг хэсгийг шалгана, Хичээл 6.4-ийн retry тестийн адил. Гурав дахь тест шатлалын бүтцийг өөрийг нь шалгана: хэрэв хожим хэн нэгэн шинэ алдааг Exception-ээс шууд удамшуулбал энэ тест сануулна. Хичээл 6.2-т танилцсан issubclass энд class-уудын харилцааг шалгаж байна.
Хэрэв тест ValueError хүлээдэг бол тэр бидний кодын алдаанаас гарсан ValueError-ийг ч "амжилттай" гэж тооцох байсан. Өөрийн exception төрлүүд тестэд "яг энэ шалтгаанаар унасан" гэдгийг баталгаажуулдаг.
Жишээ 3: хуучин кодтой нийцтэй байх — олон эцэг
Заримдаа өөрийн exception-ийг бэлэн exception-оос ч удамшуулах шаардлага гардаг. Таны дэлгүүрийн кодыг хуучин скрипт хэрэглэдэг бөгөөд тэр байхгүй бараанд except KeyError: бичсэн гэж бодъё. Хэрэв та одоо UnknownProductError гэсэн шинэ алдаа шидвэл хуучин скрипт түүнийг барихгүй, эвдэрнэ. Шийдэл нь хоёр эцэгтэй class:
python
class UnknownProductError(ValidationError, KeyError):
"""Ийм бараа каталогт байхгүй."""
try:
raise UnknownProductError("Цүнх")
except KeyError:
print("Хуучин код барилаа")
try:
raise UnknownProductError("Цүнх")
except ShopError:
print("Шинэ код барилаа")Хуучин код барилаа
Шинэ код барилааUnknownProductError нь ShopError гэр бүлийн гишүүн, мөн KeyError. Хуучин ба шинэ код хоёулаа түүнийг зөв барина. Python-ий стандарт сан ч энэ аргыг хэрэглэдэг. Шударга анхааруулга: олон эцэгтэй class-ууд нарийн, тэдгээрийн __mro__ будлиантай болж болно. Энэ аргыг зөвхөн хуучин кодтой нийцтэй байх бодит шаардлага гарахад л хэрэглэ.
Жишээ 4: юуг барих вэ — шударга зөвлөмж
Одоо хичээлийн хоёр дахь гол сэдэв. handle функцийг "хэзээ ч унахгүй" болгох гэж except Exception нэмье. place_order-т санамсаргүй үсгийн алдаа гаргая:
python
def place_order(product, quantity):
if quantity <= 0:
raise ValidationError(f"Тоо ширхэг эерэг байх ёстой, {quantity} ирлээ")
available = STOCK.get(product, 0)
if available < quantity:
raise OutOfStockError(product, quantity, available)
STOCK[product] -= quantiy
return f"{product} x{quantity} захиалагдлаа"
def handle(product, quantity):
try:
print(place_order(product, quantity))
except Exception as error:
print(f"Захиалга амжилтгүй: {error}")
handle("Дэвтэр", 2)Захиалга амжилтгүй: name 'quantiy' is not definedquantiy үсгийн алдаа NameError үүсгэсэн. except Exception түүнийг барьж, хэрэглэгчид "захиалга амжилтгүй" гэж харуулсан. Тест бичээгүй бол хөгжүүлэгч энэ мессежийг хараад "агуулахын асуудал байх" гэж бодож магадгүй, учир нь traceback харагдсангүй, аль мөрөнд алдаа гарсан нь мэдэгдсэнгүй. Хамгийн муу тохиолдолд мессеж огт хэвлэгдэхгүй: except Exception: pass. Тэр үед захиалга бүр чимээгүй бүтэлгүйтнэ.
except: гэж төрөлгүй бичих нь бүр аюултай: тэр BaseException-ий бүх удмыг, KeyboardInterrupt ба SystemExit-ийг ч барина. Хэрэглэгч Ctrl+C дарахад програм зогсохгүй, "захиалга амжилтгүй" гэж хэвлээд үргэлжилнэ. ruff ийм бичлэгийг анхааруулах дүрэмтэй.
Дүрэм: зөвхөн та юу хийхээ мэддэг алдааг бар. handle нь дэлгүүрийн алдаанд юу хийхээ мэддэг: хэрэглэгчид тодорхой мессеж харуулах. Тэр NameError-т юу хийхээ мэддэггүй, тиймээс түүнийг барих ёсгүй: програм traceback-тайгаар унаж, хөгжүүлэгчид алдааг яг аль мөрөнд байгааг харуулах ёстой.
Тэгвэл except Exception хэзээ ч зөв биш гэж үү? Зөв газар бий: програмын хил. Хичээл 9.4-ийн event bus үүнийг хэрэглэсэн: нэг сонсогчийн алдаа бусад сонсогчдыг зогсоох ёсгүй, мөн event bus сонсогчдын алдааг мэдэх боломжгүй. Гэвч тэр алдааг залгиагүй: on_error-оор мэдээлж, failures жагсаалтад хадгалсан. Ижил загвар вэб серверийн хүсэлт боловсруулагчид гардаг: нэг хүсэлтийн алдаа серверийг унагах ёсгүй, гэхдээ алдаа traceback-тайгаар бүрэн бүртгэгдэж, хэрэглэгчид ерөнхий "серверийн алдаа" мессеж очих ёстой. Course 3-ын FastAPI дотроо яг ингэж ажилладаг. Дүрмийн хоёр дахь хэсэг: ерөнхий барилт зөвхөн хилд, заавал бүрэн бүртгэлтэйгээр. Traceback-ийг хадгалах хэрэгслүүдийг Бүлэг 11-ийн дараагийн хичээлүүдэд үзнэ.
Хүсвэл Хичээл 9.4-ийн event-hub-ийн InventoryUpdater-ийг энэ шатлалтай холбож, ValueError-ийн оронд OutOfStockError шидэж, event bus-ийн on_error функцэд ShopError ба бусад алдааг өөрөөр хэвлэдэг болгоорой: дэлгүүрийн алдааг товч, бусдыг traceback-тайгаар. traceback модулийн traceback.print_exception() сүүлийнхэд тусална. Сонирхвол OutOfStockError-ийг frozen dataclass болгож болох эсэхийг туршиж үзээрэй: exception class-ууд dataclass-тай хэрхэн харьцдаг вэ, ямар хүндрэл гарах вэ? Мөн PaymentError-т retryable гэсэн class attribute нэмж, PaymentTimeoutError-д True, PaymentDeclinedError-д False гэж тохируулаад, retry-ийн exceptions-ийн оронд тэр attribute-аар шийддэг decorator бичих нь Бүлэг 7 ба 11-ийг нэгтгэсэн сонирхолтой загвар. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
Бидний кодын алдаа нуугдах. Мессежгүй, Жишээ 4-ийн урхи. except Exception эсвэл except: бичсэн try блок доторх үсгийн алдаа, None-ийн attribute зэрэг алдаанууд энгийн "амжилтгүй" мессеж болж хувирна. Шинж тэмдэг нь хэрэглэгч "үргэлж алдаа гардаг" гэж гомдоллох, харин traceback хаана ч байхгүй. Шийдэл: зөвхөн өөрийн exception-уудыг, эсвэл юу хийхээ мэддэг тодорхой бэлэн exception-уудыг бар. Ерөнхий барилтыг зөвхөн хилд, бүрэн бүртгэлтэйгээр хэрэглэ.
Нарийн except хэзээ ч ажиллахгүй байх. Мессежгүй алдаа. except-үүдийн дарааллыг буруу бичсэн үед гарна:
python
try:
place_order("Ном", 5)
except ShopError as error:
print("Ерөнхий:", error)
except OutOfStockError as error:
print("Нөөц:", error.available)OutOfStockError нь ShopError тул эхний except түүнийг барина, хоёр дахь нь хэзээ ч хүрэгдэхгүй. Trace-ийн Алхам 3-ыг санаарай: Python эхний таарсанд зогсдог. Шийдэл: нарийн төрлүүд эхэнд, ерөнхий нь сүүлд. ruff ба mypy-ийн зарим тохиргоо ийм хүрэгдэхгүй блокуудыг илрүүлж чаддаг.
TypeError: exceptions must derive from BaseException. “Exception-ууд BaseException-ээс удамшсан байх ёстой” гэсэн утгатай. Өөрийн exception class-ийг ямар ч эцэггүйгээр, class ShopError: гэж тодорхойлоод шидэхэд гарна. Хичээл 8.1-т raise NotImplemented-тэй ижил мессеж харсан. Энэ class-ийг except-д хэрэглэвэл “TypeError: catching classes that do not inherit from BaseException is not allowed” буюу “BaseException-ээс удамшаагүй class-уудыг барих зөвшөөрөгдөхгүй” гарна. Шийдэл: class ShopError(Exception):.
Мессежийн оронд tuple харагдах. OutOfStockError-ийн __init__-д super().__init__(...) мөрийг мартвал traceback-ийн сүүлийн мөр “OutOfStockError: ('Ном', 5, 1)” гэх мэт харагдана. Python exception үүсэх үед түүний argument-уудыг автоматаар args attribute-д хадгалдаг бөгөөд мессеж өгөгдөөгүй бол тэр tuple-ийг хэвлэдэг. Програм ажиллана, гэвч мессеж уншигдахгүй. Шийдэл: өөрийн __init__-тэй exception бүр эх class-д мессежийг дамжуулах.
Exception-ийг BaseException-ээс удамшуулах. Мессежгүй, гэвч ноцтой дизайны алдаа. class ShopError(BaseException): гэж бичвэл Хичээл 9.4-ийн event bus, вэб сервер зэрэг except Exception хэрэглэдэг хилүүд таны алдааг барихгүй. Бүх програм KeyboardInterrupt шиг зогсоно. BaseException зөвхөн "програмыг зогсоох хүсэлт"-д зориулагдсан. Өөрийн exception-ууд үргэлж Exception-ээс.
Бэлэн exception-ууд програм томрох тусам хэтэрхий ерөнхий болдог тул програмд өөрийн exception шатлал хэрэгтэй. Шатлал Exception-ээс удамшсан нэг эх class-аас эхэлж, түүнээс дуудагч тал өөрөөр шийдэх шаардлагатай алдаа бүрт тусдаа class удамшина. Нэрс Error-оор төгсөж, docstring нь алдаа хэзээ гардгийг, түүнийг яаж шийдэхийг тайлбарлана. Exception бол объект тул тодорхой мэдээллийг attribute-д хадгалж, мессежийг super().__init__-ээр эх class-д дамжуулна. Python except-үүдийг дээрээс доош шалгаж, isinstance-ээр эхний таарсанд зогсдог тул нарийн төрлүүд эхэнд байх ёстой. Шатлал retry шиг хэрэгслүүдэд нэрээр нь шийдвэр гаргах, тестэд яг шалтгааныг шалгах боломж олгодог. Хуучин кодтой нийцтэй байхын тулд бэлэн exception-оос ч удамшуулж болно. Зөвхөн юу хийхээ мэддэг алдаагаа бар. except Exception зөвхөн програмын хилд, бүрэн бүртгэлтэйгээр. Төрөлгүй except: Ctrl+C-г ч залгидаг.
Энэ хичээлд бид алдааг үүсгэж, барьсан. Гэвч бодит програмд алдаа ихэвчлэн өөр алдааны үр дагавар болж гардаг. Төлбөрийн функц гадны сангаас ConnectionError хүлээн авч, түүнийг PaymentTimeoutError болгон дээш дамжуулах ёстой гэж бодъё. Хэрэв бид ердөө шинэ алдаа шидвэл анхны ConnectionError ба түүний traceback алга болж, debug хийхэд жинхэнэ шалтгаан харагдахгүй. Дараагийн хичээлд raise from-оор алдаануудыг шалтгааны гинж болгон холбож сурна. Мөн хоёр алдаа агуулсан урт traceback-ийг, “The above exception was the direct cause of the following exception” гэсэн мөртэйг, Хичээл 2.1-т бид түүнийг товч харсан, эцэслэн бүрэн уншина.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.