OzbekPython / Bob 14

Xatolar va istisnolar

Sintaksis xatolari va istisnolar farqi, traceback öqiş, try/except/else/finally, raise va öz istisno sinflaringizni yaratiş.

Örta daraja18 daqiqa16 kod misoli14 / 24

Xato — dasturçining doimiy hamrohi

Har qanday dasturçi, hatto eng tajribalisi ham, har kuni xatolarga duç keladi. Muhimi — xatodan qörqmaslik, balki uni öqiy oliş va dasturni xatolarga çidamli qilib yoziş. Bu bobda aynan şuni örganamiz.

Pythonda xatolar ikki katta guruhga bölinadi:

  1. Sintaksis xatolari (SyntaxError) — kod Python qoidalariga mos yozilmagan. Bunday dastur umuman işga tuşmaydi.
  2. Istisnolar (exception) — kod töğri yozilgan, lekin işlaş paytida kutilmagan holat yuz berdi: nolga böliş, mavjud bölmagan fayl, notöğri maʼlumot va hokazo.

Sintaksis xatolari

Sintaksis xatosi — bu xuddi gapdagi imlo xatosiga öxşaydi. Python kodni bajarişdan oldin uni "öqib çiqadi" va qoidaga zid joyni topsa, birorta qatorni ham bajarmaydi.

