aidoc.uz

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.

▶ Video dars· Corey Schafer· 10:34· ingliz tilida
Python Tutorial: Using Try/Except Blocks for Error Handling

try/except/else/finally bloklari va xatolarni to'g'ri ushlash. Logging qismi bobning 9-bo'limida.

YouTube'da ochish ↗

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.

main()
train()
load_data()
open("data.csv")
FileNotFoundError ko'tarildi

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
           └── RecursionError

except LookupError ham IndexErrorni, ham KeyErrorni ushlaydi — chunki ular uning vorislari.

Eng ko'p uchraydigan istisnolar#

IstisnoQachonMisol
TypeErrorNoto'g'ri tipdagi qiymat"a" + 1, len(5)
ValueErrorTip to'g'ri, qiymat noto'g'riint("abc"), math.sqrt(-1)
KeyErrorLug'atda kalit yo'qd["yo'q"]
IndexErrorIndeks chegaradan tashqarida[1, 2][5]
AttributeErrorObyektda atribut yo'qNone.split() — ko'pincha None qaytgan joyda
FileNotFoundErrorFayl yo'qopen("yoq.txt")
ZeroDivisionErrorNolga bo'lish1 / 0
ImportError / ModuleNotFoundErrorModul topilmadiimport yoq_modul
NotImplementedErrorMetod 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")
BlokQachon ishlaydi
tryHar doim — "xavfli" kod shu yerda
except Xtryda X tipidagi (yoki vorisi) istisno bo'lsa
elsetry xatosiz tugasa
finallyHar 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 = 0

except 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
    pass

Xatoni 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 uzatish

Past 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 e

Traceback'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?")
    raise

Izoh 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 = limit

Endi 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 hammasi

Anthropic 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 = None

EAFP 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.

assertraise ValueError
Ichki mantiq xatolari (bug)Tashqi ma'lumot xatolari
O'chirilishi mumkinHar doim ishlaydi
Testlarda (pytest) asosiy vositaOmmaviy 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 ham

O'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 saqlanmadi

Darajalar#

DarajaQachonMisol
DEBUGFaqat muammo qidirishda kerak bo'ladigan tafsilotlarSo'rov tanasi, oraliq qiymatlar
INFONormal ish jarayoni bosqichlari"Model yuklandi", "epoch 3/10"
WARNINGKutilmagan, lekin dastur davom etadiRetry, eskirgan parametr, bo'sh qiymat
ERRORAmal bajarilmadiSo'rov muvaffaqiyatsiz, fayl saqlanmadi
CRITICALDastur davom eta olmaydiMa'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 traceback

log.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 qachon basicConfig chaqirmaydi — 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 result

E'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#

XatoOqibatTo'g'risi
except: passXatolar yo'qoladi, Ctrl+C ishlamaydiAniq tipni ushlang; kerak bo'lsa logga yozing
Katta try blokiKutilmagan xatolar ham ushlanaditryda faqat xavfli qator, qolgani elseda
except Exception birinchiAniqroq exceptlar ishlamaydiAniqrog'ini yuqoriga
raise Exception("xato")Chaqiruvchi aniq ushlay olmaydiAniq tip yoki o'z klassingiz
Xatoni qayta ko'tarishda fromsiz yangi xatoAsl sabab chalkashadiraise New(...) from e
Foydalanuvchi ma'lumotini assert bilan tekshirish-O da tekshiruv yo'qoladiif ...: raise ValueError
print bilan "logging"Darajasiz, faylsiz, o'chirib bo'lmaydilogging
log.error(e)Traceback yo'qoladilog.exception("...")
Kutubxonada basicConfigIlovaning sozlamalarini buzadiFaqat getLogger(__name__)
Logda API kalitSir sizib chiqadiSirlarni yashiring yoki yozmang
Amaliy topshiriq

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.

Amaliy topshiriq

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.

Amaliy topshiriq

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(...)

Amaliy topshiriq

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.

Yodda saqlang
  • 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.

Test: topshirilmaganMasalalar: 0/3

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.)