3-qism · Professional Python·10-bob
Xatolar bilan ishlash va logging
Istisnolar ierarxiyasi, try/except/else/finally, xatoni qayta ko'tarish va zanjirlash (raise from), o'z xato klasslaringiz, EAFP, ExceptionGroup, assert, va professional logging sozlamasi.
Har qanday real dasturda narsalar noto'g'ri ketadi: fayl topilmaydi,
tarmoq uziladi, API limit beradi, foydalanuvchi noto'g'ri ma'lumot
kiritadi, model NaN qaytaradi. Professional kod xatolarning yo'qligi
bilan emas, ularni qanday boshqarishi bilan ajralib turadi: qaysi
xatoni ushlash, qaysisini yuqoriga uzatish, foydalanuvchiga nima deyish
va keyin muammoni topish uchun logga nima yozish.
try/except/else/finally bloklari va xatolarni to'g'ri ushlash. Logging qismi bobning 9-bo'limida.
1. Istisno (exception) nima#
Xato yuz berganda Python istisno obyekti yaratadi va uni "ko'taradi" (raise). Istisno chaqiruvlar zanjiri bo'ylab yuqoriga ko'tariladi — kim uni ushlamasa, dastur traceback bilan to'xtaydi.
Istisno open dan load_dataga, undan trainga, undan mainga
ko'tariladi. Har bir bosqich uni ushlashi (except) yoki o'tkazib
yuborishi mumkin.
Istisnolar ierarxiyasi#
Istisnolar — klasslar va ular daraxt tashkil qiladi:
BaseException
├── KeyboardInterrupt ← Ctrl+C
├── SystemExit ← sys.exit()
└── Exception ← oddiy xatolarning hammasi shu yerdan
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── OSError
│ ├── FileNotFoundError
│ ├── PermissionError
│ └── ConnectionError
│ └── TimeoutError ...
├── TypeError
├── ValueError
│ └── UnicodeDecodeError ...
├── AttributeError
├── ImportError
│ └── ModuleNotFoundError
└── RuntimeError
└── RecursionErrorexcept LookupError ham IndexErrorni, ham KeyErrorni ushlaydi —
chunki ular uning vorislari.
Eng ko'p uchraydigan istisnolar#
| Istisno | Qachon | Misol |
|---|---|---|
TypeError | Noto'g'ri tipdagi qiymat | "a" + 1, len(5) |
ValueError | Tip to'g'ri, qiymat noto'g'ri | int("abc"), math.sqrt(-1) |
KeyError | Lug'atda kalit yo'q | d["yo'q"] |
IndexError | Indeks chegaradan tashqarida | [1, 2][5] |
AttributeError | Obyektda atribut yo'q | None.split() — ko'pincha None qaytgan joyda |
FileNotFoundError | Fayl yo'q | open("yoq.txt") |
ZeroDivisionError | Nolga bo'lish | 1 / 0 |
ImportError / ModuleNotFoundError | Modul topilmadi | import yoq_modul |
NotImplementedError | Metod hali yozilmagan (abstrakt) | 13-bob |
2. try / except / else / finally#
def read_config(path: str) -> dict:
try:
f = open(path, encoding="utf-8")
except FileNotFoundError:
print(f"{path} topilmadi, standart sozlamalar ishlatiladi")
return {}
else:
# faqat try MUVAFFAQIYATLI bo'lganda ishlaydi
with f:
return json.load(f)
finally:
# HAR DOIM ishlaydi — xato bo'lsa ham, return bo'lsa ham
print("read_config tugadi")| Blok | Qachon ishlaydi |
|---|---|
try | Har doim — "xavfli" kod shu yerda |
except X | tryda X tipidagi (yoki vorisi) istisno bo'lsa |
else | try xatosiz tugasa |
finally | Har doim, oxirida — resurslarni bo'shatish uchun |
Nega else kerak, kodni try ichiga yozsa bo'lmaydimi? Chunki try bloki
iloji boricha kichik bo'lishi kerak: faqat siz kutayotgan xato chiqishi
mumkin bo'lgan qator. Aks holda boshqa joydagi kutilmagan xatoni ham
tasodifan ushlab, yashirib qo'yasiz.
Bir nechta istisnoni ushlash#
try:
value = int(data[key])
except KeyError:
value = 0
except (ValueError, TypeError) as e: # bir nechta tip — tuple bilan
print(f"Noto'g'ri qiymat: {e}")
value = 0except bloklari tartib bilan tekshiriladi — aniqrog'ini yuqoriga
yozing. except Exception birinchi bo'lsa, qolganlari hech qachon ishlamaydi.
Hamma narsani ushlamang#
try:
train_model()
except: # ❌ "yalang'och" except — Ctrl+C va sys.exit ni ham ushlaydi!
pass
try:
train_model()
except Exception: # ❌ hali ham yomon — xato jimgina yo'qoladi
passXatoni yutib yuborish (except: pass) — eng xavfli odat. Dastur ishlashda
davom etadi, lekin noto'g'ri holatda, va siz nima bo'lganini hech qachon
bilmaysiz. Agar keng ushlash zarur bo'lsa (masalan, servisning eng yuqori
qatlamida — bitta so'rov xatosi butun serverni yiqitmasligi uchun), hech
bo'lmaganda logga yozing:
try:
handle_request(req)
except Exception:
log.exception("So'rovni qayta ishlashda xato: %s", req.id) # to'liq traceback bilan
return error_response(500)3. Istisno ko'tarish: raise#
def set_temperature(t: float) -> None:
if not 0 <= t <= 1:
raise ValueError(f"temperature 0 va 1 orasida bo'lishi kerak, berildi: {t}")
...Yaxshi xato xabari: nima noto'g'ri, qanday bo'lishi kerak va
nima berilgan edi. "Xato" yoki "Invalid input" — foydasiz.
Qayta ko'tarish va zanjirlash#
try:
process(batch)
except ConnectionError:
log.warning("Tarmoq xatosi, batch %d", batch_id)
raise # o'sha istisnoni o'zgarishsiz yuqoriga uzatishPast darajadagi xatoni yuqori darajadagi ma'noli xatoga aylantirish —
raise ... from ...:
class ConfigError(Exception):
pass
def load_config(path: str) -> dict:
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except (OSError, json.JSONDecodeError) as e:
raise ConfigError(f"Konfiguratsiyani o'qib bo'lmadi: {path}") from eTraceback'da ikkala xato ham ko'rinadi: "The above exception was the direct
cause of the following exception". Asl sabab yo'qolmaydi (e.__cause__),
lekin chaqiruvchi faqat ConfigErrorni ushlashi kifoya.
Xatoga izoh qo'shish (Python 3.11+)#
try:
value = config["model"]
except KeyError as e:
e.add_note("config.yaml faylida 'model' maydoni bormi?")
raiseIzoh traceback oxirida chiqadi — kontekst qo'shishning eng oson usuli.
4. O'z istisno klasslaringiz#
Kutubxona yoki katta ilova yozayotganda, o'z xatolar ierarxiyangizni yarating:
class LLMError(Exception):
"""Bizning LLM klientimizning barcha xatolari uchun asos."""
class RateLimitError(LLMError):
def __init__(self, retry_after: float):
super().__init__(f"API limiti: {retry_after}s dan keyin qayta urinib ko'ring")
self.retry_after = retry_after
class ContextTooLongError(LLMError):
def __init__(self, tokens: int, limit: int):
super().__init__(f"Kontekst juda uzun: {tokens} > {limit} token")
self.tokens = tokens
self.limit = limitEndi chaqiruvchi o'zi xohlagan darajada ushlay oladi:
try:
answer = ask_llm(prompt)
except RateLimitError as e:
time.sleep(e.retry_after) # aniq holat — aniq reaksiya
answer = ask_llm(prompt)
except ContextTooLongError:
answer = ask_llm(truncate(prompt))
except LLMError:
answer = "Xizmat vaqtincha ishlamayapti" # qolgan hammasiAnthropic SDK'si ham aynan shunday tuzilgan: anthropic.APIError →
APIStatusError → RateLimitError, AuthenticationError, BadRequestError...
(23-bob).
5. EAFP va LBYL#
6-bobda qisqacha ko'rgan edik. Python uslubi — EAFP ("ruxsat so'ragandan kechirim so'rash oson"):
# LBYL — oldindan tekshirish
if os.path.exists(path):
with open(path) as f: # ← tekshiruv va ochish orasida fayl o'chirilishi mumkin!
data = f.read()
# EAFP — urinib ko'rish
try:
with open(path) as f:
data = f.read()
except FileNotFoundError:
data = NoneEAFP afzalliklari: poyga holati (race condition) yo'q, "baxtli yo'l" o'qilishi
oson, ko'pincha tezroq. Lekin oldindan tekshirish arzon va aniq bo'lsa
(masalan, if not items: return) — LBYL ham to'liq to'g'ri.
6. ExceptionGroup — bir nechta xato bir vaqtda (3.11+)#
Parallel ishlarda (17-bob) bir vaqtning o'zida bir nechta xato bo'lishi
mumkin. Ular ExceptionGroupga yig'iladi va except* bilan ushlanadi:
try:
raise ExceptionGroup("bir nechta vazifa yiqildi", [
ValueError("noto'g'ri ma'lumot"),
TimeoutError("server javob bermadi"),
])
except* ValueError as eg:
print("Qiymat xatolari:", eg.exceptions)
except* TimeoutError as eg:
print("Timeout'lar:", eg.exceptions)asyncio.TaskGroup (17-bob) aynan shunday xatolarni ko'taradi.
7. assert — ichki invariantlar uchun#
def cosine_similarity(a: list[float], b: list[float]) -> float:
assert len(a) == len(b), f"Vektor uzunliklari farq qiladi: {len(a)} != {len(b)}"
...assert — "bu hech qachon bo'lmasligi kerak, bo'lsa — dasturda xato bor"
degan dasturchi uchun tekshiruv. Foydalanuvchi kiritgan ma'lumotni
tekshirish uchun ishlatmang: Python -O (optimizatsiya) rejimida
ishga tushirilsa, barcha assertlar o'chirib tashlanadi.
assert | raise ValueError |
|---|---|
| Ichki mantiq xatolari (bug) | Tashqi ma'lumot xatolari |
| O'chirilishi mumkin | Har doim ishlaydi |
| Testlarda (pytest) asosiy vosita | Ommaviy funksiyalar argumentlari |
ML kodida assertlar shakllarni tekshirishda juda foydali:
assert x.shape == (batch, seq_len, d_model), x.shape.
8. with va resurslarni bo'shatish#
Fayllar, tarmoq ulanishlari, qulflar — ishlatilgandan keyin yopilishi
kerak, xato bo'lsa ham. try/finally bilan:
f = open("data.txt", encoding="utf-8")
try:
data = f.read()
finally:
f.close()Xuddi shu narsani with (context manager) qisqaroq qiladi:
with open("data.txt", encoding="utf-8") as f:
data = f.read()
# bu yerda fayl albatta yopilgan — xato bo'lgan bo'lsa hamO'z context manager'laringizni yozishni 14-bobda o'rganamiz.
9. Logging — print emas#
print debug uchun qulay, lekin real dasturda yetarli emas: darajasi yo'q,
vaqt yo'q, qayerdan kelgani noma'lum, o'chirib-yoqib bo'lmaydi, faylga
yozilmaydi. logging moduli bularning hammasini hal qiladi.
Asosiy sozlama#
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(name)s: %(message)s",
datefmt="%H:%M:%S",
)
log = logging.getLogger(__name__) # har modulda shunday — modul nomi bilan logger
log.debug("batafsil ma'lumot") # INFO darajasida ko'rinmaydi
log.info("epoch %d tugadi, loss=%.4f", 1, 0.3456)
log.warning("GPU topilmadi, CPU ishlatilmoqda")
log.error("checkpoint saqlanmadi")07:19:13 INFO myapp.train: epoch 1 tugadi, loss=0.3456
07:19:13 WARNING myapp.train: GPU topilmadi, CPU ishlatilmoqda
07:19:13 ERROR myapp.train: checkpoint saqlanmadiDarajalar#
| Daraja | Qachon | Misol |
|---|---|---|
DEBUG | Faqat muammo qidirishda kerak bo'ladigan tafsilotlar | So'rov tanasi, oraliq qiymatlar |
INFO | Normal ish jarayoni bosqichlari | "Model yuklandi", "epoch 3/10" |
WARNING | Kutilmagan, lekin dastur davom etadi | Retry, eskirgan parametr, bo'sh qiymat |
ERROR | Amal bajarilmadi | So'rov muvaffaqiyatsiz, fayl saqlanmadi |
CRITICAL | Dastur davom eta olmaydi | Ma'lumotlar bazasi ulanmadi |
basicConfig(level=logging.INFO) — INFO va undan yuqorisi ko'rinadi.
Debug paytida DEBUG ga o'zgartirasiz — kodga tegmasdan.
Logging uchun to'g'ri formatlash#
log.info("user %s loaded %d rows", user_id, n) # ✅ satr faqat kerak bo'lsa formatlanadi
log.info(f"user {user_id} loaded {n} rows") # ⚠️ ishlaydi, lekin har doim formatlanadi%s uslubi (3-bobdagi istisno) — xabar o'chirilgan darajada bo'lsa,
formatlash umuman bajarilmaydi. Ruff (G004) bu haqda ogohlantiradi.
Xato logini traceback bilan#
try:
result = 1 / 0
except ZeroDivisionError:
log.exception("Hisoblashda xato") # ERROR darajasi + to'liq tracebacklog.exception faqat except bloki ichida ishlatiladi. log.error(..., exc_info=True)
xuddi shunday ishlaydi.
Faylga va konsolga bir vaqtda#
import logging
import sys
from logging.handlers import RotatingFileHandler
def setup_logging(level: str = "INFO", logfile: str = "app.log") -> None:
fmt = logging.Formatter("%(asctime)s %(levelname)-8s %(name)s: %(message)s")
console = logging.StreamHandler(sys.stderr)
console.setFormatter(fmt)
file = RotatingFileHandler(logfile, maxBytes=10_000_000, backupCount=5, encoding="utf-8")
file.setFormatter(fmt)
root = logging.getLogger()
root.setLevel(level)
root.handlers = [console, file]
# shovqinli kutubxonalarni tinchlantirish
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)RotatingFileHandler — fayl 10 MB ga yetganda yangisini ochadi va eng
ko'pi 5 ta eskisini saqlaydi. Diskni to'ldirib qo'ymaslik uchun.
logging kutubxonada va ilovada#
- Ilova (skript, servis) — logging'ni sozlaydi (
basicConfig, handler'lar) — bir marta,main()boshida. - Kutubxona (sizning paketingiz) — faqat
log = logging.getLogger(__name__)qiladi va xabar yozadi. Hech qachonbasicConfigchaqirmaydi — bu ilovaning ishi.
10. Amaliyot: ishonchli batch ishlovchi#
Hamma narsani birlashtiramiz — ko'p yozuvni qayta ishlaydigan va bitta buzilgan yozuv tufayli to'xtab qolmaydigan funksiya:
import json
import logging
from dataclasses import dataclass, field
log = logging.getLogger(__name__)
class RecordError(Exception):
"""Bitta yozuvni qayta ishlab bo'lmadi."""
@dataclass
class BatchResult:
ok: list[dict] = field(default_factory=list)
failed: list[tuple[int, str]] = field(default_factory=list) # (qator raqami, sabab)
def parse_record(line: str) -> dict:
try:
rec = json.loads(line)
except json.JSONDecodeError as e:
raise RecordError(f"JSON emas: {e.msg}") from e
if not isinstance(rec, dict):
raise RecordError("obyekt emas")
text = rec.get("text")
if not isinstance(text, str) or not text.strip():
raise RecordError("'text' maydoni yo'q yoki bo'sh")
return {"text": text.strip(), "label": rec.get("label")}
def process_lines(lines, max_failures: int = 100) -> BatchResult:
result = BatchResult()
for n, line in enumerate(lines, 1):
try:
result.ok.append(parse_record(line))
except RecordError as e:
log.warning("%d-qator o'tkazib yuborildi: %s", n, e)
result.failed.append((n, str(e)))
if len(result.failed) > max_failures:
raise RuntimeError(f"Juda ko'p buzilgan yozuv (>{max_failures}) — fayl formati to'g'rimi?") from e
log.info("Tayyor: %d ta yaxshi, %d ta buzilgan", len(result.ok), len(result.failed))
return resultE'tibor bering:
- faqat kutilgan xato (
RecordError) ushlanadi — boshqa har qanday kutilmagan xato (masalan, kodimizdagi bug) yuqoriga ko'tariladi; - har bir buzilgan yozuv logga yoziladi va natijada saqlanadi — keyin tahlil qilish mumkin;
- "jim muvaffaqiyatsizlik"dan himoya: agar deyarli hamma yozuv buzilgan bo'lsa (masalan, noto'g'ri fayl berilgan), dastur buni "muvaffaqiyat" deb ko'rsatmaydi, balki to'xtaydi.
Nima noto'g'ri ketishi mumkin#
| Xato | Oqibat | To'g'risi |
|---|---|---|
except: pass | Xatolar yo'qoladi, Ctrl+C ishlamaydi | Aniq tipni ushlang; kerak bo'lsa logga yozing |
Katta try bloki | Kutilmagan xatolar ham ushlanadi | tryda faqat xavfli qator, qolgani elseda |
except Exception birinchi | Aniqroq exceptlar ishlamaydi | Aniqrog'ini yuqoriga |
raise Exception("xato") | Chaqiruvchi aniq ushlay olmaydi | Aniq tip yoki o'z klassingiz |
Xatoni qayta ko'tarishda fromsiz yangi xato | Asl sabab chalkashadi | raise New(...) from e |
Foydalanuvchi ma'lumotini assert bilan tekshirish | -O da tekshiruv yo'qoladi | if ...: raise ValueError |
print bilan "logging" | Darajasiz, faylsiz, o'chirib bo'lmaydi | logging |
log.error(e) | Traceback yo'qoladi | log.exception("...") |
Kutubxonada basicConfig | Ilovaning sozlamalarini buzadi | Faqat getLogger(__name__) |
| Logda API kalit | Sir sizib chiqadi | Sirlarni yashiring yoki yozmang |
1. Xavfsiz songa aylantirish
safe_float(value, default=None) funksiyasini yozing: satr yoki sonni
floatga aylantirsin; None, bo'sh satr, noto'g'ri satr uchun default
qaytarsin; "nan", "inf" kabi qiymatlarni ham default deb hisoblasin.
Hech qachon except Exception ishlatmang.
Maslahatni ko'rsatish
try: return float(s) except (ValueError, TypeError): return default. NaN va inf satrlari ham float() dan o'tadi — math.isfinite bilan tekshiring.
2. Xatolar ierarxiyasi
Dataset yuklovchi uchun xatolar ierarxiyasini yarating: DatasetError
(asos), MissingColumnError (qaysi ustun yo'q va qaysilari mavjud),
EmptyDatasetError. load_dataset(rows, required_columns) funksiyasi
ularni ko'tarsin. Chaqiruvchi kodda ularni turli usulda ushlab ko'ring.
Maslahatni ko'rsatish
class DatasetError(Exception); class MissingColumnError(DatasetError) — __init__da column va available saqlang, super().__init__ ga tushunarli xabar bering.
3. Retry + logging
4-bobdagi call_with_retryni qayta yozing: print o'rniga logging;
qaysi istisnolarda qayta urinish kerakligini parametr bilan bering
(retry_on=(ConnectionError, TimeoutError)); oxirgi urinishdan keyin
asl xato traceback'i bilan ko'tarilsin; har bir urinishga
add_note orqali "N-urinish" izohi qo'shilsin.
Maslahatni ko'rsatish
for attempt in range(1, n+1): try: return fn() except retry_on as e: log.warning(...); if attempt == n: raise; time.sleep(...)
4. Log sozlamasi
setup_logging funksiyasini o'zingizning mlutils paketingizga qo'shing
(9-bob): daraja LOG_LEVEL muhit o'zgaruvchisidan o'qilsin, konsolga rangli
emas oddiy format, faylga esa JSON format (har qator — {"time": ..., "level": ..., "msg": ...})
yozilsin. Maslahat: o'z logging.Formatter vorisingizni yozib, format
metodini qayta aniqlang.
- 01Istisnolar — klasslar ierarxiyasi; except X vorislarni ham ushlaydi; aniqrog'ini yuqoriga yozing.
- 02try bloki kichik bo'lsin; else — muvaffaqiyatda, finally — har doim.
- 03Xatoni yutmang: aniq tipni ushlang yoki log.exception bilan yozing.
- 04raise New(...) from e — ma'noli xato + asl sabab; add_note — kontekst.
- 05O'z xatolar ierarxiyangiz (Exception'dan) chaqiruvchiga aniq reaksiya imkonini beradi.
- 06assert — ichki invariantlar uchun; foydalanuvchi ma'lumoti uchun raise ValueError.
- 07logging: getLogger(__name__), darajalar, %s formatlash, log.exception; sozlash — faqat ilovada.
- 08Logga sirlarni yozmang.
Mustahkamlash
Test va amaliy masalalar
Avval testni ishlang — har javobdan keyin izoh chiqadi. Keyin masalalarni shu yerning o'zida yechib, brauzerdagi haqiqiy Python bilan avtomatik tekshiring. Natijalaringiz kurs progressi sahifasida jamlanadi.
Bilimni tekshirish
Bob bo'yicha test
12 ta savol · har javobdan keyin to'g'ri yoki xatoligi va izoh ko'rsatiladi · oxirida natija foizda.
Amaliy masalalar
Kodni yozing va «Tekshirish»ni bosing (yoki Ctrl/⌘ + Enter). Kodingiz shu brauzerda saqlanib turadi.
01Xavfsiz float
safe_float(value, default=None) — satr yoki sonni floatga aylantirsin; None, bo'sh satr, noto'g'ri satr, "nan", "inf" uchun default qaytarsin. except Exception ishlatmang — aniq istisnolar.
02O'z xatolar ierarxiyangiz
DatasetError(Exception) va undan meros MissingColumnError yarating: MissingColumnError(column, available) — column va available atributlarini saqlasin, xabar "Ustun yo'q: 'x'. Mavjud: a, b" ko'rinishida bo'lsin. check_columns(rows, required) — birinchi qatorda yo'q birinchi ustun uchun shu xatoni ko'tarsin.
03Retry funksiyasi
call_with_retry(fn, attempts=3, retry_on=(ConnectionError,)) — fn() ni chaqirsin; retry_on dagi xato bo'lsa qayta urinsin, urinishlar tugasa oxirgi xatoni ko'tarsin; boshqa xatolarni darhol ko'tarsin. (Kutish shart emas.)