Natija
print("Salom"
Natija
  File "salom.py", line 1
    print("Salom"
         ^
SyntaxError: '(' was never closed
Natija
if True
    print(1)
Natija
  File "şart.py", line 1
    if True
           ^
SyntaxError: expected ':'

Python 3.12 xabarlari ança tuşunarli: '(' was never closed — qavs yopilmagan, expected ':' — ikki nuqta kutilgan edi. ^ belgisi esa muammo joyini körsatadi. Sintaksis xatolarini try bilan "uşlab" bölmaydi — ularni faqat kodni tuzatib yöqotasiz.

Istisnolar va traceback

Endi sintaktik jihatdan töğri, lekin işlaş paytida "qulaydigan" dasturni köraylik. narx.py fayli:

Python
def ortacha_narx(narxlar):
    return sum(narxlar) / len(narxlar)


def hisobot(dokon, narxlar):
    print(f"{dokon}: {ortacha_narx(narxlar)} söm")


hisobot("Çorsu bozori", [12000, 15000, 13500])
hisobot("Yangi dökon", [])

Işga tuşirsak:

Natija
Çorsu bozori: 13500.0 söm
Traceback (most recent call last):
  File "narx.py", line 10, in <module>
    hisobot("Yangi dökon", [])
  File "narx.py", line 6, in hisobot
    print(f"{dokon}: {ortaça_narx(narxlar)} söm")
                      ^^^^^^^^^^^^^^^^^^^^^
  File "narx.py", line 2, in ortaça_narx
    return sum(narxlar) / len(narxlar)
           ~~~~~~~~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero

Bu matn traceback (yaʼni "orqaga iz") deb ataladi. Uni qörqmasdan öqişni örganing:

  • Eng pastki qatordan boşlang. U yerda istisno turi (ZeroDivisionError) va qisqa izoh (division by zero — nolga böliş) yozilgan.
  • Yuqoriga qarab çiqing. Har bir File ..., line ... qatori çaqiruvlar zanjirini körsatadi: 10-qatordagi hisobot() → 6-qatordagi ortacha_narx() → 2-qatordagi böliş amali.
  • Eng oxirgi File qatori xato aynan qayerda yuz berganini bildiradi. Bizda — böş röyxat uzunligi 0 ga bölinmoqda.

Eʼtibor bering: birinçi hisobot muvaffaqiyatli bajarildi, ikkinçisi esa dasturni töxtatdi. Istisno uşlanmasa, dastur öşa joyda tugaydi.

Köp uçraydigan istisnolar

IstisnoQaçon yuz beradiMisol
ValueErrorTuri töğri, lekin qiymati yaroqsizint("beş")
TypeErrorAmal bu turdagi qiymatga mos emas"5" + 3
ZeroDivisionErrorNolga böliş10 / 0
KeyErrorLuğatda bunday kalit yöq{"a": 1}["b"]
IndexErrorRöyxat çegarasidan taşqariga çiqiş[1, 2][5]
FileNotFoundErrorFayl topilmadiopen("yoq.txt")
AttributeErrorObyektda bunday atribut yoki metod yöq"matn".push()
NameErrorÖzgaruvçi eʼlon qilinmaganprint(nomalum)

Har birini öz közimiz bilan köraylik. Quyidagi kod har bir xatoni uşlab, uning turi va xabarini çiqaradi (try ni birozdan keyin batafsil tuşuntiramiz):

Python
sinovlar = [
    lambda: int("beş"),
    lambda: "5" + 3,
    lambda: 10 / 0,
    lambda: {"olma": 8000}["anor"],
    lambda: [1, 2, 3][5],
    lambda: open("mavjud_emas.txt"),
]

for sinov in sinovlar:
    try:
        sinov()
    except Exception as xato:
        print(f"{type(xato).__name__}: {xato}")

Natija:

Natija
ValueError: invalid literal for int() with base 10: 'beş'
TypeError: can only concatenate str (not "int") to str
ZeroDivisionError: division by zero
KeyError: 'anor'
IndexError: list index out of range
FileNotFoundError: [Errno 2] No such file or directory: 'mavjud_emas.txt'

try va except

Istisnoni uşlab, dasturni töxtatmasdan davom ettiriş uçun try ... except işlatiladi:

  • try blokiga xato berişi mumkin bölgan kod yoziladi.
  • except blokiga xato yuz berganda nima qiliş kerakligi yoziladi.
Python
matn = "yigirma"

try:
    yosh = int(matn)
    print("Yoşingiz:", yosh)
except ValueError:
    print(f"'{matn}' — son emas. Iltimos, raqamlar bilan yozing.")

print("Dastur davom etmoqda...")

Natija:

Natija
'yigirma' — son emas. Iltimos, raqamlar bilan yozing.
Dastur davom etmoqda...

int(matn) xato bergan zahoti Python try blokining qolgan qismini taşlab, töğridan-töğri except ga ötadi. print("Yoşingiz:", ...) qatori bajarilmaydi.

Bir neçta except

Bitta try blokidan turli xatolar kelişi mumkin. Har biri uçun alohida except yozasiz — Python ularni yuqoridan pastga tekşiradi va birinçi mos kelganini bajaradi.

Python
narxlar = {"non": 4000, "sut": 12000, "tuxum": 1500}


def jami_narx(mahsulot, miqdor):
    try:
        jami = narxlar[mahsulot] * int(miqdor)
        print(f"{miqdor} ta {mahsulot}: {jami} söm")
    except KeyError:
        print(f"Keçirasiz, dökonda '{mahsulot}' yöq.")
    except ValueError:
        print(f"Miqdor notöğri: '{miqdor}'")


jami_narx("non", "3")
jami_narx("asal", "1")
jami_narx("sut", "ikki")

Natija:

Natija
3 ta non: 12000 söm
Keçirasiz, dökonda 'asal' yöq.
Miqdor notöğri: 'ikki'

Agar bir neça xatoga bir xil munosabat bildirmoqçi bölsangiz, ularni qavs içida birlaştiring: except (KeyError, ValueError):.

except ... as e — xato haqida maʼlumot oliş

as yordamida istisno obyektini özgaruvçiga oliş mumkin. Unda xato xabari saqlanadi.

Python
qiymatlar = ["12", "3.5", "", "7"]

for q in qiymatlar:
    try:
        print(int(q) * 2)
    except ValueError as e:
        print("Xato:", e)

Natija:

Natija
24
Xato: invalid literal for int() with base 10: '3.5'
Xato: invalid literal for int() with base 10: ''
14

else va finally

try konstruksiyasining yana ikki ixtiyoriy qismi bor:

  • else — xato bölmagandagina bajariladi. Xato berişi mumkin bölmagan, lekin muvaffaqiyatga boğliq kodni şu yerga yoziş try blokini qisqa va aniq qiladi.
  • finally — har doim bajariladi: xato bölsa ham, bölmasa ham, hatto return bölsa ham. Odatda resurslarni yopiş yoki yakuniy xabar uçun işlatiladi.
Python
def boluvchi(a, b):
    print(f"--- {a} / {b} ---")
    try:
        natija = a / b
    except ZeroDivisionError:
        print("Nolga bölib bölmaydi!")
    else:
        print("Javob:", natija)
    finally:
        print("Hisob yakunlandi.")


boluvchi(10, 4)
boluvchi(7, 0)

Natija:

Natija
--- 10 / 4 ---
Javob: 2.5
Hisob yakunlandi.
--- 7 / 0 ---
Nolga bölib bölmaydi!
Hisob yakunlandi.

Töliq tartib quyidagiça: try → (xato bölsa) except yoki (xato bölmasa) else → har doim finally.

raise — istisnoni özingiz qözğatiş

Baʼzan Python nuqtai nazaridan hammasi joyida, lekin sizning dasturingiz mantiği uçun qiymat yaroqsiz böladi. Masalan, yoş manfiy bölişi mumkin emas. Bunday holda raise bilan istisnoni özingiz "otasiz".

Python
def yosh_belgila(yosh: int) -> int:
    if not isinstance(yosh, int):
        raise TypeError("Yoş butun son bölişi kerak")
    if yosh < 0 or yosh > 150:
        raise ValueError(f"Yoş 0 va 150 oraliğida bölişi kerak, siz {yosh} berdingiz")
    return yosh


for qiymat in [25, -3, "ön"]:
    try:
        print("Qabul qilindi:", yosh_belgila(qiymat))
    except (TypeError, ValueError) as e:
        print("Rad etildi:", e)

Natija:

Natija
Qabul qilindi: 25
Rad etildi: Yoş 0 va 150 oraliğida bölişi kerak, siz -3 berdingiz
Rad etildi: Yoş butun son bölişi kerak

except içida yozilgan yolğiz raise esa uşlangan xatoni qayta yuqoriga uzatadi. Bu xatoni qayd qilib (masalan, jurnalga yozib), lekin uni yaşirmaslik kerak bölganda foydali.

Öz istisno sinflaringiz

Katta dasturlarda standart istisnolar yetmay qoladi. Exception sinfidan meros olib, öz istisnoingizni yaratiş mumkin. (Sinflar va meros haqida 16-bobda batafsil gaplaşamiz — hozirça şablonni eslab qoliş kifoya.)

Python
class MablagYetarliEmas(Exception):
    """Hisobda pul yetmaganda qözğatiladi."""

    def __init__(self, balans, sorov):
        self.balans = balans
        self.sorov = sorov
        super().__init__(f"Balans {balans} söm, lekin {sorov} söm söraldi")


def pul_yechish(balans, summa):
    if summa > balans:
        raise MablagYetarliEmas(balans, summa)
    return balans - summa


balans = 150_000
for summa in [50_000, 200_000]:
    try:
        balans = pul_yechish(balans, summa)
        print(f"{summa} söm yeçildi. Qoldiq: {balans}")
    except MablagYetarliEmas as e:
        print("Amal bajarilmadi:", e)
        print("Yetişmayotgan summa:", e.sorov - e.balans)

Natija:

Natija
50000 söm yeçildi. Qoldiq: 100000
Amal bajarilmadi: Balans 100000 söm, lekin 200000 söm söraldi
Yetişmayotgan summa: 100000

Öz istisnoingizning afzalligi — uni boşqa xatolardan aniq ajratib uşlaş mumkin va unga qöşimça maʼlumot (bu yerda balans va sorov) biriktiriladi.

Amaliyot: xavfsiz böliş

Keling, heç qaçon "qulamaydigan" böliş funksiyasini yozamiz. U har qanday kiritişni qabul qiladi va natija yoki tuşunarli xabar qaytaradi.

Python
def xavfsiz_bolish(a, b):
    try:
        return float(a) / float(b)
    except ZeroDivisionError:
        return "Xato: nolga bölib bölmaydi"
    except (ValueError, TypeError):
        return f"Xato: {a!r} yoki {b!r} son emas"


misollar = [(10, 4), ("15", "3"), (8, 0), ("on", 2), (None, 5)]
for a, b in misollar:
    print(f"{a!r} / {b!r} → {xavfsiz_bolish(a, b)}")

Natija:

Natija
10 / 4 → 2.5
'15' / '3' → 5.0
8 / 0 → Xato: nolga bölib bölmaydi
'on' / 2 → Xato: 'on' yoki 2 son emas
None / 5 → Xato: None yoki 5 son emas

Amaliyot: işonçli kiritiş öquvçisi

Foydalanuvçi har doim ham töğri maʼlumot kiritmaydi: harf yozib yuboradi, manfiy son beradi, böş qoldiradi. Şuning uçun kiritişni tsikl içida tekşiriş odat tusiga kirgan: töğri qiymat kelmagunça qayta söraymiz.

Funksiyamiz kiritiş funksiyasini parametr sifatida oladi. Odatda bu input, lekin sinov uçun örniga oldindan tayyorlangan javoblarni bera olamiz.

Python
def son_oqi(savol, eng_kichik=None, eng_katta=None, kiritish=input):
    while True:
        matn = kiritish(savol).strip()
        try:
            son = int(matn)
        except ValueError:
            print("  ✗ Butun son kiriting.")
            continue
        if eng_kichik is not None and son < eng_kichik:
            print(f"  ✗ Son {eng_kichik} dan kiçik bölmasin.")
        elif eng_katta is not None and son > eng_katta:
            print(f"  ✗ Son {eng_katta} dan katta bölmasin.")
        else:
            return son


# Sinov: foydalanuvçi ketma-ket şu javoblarni yozdi deb faraz qilamiz
javoblar = iter(["ön sakkiz", "-4", "250", "19"])

def soxta_kiritish(savol):
    javob = next(javoblar)
    print(savol + javob)
    return javob


yosh = son_oqi("Yoşingiz: ", 0, 120, kiritish=soxta_kiritish)
print("Qabul qilindi:", yosh)

Natija:

Natija
Yoşingiz: ön sakkiz
  ✗ Butun son kiriting.
Yoşingiz: -4
  ✗ Son 0 dan kiçik bölmasin.
Yoşingiz: 250
  ✗ Son 120 dan katta bölmasin.
Yoşingiz: 19
Qabul qilindi: 19

Haqiqiy dasturda şunçaki yosh = son_oqi("Yoşingiz: ", 0, 120) deb çaqirasiz va funksiya oddiy input() bilan işlaydi.

Xatolarni jamlab hisobot beriş

Köpinça bitta xato tufayli butun işni töxtatiş şart emas. Masalan, talabalar baholari matn körinişida keldi va ularning baʼzilari buzilgan. Yaroqlilarini hisoblab, yaroqsizlari haqida xabar beramiz:

Python
qatorlar = ["Aziza:95", "Bekzod:87", "Gulnoz:", "Doniyor:yuz", "Erkin:72", "Feruza"]

baholar = {}
xatolar = []
for qator in qatorlar:
    try:
        ism, baho = qator.split(":")
        baholar[ism] = int(baho)
    except ValueError as e:
        xatolar.append(f"'{qator}' — {e}")

print("Yaroqli baholar:", baholar)
print(f"Örtaça: {sum(baholar.values()) / len(baholar):.1f}")
print("Ötkazib yuborilgan qatorlar:")
for x in xatolar:
    print(" -", x)

Natija:

Natija
Yaroqli baholar: {'Aziza': 95, 'Bekzod': 87, 'Erkin': 72}
Örtaça: 84.7
Ötkazib yuborilgan qatorlar:
 - 'Gulnoz:' — invalid literal for int() with base 10: ''
 - 'Doniyor:yuz' — invalid literal for int() with base 10: 'yuz'
 - 'Feruza' — not enough values to unpack (expected 2, got 1)

Xulosa

  • Sintaksis xatosi dasturni umuman işga tuşirmaydi; istisno esa işlaş paytida yuz beradi.
  • traceback ni pastdan yuqoriga öqing: oxirgi qator — xato turi, undan yuqoridagilar — çaqiruvlar zanjiri.
  • try/except istisnoni uşlaydi; iloji boriça aniq turini yozing (ValueError, KeyError...).
  • except ... as e xato xabarini beradi, else faqat muvaffaqiyatda, finally har doim bajariladi.
  • raise bilan özingiz istisno qözğatasiz; Exception dan meros olib, öz istisno sinfingizni yaratasiz.
  • Foydalanuvçi kiritişini tsikl içida tekşirib, töğri qiymat kelgunça qayta sörang.

Maşqlar

  1. royxatdan_ol(royxat, indeks) funksiyasini yozing: element mavjud bölsa uni, aks holda "Bunday indeks yöq" matnini qaytarsin. [10, 20, 30] röyxati uçun 1 va 7 indekslarini sinang.
Yeçim
Python
def royxatdan_ol(royxat, indeks):
    try:
        return royxat[indeks]
    except IndexError:
        return "Bunday indeks yöq"


sonlar = [10, 20, 30]
print(royxatdan_ol(sonlar, 1))
print(royxatdan_ol(sonlar, 7))
  1. ["5", "12", "abc", "0", "7.5", "3"] röyxatidagi har bir qiymatni int ga aylantirib, 100 ni unga böling. Har bir holat uçun natija yoki xato turini çiqaring.
Yeçim
Python
for q in ["5", "12", "abc", "0", "7.5", "3"]:
    try:
        print(q, "→", 100 / int(q))
    except ZeroDivisionError:
        print(q, "→ nolga böliş")
    except ValueError:
        print(q, "→ butun son emas")
  1. harorat_tekshir(gradus) funksiyasini yozing: agar harorat -90 dan past yoki 60 dan yuqori bölsa (Yerda bunday ob-havo deyarli bölmaydi), ValueError qözğatsin. 25, -120, 70 qiymatlari bilan sinang.
Yeçim
Python
def harorat_tekshir(gradus):
    if not -90 <= gradus <= 60:
        raise ValueError(f"{gradus}°C — işonib bölmaydigan harorat")
    return gradus


for g in [25, -120, 70]:
    try:
        print("Töğri:", harorat_tekshir(g))
    except ValueError as e:
        print("Xato:", e)
  1. ParolZaif nomli öz istisno sinfingizni yarating. parol_tekshir(parol) funksiyasi parol 8 belgidan qisqa bölsa yoki unda raqam bölmasa ParolZaif qözğatsin. "salom", "salomdunyo", "salom2025" bilan sinang.
Yeçim
Python
class ParolZaif(Exception):
    pass


def parol_tekshir(parol):
    if len(parol) < 8:
        raise ParolZaif("kamida 8 belgi bölişi kerak")
    if not any(b.isdigit() for b in parol):
        raise ParolZaif("kamida bitta raqam bölişi kerak")
    return True


for p in ["salom", "salomdunyo", "salom2025"]:
    try:
        parol_tekshir(p)
        print(p, "— yaxşi parol")
    except ParolZaif as e:
        print(p, "— zaif:", e)
  1. finally qaçon bajarilişini tekşiring: funksiya içida try blokidan return qilinsa ham finally işlaşini körsatadigan kiçik misol yozing.
Yeçim
Python
def sinov():
    try:
        print("try içida")
        return "qaytarilgan qiymat"
    finally:
        print("finally baribir işladi")


print(sinov())